diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9932879 --- /dev/null +++ b/composer.lock @@ -0,0 +1,82 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7d5fe279e64858f72c1db8f82722e08b", + "packages": [ + { + "name": "workerman/workerman", + "version": "v4.1.15", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/afc8242fc769ab7cf22eb4ac22b97cb59d465e4e", + "reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2024-02-19T02:10:39+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/config.php b/config.php index d4f1de8..1ab3c52 100644 --- a/config.php +++ b/config.php @@ -8,7 +8,7 @@ $config = [ } ], "log_level" => LOG_DEBUG, - "tcp_port" => 1080, + "tcp_port" => 20501, "udp_port" => 0, - "wanIP" => '192.168.1.1', + "wanIP" => '10.88.0.4', ]; diff --git a/superbench.log b/superbench.log new file mode 100644 index 0000000..0ecc38a --- /dev/null +++ b/superbench.log @@ -0,0 +1,11 @@ +---------------------------------------------------------------------------------- + Node Name Upload Speed Download Speed Latency Packet Loss + Speedtest.net 774.65 Mbit/s 648.17 Mbit/s (*)94.45 ms Not available + Zhenjiang 5G CT 644.54 Mbit/s 30.92 Mbit/s 46.07 ms Not available + Nanjing 5G CT 24.85 Mbit/s 744.16 Mbit/s 41.06 ms 13.0% + Hangzhou CT 521.82 Mbit/s 991.68 Mbit/s 38.51 ms Not available + Shanghai 5G CU 941.08 Mbit/s 1159.90 Mbit/s 82.42 ms 0.0% + TianJin 5G CU 1.46 Mbit/s 10.20 Mbit/s 199.76 ms Not available + Beijing CM 918.89 Mbit/s 1135.99 Mbit/s 73.82 ms Not available + Chengdu BN 896.37 Mbit/s 26.72 Mbit/s 75.30 ms Not available +---------------------------------------------------------------------------------- diff --git a/superbench.sh b/superbench.sh new file mode 100644 index 0000000..28f96b0 --- /dev/null +++ b/superbench.sh @@ -0,0 +1,1095 @@ +#!/usr/bin/env bash +# +# Description: Auto system info & I/O test & network to China script +# + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +SKYBLUE='\033[0;36m' +PLAIN='\033[0m' +BrowserUA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" +GeekbenchTest='Y' + +about() { + echo "" + echo " ========================================================= " + echo " \ Superbench.sh Script / " + echo " \ Basic system info, I/O test and speedtest / " + echo " \ v1.3.12 (2024-05-10) / " + echo " ========================================================= " + echo "" + echo "" +} + +cancel() { + echo "" + next; + echo " Abort ..." + echo " Cleanup ..." + cleanup; + echo " Done" + exit +} + +trap cancel SIGINT + +benchinit() { + if [ -f /etc/redhat-release ]; then + release="centos" + elif cat /etc/issue | grep -Eqi "debian"; then + release="debian" + elif cat /etc/issue | grep -Eqi "ubuntu"; then + release="ubuntu" + elif cat /etc/issue | grep -Eqi "centos|red hat|redhat"; then + release="centos" + elif cat /proc/version | grep -Eqi "debian"; then + release="debian" + elif cat /proc/version | grep -Eqi "ubuntu"; then + release="ubuntu" + elif cat /proc/version | grep -Eqi "rockylinux"; then + release="rockylinux" + elif cat /proc/version | grep -Eqi "centos|red hat|redhat"; then + release="centos" + fi + + [[ $EUID -ne 0 ]] && echo -e "${RED}Error:${PLAIN} This script must be run as root!" && exit 1 + + # determine architecture of host + ARCH=$(uname -m) + if [[ $ARCH = *x86_64* ]]; then + # host is running a 64-bit kernel + ARCH="x64" + elif [[ $ARCH = *i?86* ]]; then + # host is running a 32-bit kernel + ARCH="x86" + elif [[ $ARCH = *aarch* || $ARCH = *arm* ]]; then + KERNEL_BIT=`getconf LONG_BIT` + if [[ $KERNEL_BIT = *64* ]]; then + # host is running an ARM 64-bit kernel + ARCH="aarch64" + else + # host is running an ARM 32-bit kernel + ARCH="arm" + fi + echo -e "\nARM compatibility is considered *experimental*" + else + # host is running a non-supported kernel + echo -e "Architecture not supported by Superbench." + exit 1 + fi + + if [[ "$(command -v dmidecode)" == "" ]]; then + echo " Installing Dmidecode ..." + if [[ ! -z "$(type -p yum)" ]]; then + yum -y install dmidecode > /dev/null 2>&1 + else + apt-get update > /dev/null 2>&1 + apt-get -y install dmidecode > /dev/null 2>&1 + fi + fi + + if [[ "$(command -v curl)" == "" ]]; then + echo " Installing Curl ..." + if [[ ! -z "$(type -p yum)" ]]; then + yum -y install curl > /dev/null 2>&1 + else + apt-get update > /dev/null 2>&1 + apt-get -y install curl > /dev/null 2>&1 + fi + fi + + if [[ "$(command -v tar)" == "" ]]; then + echo " Installing Tar ..." + if [[ ! -z "$(type -p yum)" ]]; then + yum -y install tar > /dev/null 2>&1 + else + apt-get update > /dev/null 2>&1 + apt-get -y install tar > /dev/null 2>&1 + fi + fi + + if [[ "$(command -v wget)" == "" ]]; then + echo " Installing Wget ..." + if [[ ! -z "$(type -p yum)" ]]; then + yum -y install wget > /dev/null 2>&1 + else + apt-get update > /dev/null 2>&1 + apt-get -y install wget > /dev/null 2>&1 + fi + fi + + if [[ "$(command -v unzip)" == "" ]]; then + echo " Installing UnZip ..." + if [[ ! -z "$(type -p yum)" ]]; then + yum -y install unzip > /dev/null 2>&1 + else + apt-get update > /dev/null 2>&1 + apt-get -y install unzip > /dev/null 2>&1 + fi + fi + + IsGlobal="0" + delay="$(ping -4 -c 2 -w 2 www.google.com | grep rtt | cut -d'/' -f4 | awk '{ print $3 }' | sed -n '/^[0-9]\+\(\.[0-9]\+\)\?$/p')"; + if [ "$delay" != "" ] ; then + IsGlobal="1" + fi + + if [ ! -e './speedtest-cli/speedtest' ]; then + echo " Installing Speedtest-cli ..." + wget --no-check-certificate -qO speedtest.tgz https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-$(uname -m).tgz > /dev/null 2>&1 + if [[ $? -ne '0' ]]; then + wget --no-check-certificate -qO speedtest.tgz https://down.vpsaff.net/linux/speedtest/ookla-speedtest/1.2.0/ookla-speedtest-1.2.0-linux-$(uname -m).tgz > /dev/null 2>&1 + fi + fi + mkdir -p speedtest-cli && tar zxvf speedtest.tgz -C ./speedtest-cli/ > /dev/null 2>&1 && chmod a+rx ./speedtest-cli/speedtest + + # ipip.net besttrace + if [ ! -e './besttrace4/besttrace' ]; then + echo " Installing Best Trace..." + #wget --no-check-certificate -T 10 -qO besttrace4linux.zip https://cdn.ipip.net/17mon/besttrace4linux.zip > /dev/null 2>&1 + #if [[ $? -ne '0' ]]; then + wget --no-check-certificate -O besttrace4linux.zip https://down.vpsaff.net/linux/besttrace4linux.zip > /dev/null 2>&1 + #fi + unzip besttrace4linux.zip -d besttrace4 > /dev/null 2>&1 + fi + chmod +x ./besttrace4/besttrace + + # worsttrace + #if [ ! -e './worsttrace' ]; then + # echo " Installing WorstTrace..." + # wget --no-check-certificate -T 10 -qO worsttrace https://down.vpsaff.net/linux/worsttrace/worsttrace > /dev/null 2>&1 + #fi + #chmod +x ./worsttrace + + # nexttrace + #if [ ! -e './nexttrace' ]; then + # if [[ "$ARCH" == "x64" ]]; then + # wget --no-check-certificate -T 10 -qO nexttrace https://down.vpsaff.net/linux/nexttrace/nexttrace_linux_amd64 > /dev/null 2>&1 + # elif [[ "$ARCH" == "x86" ]]; then + # wget --no-check-certificate -T 10 -qO nexttrace https://down.vpsaff.net/linux/nexttrace/nexttrace_linux_386 > /dev/null 2>&1 + # elif [[ "$ARCH" == "aarch64" ]]; then + # wget --no-check-certificate -T 10 -qO nexttrace https://down.vpsaff.net/linux/nexttrace/nexttrace_linux_amd64 > /dev/null 2>&1 + # elif [[ "$ARCH" == "arm" ]]; then + # wget --no-check-certificate -T 10 -qO nexttrace https://down.vpsaff.net/linux/nexttrace/nexttrace_linux_armv7 > /dev/null 2>&1 + # fi + #fi + #chmod +x ./nexttrace + + if [[ "$GeekbenchTest" == "Y" ]]; then + if [[ ! -e './geekbench' ]]; then + mkdir geekbench + fi + GeekbenchVer=6 + if [[ $ARCH = *x86* ]]; then + download_geekbench4; + $GeekbenchVer=4 + elif [[ $ARCH != *aarch64* && $ARCH != *arm* ]]; then + if [ ! -e './geekbench/geekbench6' ]; then + echo " Installing Geekbench 6..." + if [[ "$IsGlobal" == "1" ]];then + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-6.0.3-Linux.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + else + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-6.0.3-Linux.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + fi + fi + chmod +x ./geekbench/geekbench6 + elif [[ $ARCH == *aarch64* ]]; then + if [ ! -e './geekbench/geekbench6' ]; then + echo " Installing Geekbench 6..." + if [[ "$IsGlobal" == "1" ]];then + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-6.0.3-LinuxARMPreview.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + else + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-6.0.3-LinuxARMPreview.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + fi + fi + chmod +x ./geekbench/geekbench6 + else + if [ ! -e './geekbench/geekbench5' ]; then + echo " Installing Geekbench 5..." + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-5.4.4-LinuxARMPreview.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + fi + chmod +x ./geekbench/geekbench5 + fi + fi + + sleep 5 + + start=$(date +%s) +} + +download_geekbench4(){ + if [[ ! -d ./geekbench ]]; then + mkdir geekbench + fi + if [[ ! -d ./geekbench/geekbench4 ]]; then + echo -n -e " Installing Geekbench 4..." + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-4.4.4-Linux.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + fi + chmod +x ./geekbench/geekbench4 +} + +download_geekbench5(){ + if [[ ! -d ./geekbench ]]; then + mkdir geekbench + fi + if [[ ! -d ./geekbench/geekbench5 ]]; then + echo " Installing Geekbench 5..." + wget -qO- https://down.vpsaff.net/linux/geekbench/Geekbench-5.4.4-Linux.tar.gz | tar xz --strip-components=1 -C ./geekbench &>/dev/null + fi + chmod +x ./geekbench/geekbench5 +} + +get_opsy() { + [ -f /etc/redhat-release ] && awk '{print ($1,$3~/^[0-9]/?$3:$4)}' /etc/redhat-release && return + [ -f /etc/os-release ] && awk -F'[= "]' '/PRETTY_NAME/{print $3,$4,$5}' /etc/os-release && return + [ -f /etc/lsb-release ] && awk -F'[="]+' '/DESCRIPTION/{print $2}' /etc/lsb-release && return +} + +next() { + printf "%-82s\n" "-" | sed 's/\s/-/g' | tee -a $log +} + +speed_test(){ + if [[ $1 == '' ]]; then + speedtest-cli/speedtest -p no --accept-license --accept-gdpr > $speedLog 2>&1 + is_upload=$(cat $speedLog | grep 'Upload') + result_speed=$(cat $speedLog | awk -F ' ' '/Result/{print $3}') + if [[ ${is_upload} ]]; then + local REDownload=$(cat $speedLog | awk -F ' ' '/Download/{print $3}') + local reupload=$(cat $speedLog | awk -F ' ' '/Upload/{print $3}') + local relatency=$(cat $speedLog | awk -F ' ' '/Idle/{print $3}') + local packetLoss=$(cat $speedLog | awk -F ' ' '/Packet/{print $3}') + local result=$(cat $speedLog | grep "Packet Loss: Not available.") + if [[ "$result" != "" ]]; then + packetLoss="Not available" + fi + temp=$(echo "$relatency" | awk -F '.' '{print $1}') + if [[ ${temp} -gt 50 ]]; then + relatency="(*)"${relatency} + fi + local nodeName=$2 + + temp=$(echo "${REDownload}" | awk -F ' ' '{print $1}') + if [[ $(awk -v num1=${temp} -v num2=0 'BEGIN{print(num1>num2)?"1":"0"}') -eq 1 ]]; then + printf "${YELLOW}%-18s${GREEN}%-18s${RED}%-20s${SKYBLUE}%-12s${PLAIN}%-20s\n" " ${nodeName}" "${reupload} Mbit/s" "${REDownload} Mbit/s" "${relatency} ms" "${packetLoss}" | tee -a $log + fi + else + local cerror="ERROR" + fi + else + speedtest-cli/speedtest -p no -s $1 --accept-license --accept-gdpr > $speedLog 2>&1 + is_upload=$(cat $speedLog | grep 'Upload') + if [[ ${is_upload} ]]; then + local REDownload=$(cat $speedLog | awk -F ' ' '/Download/{print $3}') + local reupload=$(cat $speedLog | awk -F ' ' '/Upload/{print $3}') + local relatency=$(cat $speedLog | awk -F ' ' '/Idle/{print $3}') + local packetLoss=$(cat $speedLog | awk -F ' ' '/Packet/{print $3}') + local result=$(cat $speedLog | grep "Packet Loss: Not available.") + if [[ "$result" != "" ]]; then + packetLoss="Not available" + fi + local nodeName=$2 + + temp=$(echo "${REDownload}" | awk -F ' ' '{print $1}') + if [[ $(awk -v num1=${temp} -v num2=0 'BEGIN{print(num1>num2)?"1":"0"}') -eq 1 ]]; then + printf "${YELLOW}%-18s${GREEN}%-18s${RED}%-20s${SKYBLUE}%-12s${PLAIN}%-20s\n" " ${nodeName}" "${reupload} Mbit/s" "${REDownload} Mbit/s" "${relatency} ms" "${packetLoss}" | tee -a $log + fi + else + local cerror="ERROR" + fi + fi +} + +print_china_speedtest() { + printf "%-18s%-18s%-20s%-12s%-20s\n" " Node Name" "Upload Speed" "Download Speed" "Latency" "Packet Loss" | tee -a $log + speed_test '' 'Speedtest.net' +# speed_test '3633' 'Shanghai CT' +# speed_test '27594' 'Guangzhou 5G CT' + speed_test '36663' 'Zhenjiang 5G CT' + speed_test '26352' 'Nanjing 5G CT' + speed_test '35722' 'TianJin CT' +# speed_test '28225' 'Changsha 5G CT' + speed_test '59386' 'Hangzhou CT' +# speed_test '23844' 'Wuhan CT' + speed_test '5145' 'Beijing CU' + speed_test '24447' 'Shanghai 5G CU' +# speed_test '26678' 'Guangzhou 5G CU' +# speed_test '16192' 'ShenZhen CU' +# speed_test '9484' 'Changchun CU' + speed_test '27154' 'TianJin 5G CU' + speed_test '54432' 'SanYa CU' +# speed_test '45170' 'Wu Xi CU' + speed_test '13704' 'Nanjing CU' +# speed_test '37235' 'Shenyang CU' +# speed_test '37235' 'Shenyang CU' +# speed_test '41009' 'Wuhan 5G CU' + speed_test '25637' 'Shanghai 5G CM' +# speed_test '26656' 'Harbin CM' + speed_test '26940' 'Yinchuan CM' + speed_test '27249' 'Nanjing 5G CM' +# speed_test '40131' 'Suzhou 5G CM' +# speed_test '15863' 'Nanning CM' + speed_test '25858' 'Beijing CM' + speed_test '4575' 'Chengdu CM' + speed_test '54312' 'Hangzhou 5G CM' +# speed_test '60794' 'GuangZhou 5G CM' +# speed_test '5505' 'Beijing BN' + speed_test '35527' 'Chengdu BN' +} + +print_global_speedtest() { + printf "%-18s%-18s%-20s%-12s%-20s\n" " Node Name" "Upload Speed" "Download Speed" "Latency" "Packet Loss" | tee -a $log + speed_test '1536' 'Hong Kong CN' + speed_test '33250' 'Macau CN' + speed_test '29106' 'Taiwan CN' + speed_test '40508' 'Singapore SG' +# speed_test '4956' 'Kuala Lumpur MY' +# speed_test '38134' 'Fukuoka JP' + speed_test '28910' 'Tokyo JP' + speed_test '6527' 'Seoul KR' + speed_test '18229' 'Los Angeles US' +# speed_test '15786' 'San Jose US' + speed_test '41248' 'London UK' + speed_test '10010' 'Frankfurt DE' + speed_test '21268' 'France FR' +} + +print_speedtest_fast() { + printf "%-18s%-18s%-20s%-12s%-20s\n" " Node Name" "Upload Speed" "Download Speed" "Latency" "Packet Loss" | tee -a $log + speed_test '' 'Speedtest.net' + speed_test '3633' 'Shanghai CT' + speed_test '27594' 'Guangzhou 5G CT' + speed_test '24447' 'Shanghai 5G CU' + speed_test '9484' 'Changchun CU' + speed_test '45170' 'Wu Xi CU' + speed_test '25637' 'Shanghai 5G CM' + speed_test '15863' 'Nanning CM' + speed_test '4575' 'Chengdu CM' + speed_test '5505' 'Beijing BN' + speed_test '35527' 'Chengdu BN' + + rm -rf speedtest* +} + +io_test() { + (LANG=C dd if=/dev/zero of=test_file_$$ bs=512K count=$1 conv=fdatasync && rm -f test_file_$$ ) 2>&1 | awk -F, '{io=$NF} END { print io}' | sed 's/^[ \t]*//;s/[ \t]*$//' +} + +calc_disk() { + local total_size=0 + local array=$@ + for size in ${array[@]} + do + [ "${size}" == "0" ] && size_t=0 || size_t=`echo ${size:0:${#size}-1}` + [ "`echo ${size:(-1)}`" == "K" ] && size=0 + [ "`echo ${size:(-1)}`" == "M" ] && size=$( awk 'BEGIN{printf "%.1f", '$size_t' / 1024}' ) + [ "`echo ${size:(-1)}`" == "T" ] && size=$( awk 'BEGIN{printf "%.1f", '$size_t' * 1024}' ) + [ "`echo ${size:(-1)}`" == "G" ] && size=${size_t} + total_size=$( awk 'BEGIN{printf "%.1f", '$total_size' + '$size'}' ) + done + echo ${total_size} +} + +power_time() { + + result=$(smartctl -a $(result=$(cat /proc/mounts) && echo $(echo "$result" | awk '/data=ordered/{print $1}') | awk '{print $1}') 2>&1) && power_time=$(echo "$result" | awk '/Power_On/{print $10}') && echo "$power_time" +} + +ip_info4(){ + local org="$(wget -q -T10 -O- ipinfo.io/org)" + local city="$(wget -q -T10 -O- ipinfo.io/city)" + local country="$(wget -q -T10 -O- ipinfo.io/country)" + local region="$(wget -q -T10 -O- ipinfo.io/region)" + if [[ -n "$org" ]]; then + echo -e " Organization : ${YELLOW}$org${PLAIN}" | tee -a $log + fi + if [[ -n "$city" && -n "country" ]]; then + echo -e " Location : ${SKYBLUE}$city / ${YELLOW}$country${PLAIN}" | tee -a $log + fi + if [[ -n "$region" ]]; then + echo -e " Region : ${SKYBLUE}$region${PLAIN}" | tee -a $log + fi +} + +virt_check(){ + if hash ifconfig 2>/dev/null; then + eth=$(ifconfig) + fi + + virtualx=$(dmesg) 2>/dev/null + + if [[ "$(command -v dmidecode)" != "" ]]; then + sys_manu=$(dmidecode -s system-manufacturer) 2>/dev/null + sys_product=$(dmidecode -s system-product-name) 2>/dev/null + sys_ver=$(dmidecode -s system-version) 2>/dev/null + else + sys_manu="" + sys_product="" + sys_ver="" + fi + + if grep docker /proc/1/cgroup -qa; then + virtual="Docker" + elif grep lxc /proc/1/cgroup -qa; then + virtual="Lxc" + elif grep -qa container=lxc /proc/1/environ; then + virtual="Lxc" + elif [[ -f /proc/user_beancounters ]]; then + virtual="OpenVZ" + elif [[ "$virtualx" == *kvm-clock* ]]; then + virtual="KVM" + elif [[ "$cname" == *KVM* ]]; then + virtual="KVM" + elif [[ "$cname" == *QEMU* ]]; then + virtual="KVM" + elif [[ "$virtualx" == *"VMware Virtual Platform"* ]]; then + virtual="VMware" + elif [[ "$virtualx" == *"Parallels Software International"* ]]; then + virtual="Parallels" + elif [[ "$virtualx" == *VirtualBox* ]]; then + virtual="VirtualBox" + elif [[ -e /proc/xen ]]; then + virtual="Xen" + elif [[ "$sys_manu" == *"Microsoft Corporation"* ]]; then + if [[ "$sys_product" == *"Virtual Machine"* ]]; then + if [[ "$sys_ver" == *"7.0"* || "$sys_ver" == *"Hyper-V" ]]; then + virtual="Hyper-V" + else + virtual="Microsoft Virtual Machine" + fi + fi + else + virtual="Dedicated" + fi +} + +freedisk() { + freespace=$( df -m . | awk 'NR==2 {print $4}' ) + if [[ $freespace == "" ]]; then + $freespace=$( df -m . | awk 'NR==3 {print $3}' ) + fi + if [[ $freespace -gt 1024 ]]; then + printf "%s" $((1024*2)) + elif [[ $freespace -gt 512 ]]; then + printf "%s" $((512*2)) + elif [[ $freespace -gt 256 ]]; then + printf "%s" $((256*2)) + elif [[ $freespace -gt 128 ]]; then + printf "%s" $((128*2)) + else + printf "1" + fi +} + +print_io() { + if [[ $1 == "fast" ]]; then + writemb=$((128*2)) + else + writemb=$(freedisk) + fi + + writemb_size="$(( writemb / 2 ))MB" + if [[ $writemb_size == "1024MB" ]]; then + writemb_size="1.0GB" + fi + + if [[ $writemb != "1" ]]; then + echo -n " I/O Speed( $writemb_size ) : " | tee -a $log + io1=$( io_test $writemb ) + echo -e "${YELLOW}$io1${PLAIN}" | tee -a $log + echo -n " I/O Speed( $writemb_size ) : " | tee -a $log + io2=$( io_test $writemb ) + echo -e "${YELLOW}$io2${PLAIN}" | tee -a $log + echo -n " I/O Speed( $writemb_size ) : " | tee -a $log + io3=$( io_test $writemb ) + echo -e "${YELLOW}$io3${PLAIN}" | tee -a $log + ioraw1=$( echo $io1 | awk 'NR==1 {print $1}' ) + [ "`echo $io1 | awk 'NR==1 {print $2}'`" == "GB/s" ] && ioraw1=$( awk 'BEGIN{print '$ioraw1' * 1024}' ) + ioraw2=$( echo $io2 | awk 'NR==1 {print $1}' ) + [ "`echo $io2 | awk 'NR==1 {print $2}'`" == "GB/s" ] && ioraw2=$( awk 'BEGIN{print '$ioraw2' * 1024}' ) + ioraw3=$( echo $io3 | awk 'NR==1 {print $1}' ) + [ "`echo $io3 | awk 'NR==1 {print $2}'`" == "GB/s" ] && ioraw3=$( awk 'BEGIN{print '$ioraw3' * 1024}' ) + ioall=$( awk 'BEGIN{print '$ioraw1' + '$ioraw2' + '$ioraw3'}' ) + ioavg=$( awk 'BEGIN{printf "%.1f", '$ioall' / 3}' ) + echo -e " Average I/O Speed : ${YELLOW}$ioavg MB/s${PLAIN}" | tee -a $log + else + echo -e " ${RED}Not enough space!${PLAIN}" + fi +} + +print_system_info() { + echo -e " CPU Model : ${SKYBLUE}$cname${PLAIN}" | tee -a $log + echo -e " CPU Cores : ${YELLOW}$cores Cores ${SKYBLUE}$freq MHz $arch${PLAIN}" | tee -a $log + if [[ -n "$coresL3cache" ]]; then + echo -e " CPU Cache : ${SKYBLUE}L2 $coresL2cache & ${YELLOW}L3 $coresL3cache${PLAIN}" | tee -a $log + else + echo -e " CPU Cache : ${SKYBLUE}$corescache ${PLAIN}" | tee -a $log + fi + echo -e " CPU Flags : ${SKYBLUE}AES-NI $aes & ${YELLOW}VM-x/AMD-V $virt ${PLAIN}" | tee -a $log + echo -e " OS : ${SKYBLUE}$opsy ($lbit Bit) ${YELLOW}$virtual${PLAIN}" | tee -a $log + echo -e " Kernel : ${SKYBLUE}$kern${PLAIN}" | tee -a $log + echo -e " Total Space : ${SKYBLUE}$disk_used_size GB / ${YELLOW}$disk_total_size GB ${PLAIN}" | tee -a $log + echo -e " Total RAM : ${SKYBLUE}$uram MB / ${YELLOW}$tram MB ${SKYBLUE}($bram MB Buff)${PLAIN}" | tee -a $log + echo -e " Total SWAP : ${SKYBLUE}$uswap MB / $swap MB${PLAIN}" | tee -a $log + echo -e " Uptime : ${SKYBLUE}$up${PLAIN}" | tee -a $log + echo -e " Load Average : ${SKYBLUE}$load${PLAIN}" | tee -a $log + echo -e " TCP CC : ${SKYBLUE}$tcpctrl + ${YELLOW}$qdisc${PLAIN}" | tee -a $log +} + +print_end_time() { + end=$(date +%s) + time=$(( $end - $start )) + if [[ $time -gt 60 ]]; then + min=$(expr $time / 60) + sec=$(expr $time % 60) + echo -ne " Finished in : ${min} min ${sec} sec" | tee -a $log + else + echo -ne " Finished in : ${time} sec" | tee -a $log + fi + + printf '\n' | tee -a $log + + bj_time=$(curl -s http://cgi.im.qq.com/cgi-bin/cgi_svrtime) + + if [[ $(echo $bj_time | grep "html") ]]; then + bj_time=$(date -u +%Y-%m-%d" "%H:%M:%S -d '+8 hours') + fi + echo " Timestamp : $bj_time GMT+8" | tee -a $log + echo " Results : $log" +} + +get_system_info() { + cname=$( awk -F: '/model name/ {name=$2} END {print name}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' ) + cores=$( awk -F: '/model name/ {core++} END {print core}' /proc/cpuinfo ) + freq=$( awk -F: '/cpu MHz/ {freq=$2} END {print freq}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' ) + corescache=$( awk -F: '/cache size/ {cache=$2} END {print cache}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' ) + if [[ -e /sys/devices/system/cpu/cpu0/cache/index2/size ]]; then + coresL2cache=$(cat /sys/devices/system/cpu/cpu0/cache/index2/size) + fi + if [[ -e /sys/devices/system/cpu/cpu0/cache/index3/size ]]; then + coresL3cache=$(cat /sys/devices/system/cpu/cpu0/cache/index3/size) + fi + aes=$(cat /proc/cpuinfo | grep aes) + [[ -z "$aes" ]] && aes="Disabled" || aes="Enabled" + virt=$(cat /proc/cpuinfo | grep 'vmx\|svm') + [[ -z "$virt" ]] && virt="Disabled" || virt="Enabled" + tram=$( free -m | awk '/Mem/ {print $2}' ) + uram=$( free -m | awk '/Mem/ {print $3}' ) + bram=$( free -m | awk '/Mem/ {print $6}' ) + swap=$( free -m | awk '/Swap/ {print $2}' ) + uswap=$( free -m | awk '/Swap/ {print $3}' ) + up=$( awk '{a=$1/86400;b=($1%86400)/3600;c=($1%3600)/60} {printf("%d days %d hour %d min\n",a,b,c)}' /proc/uptime ) + load=$( w | head -1 | awk -F'load average:' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//' ) + opsy=$( get_opsy ) + arch=$( uname -m ) + lbit=$( getconf LONG_BIT ) + kern=$( uname -r ) + + disk_size1=$( LANG=C df -hPl | grep -wvE '\-|none|tmpfs|overlay|shm|udev|devtmpfs|by-uuid|chroot|Filesystem' | awk '{print $2}' ) + disk_size2=$( LANG=C df -hPl | grep -wvE '\-|none|tmpfs|overlay|shm|udev|devtmpfs|by-uuid|chroot|Filesystem' | awk '{print $3}' ) + disk_total_size=$( calc_disk ${disk_size1[@]} ) + disk_used_size=$( calc_disk ${disk_size2[@]} ) + if [[ -e /proc/sys/net/ipv4/tcp_congestion_control ]]; then + tcpctrl=$( sysctl net.ipv4.tcp_congestion_control | awk -F ' ' '{print $3}' ) + fi + if [[ -e /proc/sys/net/core/default_qdisc ]]; then + qdisc=$(sysctl -n net.core.default_qdisc) + fi + + virt_check +} + +traceroute_test() { + #BestTrace + if [ "$2" = "tcp" ] || [ "$2" = "TCP" ]; then + echo -e "\nTraceroute to $4 (TCP Mode, Max $3 Hop)" | tee -a $log + echo -e "============================================================" | tee -a $log + ./besttrace4/besttrace -g en -q 1 -n -T -m $3 $1 | tee -a $log + else + echo -e "\nTracecroute to $4 (ICMP Mode, Max $3 Hop)" | tee -a $log + echo -e "============================================================" | tee -a $log + ./besttrace4/besttrace -g en -q 1 -n -m $3 $1 | tee -a $log + fi + + #WorstTrace + #echo -e "\n worsttrace to $4 (ICMP Mode, Max $3 Hop)" | tee -a $log + #echo -e " =========================================================================================" | tee -a $log + #./worsttrace -n --maxhop $3 $1 | tail -n +5 | tee -a $log + + #NextTrace + #if [ "$2" = "tcp" ] || [ "$2" = "TCP" ]; then + # echo -e "\nNextTrace to $4 (TCP Mode, Max $3 Hop)" | tee -a $log + # echo -e "============================================================" | tee -a $log + # ./nexttrace $1 -m $3 -g en -T -q 1 2>&1 | tail -n +4 | head -n -1 | tee -a $log + #else + # echo -e "\nNextTrace to $4 (ICMP Mode, Max $3 Hop)" | tee -a $log + # echo -e "============================================================" | tee -a $log + # ./nexttrace $1 -m $3 -g en -q 1 2>&1 | tail -n +4 | head -n -1 | tee -a $log + #fi +} + +print_traceroute_test(){ + #echo -e " WorstTrace 2.0.8" + #echo -e " Join Our group at: https://t.me/WorstTrace" + + traceroute_test "113.108.209.1" "TCP" "30" "China, Guangzhou CT" + traceroute_test "180.153.28.5" "TCP" "30" "China, Shanghai CT" + traceroute_test "180.149.128.9" "TCP" "30" "China, Beijing CT" + traceroute_test "210.21.4.130" "TCP" "30" "China, Guangzhou CU" + traceroute_test "58.247.8.158" "TCP" "30" "China, Shanghai CU" + traceroute_test "123.125.99.1" "TCP" "30" "China, Beijing CU" + traceroute_test "120.196.212.25" "TCP" "30" "China, Guangzhou CM" + traceroute_test "221.183.55.22" "TCP" "30" "China, Shanghai CM" + traceroute_test "211.136.25.153" "TCP" "30" "China, Beijing CM" + traceroute_test "211.167.230.100" "TCP" "30" "China, Beijing Dr.Peng Network IDC Network" +} + +geekbench() { + echo -e " Geekbench v${GeekbenchVer} Test :" | tee -a $log + if test -f "geekbench.license"; then + ./geekbench/geekbench$GeekbenchVer --unlock `cat geekbench.license` > /dev/null 2>&1 + fi + + GEEKBENCH_TEST=$(./geekbench/geekbench$GeekbenchVer --upload 2>/dev/null | grep "https://browser") + + if [[ -z "$GEEKBENCH_TEST" ]]; then + echo -e " ${RED}Geekbench v${GeekbenchVer} test failed. Run manually to determine cause.${PLAIN}" | tee -a $log + GEEKBENCH_URL='' + if [[ $ARCH != *aarch64* && $ARCH != *arm* ]]; then + if [[ $GeekbenchVer == *6* ]]; then + rm -rf geekbench + download_geekbench5; + echo -n -e "\r" | tee -a $log + GeekbenchVer=5; + geekbench; + elif [[ $GeekbenchVer == *5* ]]; then + rm -rf geekbench + download_geekbench4; + echo -n -e "\r" | tee -a $log + GeekbenchVer=4; + geekbench; + fi + fi + else + GEEKBENCH_URL=$(echo -e $GEEKBENCH_TEST | head -1) + GEEKBENCH_URL_CLAIM=$(echo $GEEKBENCH_URL | awk '{ print $2 }') + GEEKBENCH_URL=$(echo $GEEKBENCH_URL | awk '{ print $1 }') + sleep 5 + ([[ $GeekbenchVer == *6* ]] || [[ $GeekbenchVer == *5* ]]) && GEEKBENCH_SCORES=$(curl -s $GEEKBENCH_URL | grep "div class='score'") || GEEKBENCH_SCORES=$(curl -s $GEEKBENCH_URL | grep "span class='score'") + GEEKBENCH_SCORES_SINGLE=$(echo $GEEKBENCH_SCORES | awk -v FS="(>|<)" '{ print $3 }') + GEEKBENCH_SCORES_MULTI=$(echo $GEEKBENCH_SCORES | awk -v FS="(>|<)" '{ print $7 }') + + echo -e " Single Core : ${YELLOW}$GEEKBENCH_SCORES_SINGLE $grank${PLAIN}" | tee -a $log + echo -e " Multi Core : ${YELLOW}$GEEKBENCH_SCORES_MULTI${PLAIN}" | tee -a $log + [ ! -z "$GEEKBENCH_URL_CLAIM" ] && echo -e "$GEEKBENCH_URL_CLAIM" >> geekbench_claim.url 2> /dev/null + fi + rm -rf geekbench +} + +function UnlockNetflixTest() { + local result1=$(curl --user-agent "${BrowserUA}" -fsL --write-out %{http_code} --output /dev/null --max-time 10 "https://www.netflix.com/title/81280792" 2>&1) + local result12=$(curl --user-agent "${BrowserUA}" -fsL --write-out %{http_code} --output /dev/null --max-time 10 "https://www.netflix.com/title/70143836" 2>&1) + + if [[ "$result1" == "404" ]] && [[ "$result12" == "404" ]];then + echo -e " Netflix : ${YELLOW}Originals Only${PLAIN}" | tee -a $log + elif [[ "$result1" == "403" ]];then + echo -e " Netflix : ${RED}No${PLAIN}" | tee -a $log + elif [[ "$result1" == "200" ]];then + local region=`tr [:lower:] [:upper:] <<< $(curl --user-agent "${BrowserUA}" -fs --max-time 10 --write-out %{redirect_url} --output /dev/null "https://www.netflix.com/title/80018499" | cut -d '/' -f4 | cut -d '-' -f1)` ; + if [[ ! -n "$region" ]];then + region="US"; + fi + echo -e " Netflix : ${GREEN}Yes (Region: ${region})${PLAIN}" | tee -a $log + elif [[ "$result1" == "000" ]];then + echo -e " Netflix : ${RED}Network connection failed${PLAIN}" | tee -a $log + fi +} + +function UnlockYouTubePremiumTest() { + local tmpresult=$(curl --max-time 10 -sS -H "Accept-Language: en" "https://www.youtube.com/premium" 2>&1 ) + local region=$(curl --user-agent "${BrowserUA}" -sL --max-time 10 "https://www.youtube.com/premium" | grep "countryCode" | sed 's/.*"countryCode"//' | cut -f2 -d'"') + if [ -n "$region" ]; then + sleep 0 + else + isCN=$(echo $tmpresult | grep 'www.google.cn') + if [ -n "$isCN" ]; then + region=CN + else + region=US + fi + fi + + if [[ "$tmpresult" == "curl"* ]];then + echo -e " YouTube Premium : ${RED}Network connection failed${PLAIN}" | tee -a $log + return; + fi + + local result=$(echo $tmpresult | grep 'Premium is not available in your country') + if [ -n "$result" ]; then + echo -e " YouTube Premium : ${RED}No${PLAIN} ${PLAIN}${GREEN} (Region: $region)${PLAIN}" | tee -a $log + return; + + fi + local result=$(echo $tmpresult | grep 'YouTube and YouTube Music ad-free') + if [ -n "$result" ]; then + echo -e " YouTube Premium : ${GREEN}Yes (Region: $region)${PLAIN}" | tee -a $log + return; + else + echo -e " YouTube Premium : ${RED}Failed${PLAIN}" | tee -a $log + fi +} + +function YouTubeCDNTest() { + local tmpresult=$(curl -sS --max-time 10 https://redirector.googlevideo.com/report_mapping 2>&1) + if [[ "$tmpresult" == "curl"* ]];then + echo -e " YouTube Region : ${RED}Network connection failed${PLAIN}" | tee -a $log + return; + fi + + local IATACode=$(curl -s --retry 3 --max-time 10 "https://down.vpsaff.net/iata/IATACode.txt"); + local iata=$(echo $tmpresult | grep '=>'| awk "NR==1" | awk '{print $3}' | cut -f2 -d'-' | cut -c 1-3 | tr [:lower:] [:upper:]) + if [ -n "$iata" ] && [ ${#iata} -eq 3 ]; then + local lineNo=$(echo "$IATACode" | cut -f3 -d"|" | sed -n "/${iata}/=") + if [ -n "$lineNo" ]; then + local location=$(echo "$IATACode" | awk "NR==${lineNo}" | cut -f1 -d"|" | sed -e 's/^[[:space:]]*//') + else + local location="$iata" + fi + fi + local isIDC=$(echo $tmpresult | grep "router") + if [ -n "$iata" ] && [ -z "$isIDC" ]; then + local CDN_ISP=$(echo $tmpresult | awk "NR==1" | awk '{print $3}' | cut -f1 -d"-" | tr [:lower:] [:upper:]) + echo -e " YouTube CDN : ${YELLOW}$CDN_ISP in $location${PLAIN}" | tee -a $log + return + elif [ -n "$iata" ] && [ -n "$isIDC" ]; then + echo -e " YouTube CDN : ${GREEN}$location${PLAIN}" | tee -a $log + return + else + echo -e " YouTube CDN : ${RED}Undetectable${PLAIN}" | tee -a $log + return + fi +} + +function UnlockBilibiliTest() { + #Test Mainland + local randsession="$(cat /dev/urandom | head -n 32 | md5sum | head -c 32)"; + local result=$(curl --user-agent "${BrowserUA}" -fsSL --max-time 10 "https://api.bilibili.com/pgc/player/web/playurl?avid=82846771&qn=0&type=&otype=json&ep_id=307247&fourk=1&fnver=0&fnval=16&session=${randsession}&module=bangumi" 2>&1); + if [[ "$result" != "curl"* ]]; then + result="$(echo "${result}" | grep '"code"' | awk -F 'code":' '{print $2}' | awk -F ',' '{print $1}')"; + if [ "${result}" = "0" ]; then + echo -e " BiliBili China : ${GREEN}Yes (Region: Mainland Only)${PLAIN}" | tee -a $log + return; + fi + else + echo -e " BiliBili China : ${RED}Network connection failed${PLAIN}" | tee -a $log + return; + fi + + #Test Hongkong/Macau/Taiwan + randsession="$(cat /dev/urandom | head -n 32 | md5sum | head -c 32)"; + result=$(curl --user-agent "${BrowserUA}" -fsSL --max-time 10 "https://api.bilibili.com/pgc/player/web/playurl?avid=18281381&cid=29892777&qn=0&type=&otype=json&ep_id=183799&fourk=1&fnver=0&fnval=16&session=${randsession}&module=bangumi" 2>&1); + if [[ "$result" != "curl"* ]]; then + result="$(echo "${result}" | grep '"code"' | awk -F 'code":' '{print $2}' | awk -F ',' '{print $1}')"; + if [ "${result}" = "0" ]; then + echo -e " BiliBili China : ${GREEN}Yes (Region: HongKong/Macau/Taiwan Only)${PLAIN}" | tee -a $log + return; + fi + else + echo -e " BiliBili China : ${RED}Network connection failed${PLAIN}" | tee -a $log + return; + fi + + #Test Taiwan + randsession="$(cat /dev/urandom | head -n 32 | md5sum | head -c 32)"; + result=$(curl --user-agent "${BrowserUA}" -fsSL --max-time 10 "https://api.bilibili.com/pgc/player/web/playurl?avid=50762638&cid=100279344&qn=0&type=&otype=json&ep_id=268176&fourk=1&fnver=0&fnval=16&session=${randsession}&module=bangumi" 2>&1); + if [[ "$result" != "curl"* ]]; then + result="$(echo "${result}" | grep '"code"' | awk -F 'code":' '{print $2}' | awk -F ',' '{print $1}')"; + if [ "${result}" = "0" ]; then + echo -e " BiliBili China : ${GREEN}Yes (Region: Taiwan Only)${PLAIN}" | tee -a $log + return; + fi + else + echo -e " BiliBili China : ${RED}Network connection failed${PLAIN}" | tee -a $log + return; + fi + echo -e " BiliBili China : ${RED}No${PLAIN}" | tee -a $log +} + +function UnlockTiktokTest() { + local result=$(curl --user-agent "${BrowserUA}" -fsSL --max-time 10 "https://www.tiktok.com/" 2>&1); + if [[ "$result" != "curl"* ]]; then + result="$(echo ${result} | grep 'vregion' | awk -F 'vregion":"' '{print $2}' | awk -F '"' '{print $1}')"; + if [ -n "$result" ]; then + if [[ "$result" == "The #TikTokTraditions"* ]] || [[ "$result" == "This LIVE isn't available"* ]]; then + echo -e " TikTok : ${RED}No${PLAIN}" | tee -a $log + else + echo -e " TikTok : ${GREEN}Yes (Region: ${result})${PLAIN}" | tee -a $log + fi + else + echo -e " TikTok : ${RED}Failed${PLAIN}" | tee -a $log + return + fi + else + echo -e " TikTok : ${RED}Network connection failed${PLAIN}" | tee -a $log + fi +} + +function UnlockiQiyiIntlTest() { + curl --user-agent "${BrowserUA}" -s -I --max-time 10 "https://www.iq.com/" >/tmp/iqiyi + if [ $? -eq 1 ]; then + echo -e " iQIYI International : ${RED}Network connection failed${PLAIN}" | tee -a $log + return + fi + + local result="$(cat /tmp/iqiyi | grep 'mod=' | awk '{print $2}' | cut -f2 -d'=' | cut -f1 -d';')"; + rm -f /tmp/iqiyi + + if [ -n "$result" ]; then + if [[ "$result" == "ntw" ]]; then + result=TW + echo -e " iQIYI International : ${GREEN}Yes (Region: ${result})${PLAIN}" | tee -a $log + return + else + result=$(echo $result | tr [:lower:] [:upper:]) + echo -e " iQIYI International : ${GREEN}Yes (Region: ${result})${PLAIN}" | tee -a $log + return + fi + else + echo -e " iQIYI International : ${RED}Failed${PLAIN}" | tee -a $log + return + fi +} + +function UnlockChatGPTTest() { + if [[ $(curl --max-time 10 -sS https://chat.openai.com/ -I | grep "text/plain") != "" ]] + then + echo -e " ChatGPT : ${RED}IP is BLOCKED${PLAIN}" | tee -a $log + return + fi + local countryCode="$(curl --max-time 10 -sS https://chat.openai.com/cdn-cgi/trace | grep "loc=" | awk -F= '{print $2}')"; + if [ $? -eq 1 ]; then + echo -e " ChatGPT : ${RED}Network connection failed${PLAIN}" | tee -a $log + return + fi + if [ -n "$countryCode" ]; then + support_countryCodes=(T1 XX AL DZ AD AO AG AR AM AU AT AZ BS BD BB BE BZ BJ BT BA BW BR BG BF CV CA CL CO KM CR HR CY DK DJ DM DO EC SV EE FJ FI FR GA GM GE DE GH GR GD GT GN GW GY HT HN HU IS IN ID IQ IE IL IT JM JP JO KZ KE KI KW KG LV LB LS LR LI LT LU MG MW MY MV ML MT MH MR MU MX MC MN ME MA MZ MM NA NR NP NL NZ NI NE NG MK NO OM PK PW PA PG PE PH PL PT QA RO RW KN LC VC WS SM ST SN RS SC SL SG SK SI SB ZA ES LK SR SE CH TH TG TO TT TN TR TV UG AE US UY VU ZM BO BN CG CZ VA FM MD PS KR TW TZ TL GB) + if [[ "${support_countryCodes[@]}" =~ "${countryCode}" ]]; then + echo -e " ChatGPT : ${GREEN}Yes (Region: ${countryCode})${PLAIN}" | tee -a $log + return + else + echo -e " ChatGPT : ${RED}No${PLAIN}" | tee -a $log + return + fi + else + echo -e " ChatGPT : ${RED}Failed${PLAIN}" | tee -a $log + return + fi +} + + +function StreamingMediaUnlockTest(){ + echo -e " Unlock Test :" | tee -a $log + UnlockNetflixTest + UnlockYouTubePremiumTest + YouTubeCDNTest + UnlockBilibiliTest + UnlockTiktokTest + UnlockiQiyiIntlTest + UnlockChatGPTTest +} + +print_intro() { + printf ' Superbench.sh -- https://www.idcoffer.com/archives/4764\n' | tee -a $log + printf " Mode : \e${GREEN}%s\e${PLAIN} Version : \e${GREEN}%s${PLAIN}\n" $mode_name 1.3.12 | tee -a $log + printf ' Usage : bash <(wget -qO- https://down.vpsaff.net/linux/speedtest/superbench.sh)\n' | tee -a $log +} + +function get_json_value() { + local json=$1 + local key=$2 + + if [[ -z "$3" ]]; then + local num=1 + else + local num=$3 + fi + + local value=$(echo "${json}" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'${key}'\042/){print $(i+1)}}}' | tr -d '"' | sed -n ${num}p) + + echo ${value} +} + +sharetest() { + if [[ -n "$GEEKBENCH_URL" ]] || [[ -n "$result_speed" ]]; then + echo " Share result:" | tee -a $log + else + echo " Share result:" + fi + if [[ -n "$GEEKBENCH_URL" ]]; then + echo " · $GEEKBENCH_URL" | tee -a $log + fi + if [[ -n "$result_speed" ]]; then + echo " · $result_speed" | tee -a $log + fi + log_preupload + case $1 in + 'wmlabs') + local wmlabsResult=$(curl -X POST -k -s --data-urlencode "content@$log_up" -d "title=superbench.sh的测试报告" -d "author=superbench.sh" -d "ttl=-1" "https://paste.wmlabs.net/" 2>&1); + share_link="https://paste.wmlabs.net/p/"$(get_json_value $wmlabsResult id);; + 'ubuntu') + share_link="https://paste.ubuntu.com"$( curl -v --data-urlencode "content@$log_up" -d "poster=superbench.sh" -d "syntax=text" "https://paste.ubuntu.com" 2>&1 | \ + grep "Location" | awk '{print $3}' );; + 'haste' ) + share_link=$( curl -X POST -s -d "$(cat $log)" https://hastebin.com/documents | awk -F '"' '{print "https://hastebin.com/"$4}' );; + 'clbin' ) + share_link=$( curl -sF 'clbin=<-' https://clbin.com < $log );; + 'ptpb' ) + share_link=$( curl -sF c=@- https://ptpb.pw/?u=1 < $log );; + esac + + echo " · $share_link" | tee -a $log + next + echo "" + rm -f $log_up + +} + +log_preupload() { + log_up="$HOME/superbench_upload.log" + true > $log_up + $(cat superbench.log 2>&1 | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" > $log_up) +} + +cleanup() { + rm -f test_file_* + rm -rf speedtest* + rm -rf besttrace4* + rm -rf geekbench* + rm -rf ./worsttrace + rm -rf ./nexttrace +} + +bench_all(){ + mode_name="Standard" + about; + benchinit; + clear + next; + print_intro; + next; + get_system_info; + print_system_info; + ip_info4; + next; + StreamingMediaUnlockTest; + next; + print_io; + if [[ "$GeekbenchTest" == "Y" ]]; then + next; + geekbench; + fi + next; + print_china_speedtest; + next; + print_global_speedtest; + next; + print_traceroute_test; + next; + print_end_time; + next; + cleanup; + sharetest ubuntu; +} + +fast_bench(){ + mode_name="Fast" + about; + benchinit; + clear + next; + print_intro; + next; + get_system_info; + print_system_info; + ip_info4; + next; + StreamingMediaUnlockTest; + next; + print_io fast; + next; + print_speedtest_fast; + next; + print_end_time; + next; + cleanup; +} + +log="./superbench.log" +true > $log +speedLog="./speedtest.log" +true > $speedLog + +case $1 in + 'info'|'-i'|'--i'|'-info'|'--info' ) + GeekbenchTest='N'; + about;sleep 3;next;get_system_info;print_system_info;next;; + 'version'|'-v'|'--v'|'-version'|'--version') + GeekbenchTest='N'; + next;about;next;; + 'io'|'-io'|'--io'|'-drivespeed'|'--drivespeed' ) + next;print_io;next;; + 'speed'|'-speed'|'--speed'|'-speedtest'|'--speedtest'|'-speedcheck'|'--speedcheck' ) + GeekbenchTest='N'; + about;benchinit;clear;next;print_china_speedtest;next;cleanup;; + 'ip'|'-ip'|'--ip'|'geoip'|'-geoip'|'--geoip' ) + about;benchinit;next;ip_info4;next;cleanup;; + 'bench'|'-a'|'--a'|'-all'|'--all'|'-bench'|'--bench' ) + bench_all;; + 'besttrace'|'-b'|'--b'|'--besttrace' ) + GeekbenchTest='N'; + about;benchinit;clear;print_traceroute_test;next;cleanup;; + 'about'|'-about'|'--about' ) + GeekbenchTest='N'; + about;; + 'fast'|'-f'|'--f'|'-fast'|'--fast' ) + fast_bench;; + 'geekbench'|'-g'|'--geekbench' ) + GeekbenchVer=6; + about;benchinit;clear;geekbench;next;cleanup;; + '--no-geekbench' ) + GeekbenchTest='N'; + bench_all;; + 'media'|'-m'|'--media' ) + GeekbenchTest='N'; + mode_name="MediaTest"; + about;sleep 3;clear;next;print_intro;next;ip_info4;next; + StreamingMediaUnlockTest;next; + log_preupload; + sharetest ubuntu;; + 'share'|'-s'|'--s'|'-share'|'--share' ) + bench_all; + is_share="share" + if [[ $2 == "" ]]; then + sharetest ubuntu; + else + sharetest $2; + fi + ;; + 'debug'|'-d'|'--d'|'-debug'|'--debug' ) + get_ip_whois_org_name;; +*) + bench_all;; +esac + +if [[ ! $is_share == "share" ]]; then + case $2 in + 'share'|'-s'|'--s'|'-share'|'--share' ) + if [[ $3 == '' ]]; then + sharetest ubuntu; + else + sharetest $3; + fi + ;; + esac +fi diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..db49a95 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/workerman/workerman'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..da40084 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..a8456ef --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'Workerman\\' => 10, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Workerman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/workerman', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..6003e81 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,72 @@ +{ + "packages": [ + { + "name": "workerman/workerman", + "version": "v4.1.15", + "version_normalized": "4.1.15.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/afc8242fc769ab7cf22eb4ac22b97cb59d465e4e", + "reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2024-02-19T02:10:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "install-path": "../workerman/workerman" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..1379333 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,32 @@ + array( + 'name' => 'workerman/phptty', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '663e8e8fcf7891b61411bcbeb9515dde8cea924f', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'workerman/phptty' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '663e8e8fcf7891b61411bcbeb9515dde8cea924f', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/workerman' => array( + 'pretty_version' => 'v4.1.15', + 'version' => '4.1.15.0', + 'reference' => 'afc8242fc769ab7cf22eb4ac22b97cb59d465e4e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/workerman', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..f79e574 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/workerman/_www_php-socks5_start.php.pid b/vendor/workerman/_www_php-socks5_start.php.pid new file mode 100644 index 0000000..9aef9e2 --- /dev/null +++ b/vendor/workerman/_www_php-socks5_start.php.pid @@ -0,0 +1 @@ +6911 \ No newline at end of file diff --git a/vendor/workerman/workerman.log b/vendor/workerman/workerman.log new file mode 100644 index 0000000..ce79ae5 --- /dev/null +++ b/vendor/workerman/workerman.log @@ -0,0 +1,100 @@ +2024-06-16 06:02:02 pid:6880 Workerman[start.php] start in DEBUG mode +2024-06-16 06:02:03 pid:6880 Workerman[start.php] stopping ... +2024-06-16 06:02:03 pid:6880 Workerman[start.php] has been stopped +2024-06-16 06:02:06 pid:6909 Workerman[start.php] start in DAEMON mode +2024-07-26 02:40:35 pid:6913 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(560): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#8 {main} +2024-07-26 02:40:35 pid:6911 worker[none:6913] exit with status 64000 +2024-08-22 04:41:53 pid:30460 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-08-22 04:41:53 pid:6911 worker[none:30460] exit with status 64000 +2024-10-16 09:13:08 pid:7425 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-10-16 09:13:08 pid:6911 worker[none:7425] exit with status 64000 +2024-11-01 23:30:00 pid:11414 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-11-01 23:30:00 pid:6911 worker[none:11414] exit with status 64000 +2024-11-03 03:46:28 pid:8250 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-11-03 03:46:28 pid:6911 worker[none:8250] exit with status 64000 +2024-11-03 13:04:45 pid:12375 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-11-03 13:04:45 pid:6911 worker[none:12375] exit with status 64000 +2024-11-25 02:25:35 pid:22517 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435 +Stack trace: +#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage() +#1 [internal function]: Workerman\Worker->acceptUdpConnection() +#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop() +#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop() +#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux() +#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux() +#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers() +#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux() +#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers() +#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll() +#10 {main} +2024-11-25 02:25:35 pid:6911 worker[none:22517] exit with status 64000 diff --git a/vendor/workerman/workerman/.github/FUNDING.yml b/vendor/workerman/workerman/.github/FUNDING.yml new file mode 100644 index 0000000..beae44f --- /dev/null +++ b/vendor/workerman/workerman/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +open_collective: workerman +patreon: walkor diff --git a/vendor/workerman/workerman/.gitignore b/vendor/workerman/workerman/.gitignore new file mode 100644 index 0000000..f3f9e18 --- /dev/null +++ b/vendor/workerman/workerman/.gitignore @@ -0,0 +1,6 @@ +logs +.buildpath +.project +.settings +.idea +.DS_Store diff --git a/vendor/workerman/workerman/Autoloader.php b/vendor/workerman/workerman/Autoloader.php new file mode 100644 index 0000000..7d760e9 --- /dev/null +++ b/vendor/workerman/workerman/Autoloader.php @@ -0,0 +1,69 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = \str_replace('\\', \DIRECTORY_SEPARATOR, $name); + if (\strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . \substr($class_path, \strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . \DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !\is_file($class_file)) { + $class_file = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (\is_file($class_file)) { + require_once($class_file); + if (\class_exists($name, false)) { + return true; + } + } + return false; + } +} + +\spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/vendor/workerman/workerman/Connection/AsyncTcpConnection.php b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..5bc8676 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,378 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use StdClass; +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable|null + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var float + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, array $context_option = array()) + { + $address_info = \parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = \explode(':', $remote_address, 2); + if('unix' === strtolower($scheme)) { + $this->_remoteAddress = substr($remote_address, strpos($remote_address, '/') + 2); + } + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 0; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + $this->_remoteAddress = 'unix' === strtolower($scheme) + ? substr($remote_address, strpos($remote_address, '/') + 2) + : $this->_remoteHost . ':' . $this->_remotePort; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(\PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + ++self::$statistics['connection_count']; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_contextOption = $context_option; + $this->context = new StdClass; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = \microtime(true); + if ($this->transport !== 'unix') { + if (!$this->_remotePort) { + $this->_remotePort = $this->transport === 'ssl' ? 443 : 80; + $this->_remoteAddress = $this->_remoteHost.':'.$this->_remotePort; + } + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = \stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + \STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket || !\is_resource($this->_socket)) { + $this->emitError(\WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + // Remove EV_EXPECT for windows. + if(\DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + if ($this->_status !== self::STATUS_CONNECTING) { + return; + } + + // Check socket state. + if ($address = \stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + \stream_set_blocking($this->_socket, false); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = \socket_import_stream($this->_socket); + \socket_set_option($raw_socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($raw_socket, \SOL_TCP, \TCP_NODELAY, 1); + } + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + if ($this->_sslHandshakeCompleted === false) { + return; + } + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onConnect + if ($this->protocol && \method_exists($this->protocol, 'onConnect')) { + try { + \call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } else { + // Connection failed. + $this->emitError(\WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(\microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/AsyncUdpConnection.php b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..745f060 --- /dev/null +++ b/vendor/workerman/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,203 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * AsyncUdpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callable + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = \ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = \substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = \stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ++ConnectionInterface::$statistics['total_request']; + try { + \call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + if ($this->connected === false) { + $this->connect(); + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + \fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = \stream_context_create($this->_contextOption); + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, \STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + + \stream_set_blocking($this->_socket, false); + + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + +} diff --git a/vendor/workerman/workerman/Connection/ConnectionInterface.php b/vendor/workerman/workerman/Connection/ConnectionInterface.php new file mode 100644 index 0000000..5d815d8 --- /dev/null +++ b/vendor/workerman/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,126 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +#[\AllowDynamicProperties] +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param string|null $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/vendor/workerman/workerman/Connection/TcpConnection.php b/vendor/workerman/workerman/Connection/TcpConnection.php new file mode 100644 index 0000000..740f01d --- /dev/null +++ b/vendor/workerman/workerman/Connection/TcpConnection.php @@ -0,0 +1,982 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Context. + * + * @var object|null + */ + public $context = null; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Sets the maximum acceptable packet size for the current connection. + * + * @var int + */ + public $maxPackageSize = 1048576; + + /** + * Default maximum acceptable packet size. + * + * @var int + */ + public static $defaultMaxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + ++self::$statistics['connection_count']; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === \PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + \stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (\function_exists('stream_set_read_buffer')) { + \stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + $this->context = new \stdClass; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int|string + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer && $this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return; + } + $len = 0; + try { + $len = @\fwrite($this->_socket, $send_buffer); + } catch (\Exception $e) { + Worker::log($e); + } catch (\Error $e) { + Worker::log($e); + } + // send successful. + if ($len === \strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = \substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!\is_resource($this->_socket) || \feof($this->_socket)) { + ++self::$statistics['send_fail']; + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return; + } + + if ($this->bufferIsFull()) { + ++self::$statistics['send_fail']; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return (string) \substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int) \substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + if (!\is_resource($this->_socket)) { + return ''; + } + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return \strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return \strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + $buffer = ''; + try { + $buffer = @\fread($socket, self::READ_BUFFER_SIZE); + } catch (\Exception $e) {} catch (\Error $e) {} + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (\feof($socket) || !\is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += \strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + try { + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + } catch (\Exception $e) {} catch (\Error $e) {} + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('Error package. package_length=' . \var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + ++self::$statistics['total_request']; + // The current packet length is equal to the length of the buffer. + if (\strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = \substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = \substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + \call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + ++self::$statistics['total_request']; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + \call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + \set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = @\fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = @\fwrite($this->_socket, $this->_sendBuffer); + } + \restore_error_handler(); + if ($len === \strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + \call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = \substr($this->_sendBuffer, $len); + } else { + ++self::$statistics['send_fail']; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param resource $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (\feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + + /** + * We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack. + * You can enable ssl3 by the codes below. + */ + /*if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER; + }*/ + + if($async){ + $type = \STREAM_CRYPTO_METHOD_SSLv2_CLIENT | \STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + \set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = \stream_socket_enable_crypto($socket, true, $type); + \restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return 0; + } + if (isset($this->onSslHandshake)) { + try { + \call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param self $dest + * @return void + */ + public function pipe(self $dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->close(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = \substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if($this->_status === self::STATUS_CONNECTING){ + $this->destroy(); + return; + } + + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } + + if ($data !== null) { + $this->send($data, $raw); + } + + $this->_status = self::STATUS_CLOSING; + + if ($this->_sendBuffer === '') { + $this->destroy(); + } else { + $this->pauseRecv(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + \call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + \call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + try { + @\fclose($this->_socket); + } catch (\Exception $e) {} catch (\Error $e) {} + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + \call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + // Try to emit protocol::onClose + if ($this->protocol && \method_exists($this->protocol, 'onClose')) { + try { + \call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + $this->_sendBuffer = $this->_recvBuffer = ''; + $this->_currentPackageLength = 0; + $this->_isPaused = $this->_sslHandshakeCompleted = false; + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = \ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . \posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/vendor/workerman/workerman/Connection/UdpConnection.php b/vendor/workerman/workerman/Connection/UdpConnection.php new file mode 100644 index 0000000..9cd95ba --- /dev/null +++ b/vendor/workerman/workerman/Connection/UdpConnection.php @@ -0,0 +1,208 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'udp'; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return; + } + } + return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0, $this->isIpV6() ? '[' . $this->getRemoteIp() . ']:' . $this->getRemotePort() : $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = \strrpos($this->_remoteAddress, ':'); + if ($pos) { + return \trim(\substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)\substr(\strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return ''; + } + return \substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = \strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)\substr(\strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@\stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * @return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * @return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return \strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } +} diff --git a/vendor/workerman/workerman/Events/Ev.php b/vendor/workerman/workerman/Events/Ev.php new file mode 100644 index 0000000..8e21bc3 --- /dev/null +++ b/vendor/workerman/workerman/Events/Ev.php @@ -0,0 +1,189 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use \EvWatcher; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param EvWatcher $event + */ + public function timerCallback(EvWatcher $event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + \Ev::stop(\Ev::BREAK_ALL); + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Event.php b/vendor/workerman/workerman/Events/Event.php new file mode 100644 index 0000000..9e25521 --- /dev/null +++ b/vendor/workerman/workerman/Events/Event.php @@ -0,0 +1,215 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (\class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (\class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param int|null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + $this->_eventBase->exit(); + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/EventInterface.php b/vendor/workerman/workerman/Events/EventInterface.php new file mode 100644 index 0000000..e6f59c6 --- /dev/null +++ b/vendor/workerman/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/vendor/workerman/workerman/Events/Libevent.php b/vendor/workerman/workerman/Events/Libevent.php new file mode 100644 index 0000000..5f61e9c --- /dev/null +++ b/vendor/workerman/workerman/Events/Libevent.php @@ -0,0 +1,225 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = \event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = \EV_SIGNAL | \EV_PERSIST; + $this->_eventSignal[$fd_key] = \event_new(); + if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!\event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = \event_new(); + $timer_id = (int)$event; + if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!\event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST; + + $event = \event_new(); + + if (!\event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!\event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!\event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + \event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + \event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + \event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + \event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + \call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + \event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + \event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + \event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} + diff --git a/vendor/workerman/workerman/Events/React/Base.php b/vendor/workerman/workerman/Events/React/Base.php new file mode 100644 index 0000000..bce4f73 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/Base.php @@ -0,0 +1,264 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; +use React\EventLoop\LoopInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param int $fd + * @param int $flag + * @param callable $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, array $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + \call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtEventLoop.php b/vendor/workerman/workerman/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..3dab25b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php new file mode 100644 index 0000000..eb02b35 --- /dev/null +++ b/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/React/StreamSelectLoop.php b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..7f5f94b --- /dev/null +++ b/vendor/workerman/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/vendor/workerman/workerman/Events/Select.php b/vendor/workerman/workerman/Events/Select.php new file mode 100644 index 0000000..ebc263e --- /dev/null +++ b/vendor/workerman/workerman/Events/Select.php @@ -0,0 +1,357 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Throwable; +use Workerman\Worker; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? \count($this->_readFds) : \count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (\DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + \pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = \microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - \microtime(true)) * 1000000; + $select_timeout = $select_timeout <= 0 ? 1 : $select_timeout; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = (int) $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + \call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(\DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + \pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + $tasks_to_insert = []; + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = (int) (($next_run_time - $time_now) * 1000000); + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $tasks_to_insert[] = [$timer_id, -$next_run_time]; + } + try { + \call_user_func_array($task_data[0], $task_data[1]); + } catch (Throwable $e) { + Worker::stopAll(250, $e); + } + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } else { + break; + } + } + foreach ($tasks_to_insert as $item) { + $this->_scheduler->insert($item[0], $item[1]); + } + if (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $next_run_time = -$scheduler_data['priority']; + $time_now = \microtime(true); + $this->_selectTimeout = \max((int) (($next_run_time - $time_now) * 1000000), 0); + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + while (1) { + if(\DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + \pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + $ret = false; + + if ($read || $write || $except) { + // Waiting read/write/signal/timeout events. + try { + $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); + } catch (\Exception $e) {} catch (\Error $e) {} + + } else { + $this->_selectTimeout >= 1 && usleep($this->_selectTimeout); + } + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + \call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Events/Swoole.php b/vendor/workerman/workerman/Events/Swoole.php new file mode 100644 index 0000000..fcd7472 --- /dev/null +++ b/vendor/workerman/workerman/Events/Swoole.php @@ -0,0 +1,230 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; +use Swoole\Event; +use Swoole\Timer; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $mapId = 0; + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 500; + + protected $_hasSignal = false; + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $res = \pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + \pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER === $flag ? 'tick' : 'after'; + if ($this->mapId > \PHP_INT_MAX) { + $this->mapId = 0; + } + $mapId = $this->mapId++; + $t = (int)($fd * 1000); + if ($t < 1) { + $t = 1; + } + $timer_id = Timer::$method($t, + function ($timer_id = null) use ($func, $args, $mapId) { + try { + \call_user_func_array($func, (array)$args); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (\array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag === self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (! isset($this->_fd[$fd_key])) { + if ($flag === self::EV_READ) { + $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag === self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) !== SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) !== SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return \pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! \array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag === self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + Event::exit(); + posix_kill(posix_getpid(), SIGINT); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return \count($this->_timer); + } +} diff --git a/vendor/workerman/workerman/Events/Uv.php b/vendor/workerman/workerman/Events/Uv.php new file mode 100644 index 0000000..49f0ddd --- /dev/null +++ b/vendor/workerman/workerman/Events/Uv.php @@ -0,0 +1,260 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libuv eventloop + */ +class Uv implements EventInterface +{ + /** + * Event Loop. + * @var object + */ + protected $_eventLoop = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * @brief Constructor + * + * @param object $loop + * + * @return void + */ + public function __construct(\UVLoop $loop = null) + { + if(!extension_loaded('uv')) + { + throw new \Exception(__CLASS__ . ' requires the UV extension, but detected it has NOT been installed yet.'); + } + + if(empty($loop) || !$loop instanceof \UVLoop) + { + $this->_eventLoop = \uv_default_loop(); + return; + } + + $this->_eventLoop = $loop; + } + + /** + * @brief Add a timer + * + * @param resource $fd + * @param int $flag + * @param callback $func + * @param mixed $args + * + * @return mixed + */ + public function add($fd, $flag, $func, $args = null) + { + switch ($flag) + { + case self::EV_SIGNAL: + $signalCallback = function($watcher, $socket)use($func, $fd){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $signalWatcher = \uv_signal_init(); + \uv_signal_start($signalWatcher, $signalCallback, $fd); + $this->_eventSignal[$fd] = $signalWatcher; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag === self::EV_TIMER_ONCE ? 0 : (int)($fd * 1000); + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $timerWatcher = \uv_timer_init(); + \uv_timer_start($timerWatcher, ($flag === self::EV_TIMER_ONCE ? (int)($fd * 1000) :1), $repeat, function($watcher)use($param){ + call_user_func_array([$this, 'timerCallback'], [$param]); + }); + $this->_eventTimer[self::$_timerId] = $timerWatcher; + return self::$_timerId++; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + $ioCallback = function($watcher, $status, $events, $fd)use($func){ + try { + \call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + }; + $ioWatcher = \uv_poll_init($this->_eventLoop, $fd); + $real_flag = $flag === self::EV_READ ? \Uv::READABLE : \Uv::WRITABLE; + \uv_poll_start($ioWatcher, $real_flag, $ioCallback); + $this->_allEvents[$fd_key][$flag] = $ioWatcher; + return true; + default: + break; + } + } + + /** + * @brief Remove a timer + * + * @param resource $fd + * @param int $flag + * + * @return boolean + */ + public function del($fd, $flag) + { + switch ($flag) + { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $watcher = $this->_allEvents[$fd_key][$flag]; + \uv_is_active($watcher) && \uv_poll_stop($watcher); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $watcher = $this->_eventSignal[$fd_key]; + \uv_is_active($watcher) && \uv_signal_stop($watcher); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $watcher = $this->_eventTimer[$fd]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$fd]); + } + break; + } + + return true; + } + + /** + * @brief Timer callback + * + * @param array $input + * + * @return void + */ + public function timerCallback($input) + { + if(!is_array($input)) return; + + $timer_id = $input[4]; + + if ($input[2] === self::EV_TIMER_ONCE) + { + $watcher = $this->_eventTimer[$timer_id]; + \uv_is_active($watcher) && \uv_timer_stop($watcher); + unset($this->_eventTimer[$timer_id]); + } + + try { + \call_user_func_array($input[0], $input[1]); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + } + + /** + * @brief Remove all timers + * + * @return void + */ + public function clearAllTimer() + { + if(!is_array($this->_eventTimer)) return; + + foreach($this->_eventTimer as $watcher) + { + \uv_is_active($watcher) && \uv_timer_stop($watcher); + } + + $this->_eventTimer = array(); + } + + /** + * @brief Start loop + * + * @return void + */ + public function loop() + { + \Uv_run(); + } + + /** + * @brief Destroy loop + * + * @return void + */ + public function destroy() + { + !empty($this->_eventLoop) && \uv_loop_delete($this->_eventLoop); + $this->_allEvents = []; + } + + /** + * @brief Get timer count + * + * @return integer + */ + public function getTimerCount() + { + return \count($this->_eventTimer); + } +} diff --git a/vendor/workerman/workerman/Lib/Constants.php b/vendor/workerman/workerman/Lib/Constants.php new file mode 100644 index 0000000..f5e2424 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Constants.php @@ -0,0 +1,44 @@ + + * @copyright walkor + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * + * @link http://www.workerman.net/ + */ + +// Pcre.jit is not stable, temporarily disabled. +ini_set('pcre.jit', 0); + +// For onError callback. +const WORKERMAN_CONNECT_FAIL = 1; +// For onError callback. +const WORKERMAN_SEND_FAIL = 2; + +// Define OS Type +const OS_TYPE_LINUX = 'linux'; +const OS_TYPE_WINDOWS = 'windows'; + +// Compatible with php7 +if (!class_exists('Error')) { + class Error extends Exception + { + } +} + +if (!interface_exists('SessionHandlerInterface')) { + interface SessionHandlerInterface { + public function close(); + public function destroy($session_id); + public function gc($maxlifetime); + public function open($save_path ,$session_name); + public function read($session_id); + public function write($session_id , $session_data); + } +} diff --git a/vendor/workerman/workerman/Lib/Timer.php b/vendor/workerman/workerman/Lib/Timer.php new file mode 100644 index 0000000..b110051 --- /dev/null +++ b/vendor/workerman/workerman/Lib/Timer.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +/** + * Do not use Workerman\Lib\Timer. + * Please use Workerman\Timer. + * This class is only used for compatibility with workerman 3.* + * @package Workerman\Lib + */ +class Timer extends \Workerman\Timer {} \ No newline at end of file diff --git a/vendor/workerman/workerman/MIT-LICENSE.txt b/vendor/workerman/workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/vendor/workerman/workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/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. diff --git a/vendor/workerman/workerman/Protocols/Frame.php b/vendor/workerman/workerman/Protocols/Frame.php new file mode 100644 index 0000000..26b04de --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (\strlen($buffer) < 4) { + return 0; + } + $unpack_data = \unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return \substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + \strlen($buffer); + return \pack('N', $total_length) . $buffer; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http.php b/vendor/workerman/workerman/Protocols/Http.php new file mode 100644 index 0000000..9e5d928 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http.php @@ -0,0 +1,323 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Protocols\Http\Response; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Websocket; +use Workerman\Worker; + +/** + * Class Http. + * @package Workerman\Protocols + */ +class Http +{ + /** + * Request class name. + * + * @var string + */ + protected static $_requestClass = 'Workerman\Protocols\Http\Request'; + + /** + * Upload tmp dir. + * + * @var string + */ + protected static $_uploadTmpDir = ''; + + /** + * Open cache. + * + * @var bool. + */ + protected static $_enableCache = true; + + /** + * Get or set session name. + * + * @param string|null $name + * @return string + */ + public static function sessionName($name = null) + { + if ($name !== null && $name !== '') { + Session::$name = (string)$name; + } + return Session::$name; + } + + /** + * Get or set the request class name. + * + * @param string|null $class_name + * @return string + */ + public static function requestClass($class_name = null) + { + if ($class_name) { + static::$_requestClass = $class_name; + } + return static::$_requestClass; + } + + /** + * Enable or disable Cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + static $input = []; + if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) { + return $input[$recv_buffer]; + } + $crlf_pos = \strpos($recv_buffer, "\r\n\r\n"); + if (false === $crlf_pos) { + // Judge whether the package length exceeds the limit. + if (\strlen($recv_buffer) >= 16384) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + return 0; + } + + $length = $crlf_pos + 4; + $method = \strstr($recv_buffer, ' ', true); + + if (!\in_array($method, ['GET', 'POST', 'OPTIONS', 'HEAD', 'DELETE', 'PUT', 'PATCH'])) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + + $header = \substr($recv_buffer, 0, $crlf_pos); + if ($pos = \strpos($header, "\r\nContent-Length: ")) { + $length = $length + (int)\substr($header, $pos + 18, 10); + $has_content_length = true; + } else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) { + $length = $length + $match[1]; + $has_content_length = true; + } else { + $has_content_length = false; + if (false !== stripos($header, "\r\nTransfer-Encoding:")) { + $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + if ($has_content_length) { + if ($length > $connection->maxPackageSize) { + $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true); + return 0; + } + } + + if (!isset($recv_buffer[512])) { + $input[$recv_buffer] = $length; + if (\count($input) > 512) { + unset($input[key($input)]); + } + } + + return $length; + } + + /** + * Http decode. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return \Workerman\Protocols\Http\Request + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + static $requests = array(); + $cacheable = static::$_enableCache && !isset($recv_buffer[512]); + if (true === $cacheable && isset($requests[$recv_buffer])) { + $request = $requests[$recv_buffer]; + $request->connection = $connection; + $connection->__request = $request; + $request->properties = array(); + return $request; + } + $request = new static::$_requestClass($recv_buffer); + $request->connection = $connection; + $connection->__request = $request; + if (true === $cacheable) { + $requests[$recv_buffer] = $request; + if (\count($requests) > 512) { + unset($requests[key($requests)]); + } + } + return $request; + } + + /** + * Http encode. + * + * @param string|Response $response + * @param TcpConnection $connection + * @return string + */ + public static function encode($response, TcpConnection $connection) + { + if (isset($connection->__request)) { + $connection->__request->session = null; + $connection->__request->connection = null; + $connection->__request = null; + } + if (!\is_object($response)) { + $ext_header = ''; + if (isset($connection->__header)) { + foreach ($connection->__header as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $ext_header = "$name: $item\r\n"; + } + } else { + $ext_header = "$name: $value\r\n"; + } + } + unset($connection->__header); + } + $body_len = \strlen((string)$response); + return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response"; + } + + if (isset($connection->__header)) { + $response->withHeaders($connection->__header); + unset($connection->__header); + } + + if (isset($response->file)) { + $file = $response->file['file']; + $offset = $response->file['offset']; + $length = $response->file['length']; + clearstatcache(); + $file_size = (int)\filesize($file); + $body_len = $length > 0 ? $length : $file_size - $offset; + $response->withHeaders(array( + 'Content-Length' => $body_len, + 'Accept-Ranges' => 'bytes', + )); + if ($offset || $length) { + $offset_end = $offset + $body_len - 1; + $response->header('Content-Range', "bytes $offset-$offset_end/$file_size"); + } + if ($body_len < 2 * 1024 * 1024) { + $connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true); + return ''; + } + $handler = \fopen($file, 'r'); + if (false === $handler) { + $connection->close(new Response(403, null, '403 Forbidden')); + return ''; + } + $connection->send((string)$response, true); + static::sendStream($connection, $handler, $offset, $length); + return ''; + } + + return (string)$response; + } + + /** + * Send remainder of a stream to client. + * + * @param TcpConnection $connection + * @param resource $handler + * @param int $offset + * @param int $length + */ + protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0) + { + $connection->bufferFull = false; + if ($offset !== 0) { + \fseek($handler, $offset); + } + $offset_end = $offset + $length; + // Read file content from disk piece by piece and send to client. + $do_write = function () use ($connection, $handler, $length, $offset_end) { + // Send buffer not full. + while ($connection->bufferFull === false) { + // Read from disk. + $size = 1024 * 1024; + if ($length !== 0) { + $tell = \ftell($handler); + $remain_size = $offset_end - $tell; + if ($remain_size <= 0) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $size = $remain_size > $size ? $size : $remain_size; + } + + $buffer = \fread($handler, $size); + // Read eof. + if ($buffer === '' || $buffer === false) { + fclose($handler); + $connection->onBufferDrain = null; + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function ($connection) { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function ($connection) use ($do_write) { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } + + /** + * Set or get uploadTmpDir. + * + * @return bool|string + */ + public static function uploadTmpDir($dir = null) + { + if (null !== $dir) { + static::$_uploadTmpDir = $dir; + } + if (static::$_uploadTmpDir === '') { + if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) { + static::$_uploadTmpDir = $upload_tmp_dir; + } else if ($upload_tmp_dir = \sys_get_temp_dir()) { + static::$_uploadTmpDir = $upload_tmp_dir; + } + } + return static::$_uploadTmpDir; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Chunk.php b/vendor/workerman/workerman/Protocols/Http/Chunk.php new file mode 100644 index 0000000..ab06a9c --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Chunk.php @@ -0,0 +1,48 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + + +/** + * Class Chunk + * @package Workerman\Protocols\Http + */ +class Chunk +{ + /** + * Chunk buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Chunk constructor. + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * __toString + * + * @return string + */ + public function __toString() + { + return \dechex(\strlen($this->_buffer))."\r\n$this->_buffer\r\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Request.php b/vendor/workerman/workerman/Protocols/Http/Request.php new file mode 100644 index 0000000..d544ac0 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Request.php @@ -0,0 +1,694 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Session; +use Workerman\Protocols\Http; +use Workerman\Worker; + +/** + * Class Request + * @package Workerman\Protocols\Http + */ +class Request +{ + /** + * Connection. + * + * @var TcpConnection + */ + public $connection = null; + + /** + * Session instance. + * + * @var Session + */ + public $session = null; + + /** + * Properties. + * + * @var array + */ + public $properties = array(); + + /** + * @var int + */ + public static $maxFileUploads = 1024; + + /** + * Http buffer. + * + * @var string + */ + protected $_buffer = null; + + /** + * Request data. + * + * @var array + */ + protected $_data = null; + + /** + * Enable cache. + * + * @var bool + */ + protected static $_enableCache = true; + + /** + * Is safe. + * + * @var bool + */ + protected $_isSafe = true; + + + /** + * Request constructor. + * + * @param string $buffer + */ + public function __construct($buffer) + { + $this->_buffer = $buffer; + } + + /** + * $_GET. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name = null, $default = null) + { + if (!isset($this->_data['get'])) { + $this->parseGet(); + } + if (null === $name) { + return $this->_data['get']; + } + return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default; + } + + /** + * $_POST. + * + * @param string|null $name + * @param mixed|null $default + * @return mixed|null + */ + public function post($name = null, $default = null) + { + if (!isset($this->_data['post'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['post']; + } + return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default; + } + + /** + * Get header item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function header($name = null, $default = null) + { + if (!isset($this->_data['headers'])) { + $this->parseHeaders(); + } + if (null === $name) { + return $this->_data['headers']; + } + $name = \strtolower($name); + return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default; + } + + /** + * Get cookie item by name. + * + * @param string|null $name + * @param mixed|null $default + * @return array|string|null + */ + public function cookie($name = null, $default = null) + { + if (!isset($this->_data['cookie'])) { + $this->_data['cookie'] = array(); + \parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']); + } + if ($name === null) { + return $this->_data['cookie']; + } + return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default; + } + + /** + * Get upload files. + * + * @param string|null $name + * @return array|null + */ + public function file($name = null) + { + if (!isset($this->_data['files'])) { + $this->parsePost(); + } + if (null === $name) { + return $this->_data['files']; + } + return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null; + } + + /** + * Get method. + * + * @return string + */ + public function method() + { + if (!isset($this->_data['method'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['method']; + } + + /** + * Get http protocol version. + * + * @return string + */ + public function protocolVersion() + { + if (!isset($this->_data['protocolVersion'])) { + $this->parseProtocolVersion(); + } + return $this->_data['protocolVersion']; + } + + /** + * Get host. + * + * @param bool $without_port + * @return string + */ + public function host($without_port = false) + { + $host = $this->header('host'); + if ($host && $without_port) { + return preg_replace('/:\d{1,5}$/', '', $host); + } + return $host; + } + + /** + * Get uri. + * + * @return mixed + */ + public function uri() + { + if (!isset($this->_data['uri'])) { + $this->parseHeadFirstLine(); + } + return $this->_data['uri']; + } + + /** + * Get path. + * + * @return mixed + */ + public function path() + { + if (!isset($this->_data['path'])) { + $this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH); + } + return $this->_data['path']; + } + + /** + * Get query string. + * + * @return mixed + */ + public function queryString() + { + if (!isset($this->_data['query_string'])) { + $this->_data['query_string'] = (string)\parse_url($this->uri(), PHP_URL_QUERY); + } + return $this->_data['query_string']; + } + + /** + * Get session. + * + * @return bool|\Workerman\Protocols\Http\Session + */ + public function session() + { + if ($this->session === null) { + $session_id = $this->sessionId(); + if ($session_id === false) { + return false; + } + $this->session = new Session($session_id); + } + return $this->session; + } + + /** + * Get/Set session id. + * + * @param $session_id + * @return string + */ + public function sessionId($session_id = null) + { + if ($session_id) { + unset($this->sid); + } + if (!isset($this->sid)) { + $session_name = Session::$name; + $sid = $session_id ? '' : $this->cookie($session_name); + if ($sid === '' || $sid === null) { + if ($this->connection === null) { + Worker::safeEcho('Request->session() fail, header already send'); + return false; + } + $sid = $session_id ? $session_id : static::createSessionId(); + $cookie_params = Session::getCookieParams(); + $this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid + . (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain']) + . (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime']) + . (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path']) + . (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite']) + . (!$cookie_params['secure'] ? '' : '; Secure') + . (!$cookie_params['httponly'] ? '' : '; HttpOnly')); + } + $this->sid = $sid; + } + return $this->sid; + } + + /** + * Get http raw head. + * + * @return string + */ + public function rawHead() + { + if (!isset($this->_data['head'])) { + $this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true); + } + return $this->_data['head']; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() + { + return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4); + } + + /** + * Get raw buffer. + * + * @return string + */ + public function rawBuffer() + { + return $this->_buffer; + } + + /** + * Enable or disable cache. + * + * @param mixed $value + */ + public static function enableCache($value) + { + static::$_enableCache = (bool)$value; + } + + /** + * Parse first line of http header buffer. + * + * @return void + */ + protected function parseHeadFirstLine() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $tmp = \explode(' ', $first_line, 3); + $this->_data['method'] = $tmp[0]; + $this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/'; + } + + /** + * Parse protocol version. + * + * @return void + */ + protected function parseProtocolVersion() + { + $first_line = \strstr($this->_buffer, "\r\n", true); + $protoco_version = substr(\strstr($first_line, 'HTTP/'), 5); + $this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0'; + } + + /** + * Parse headers. + * + * @return void + */ + protected function parseHeaders() + { + static $cache = []; + $this->_data['headers'] = array(); + $raw_head = $this->rawHead(); + $end_line_position = \strpos($raw_head, "\r\n"); + if ($end_line_position === false) { + return; + } + $head_buffer = \substr($raw_head, $end_line_position + 2); + $cacheable = static::$_enableCache && !isset($head_buffer[2048]); + if ($cacheable && isset($cache[$head_buffer])) { + $this->_data['headers'] = $cache[$head_buffer]; + return; + } + $head_data = \explode("\r\n", $head_buffer); + foreach ($head_data as $content) { + if (false !== \strpos($content, ':')) { + list($key, $value) = \explode(':', $content, 2); + $key = \strtolower($key); + $value = \ltrim($value); + } else { + $key = \strtolower($content); + $value = ''; + } + if (isset($this->_data['headers'][$key])) { + $this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value"; + } else { + $this->_data['headers'][$key] = $value; + } + } + if ($cacheable) { + $cache[$head_buffer] = $this->_data['headers']; + if (\count($cache) > 128) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse head. + * + * @return void + */ + protected function parseGet() + { + static $cache = []; + $query_string = $this->queryString(); + $this->_data['get'] = array(); + if ($query_string === '') { + return; + } + $cacheable = static::$_enableCache && !isset($query_string[1024]); + if ($cacheable && isset($cache[$query_string])) { + $this->_data['get'] = $cache[$query_string]; + return; + } + \parse_str($query_string, $this->_data['get']); + if ($cacheable) { + $cache[$query_string] = $this->_data['get']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse post. + * + * @return void + */ + protected function parsePost() + { + static $cache = []; + $this->_data['post'] = $this->_data['files'] = array(); + $content_type = $this->header('content-type', ''); + if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) { + $http_post_boundary = '--' . $match[1]; + $this->parseUploadFiles($http_post_boundary); + return; + } + $body_buffer = $this->rawBody(); + if ($body_buffer === '') { + return; + } + $cacheable = static::$_enableCache && !isset($body_buffer[1024]); + if ($cacheable && isset($cache[$body_buffer])) { + $this->_data['post'] = $cache[$body_buffer]; + return; + } + if (\preg_match('/\bjson\b/i', $content_type)) { + $this->_data['post'] = (array) json_decode($body_buffer, true); + } else { + \parse_str($body_buffer, $this->_data['post']); + } + if ($cacheable) { + $cache[$body_buffer] = $this->_data['post']; + if (\count($cache) > 256) { + unset($cache[key($cache)]); + } + } + } + + /** + * Parse upload files. + * + * @param string $http_post_boundary + * @return void + */ + protected function parseUploadFiles($http_post_boundary) + { + $http_post_boundary = \trim($http_post_boundary, '"'); + $buffer = $this->_buffer; + $post_encode_string = ''; + $files_encode_string = ''; + $files = []; + $boday_position = strpos($buffer, "\r\n\r\n") + 4; + $offset = $boday_position + strlen($http_post_boundary) + 2; + $max_count = static::$maxFileUploads; + while ($max_count-- > 0 && $offset) { + $offset = $this->parseUploadFile($http_post_boundary, $offset, $post_encode_string, $files_encode_string, $files); + } + if ($post_encode_string) { + parse_str($post_encode_string, $this->_data['post']); + } + + if ($files_encode_string) { + parse_str($files_encode_string, $this->_data['files']); + \array_walk_recursive($this->_data['files'], function (&$value) use ($files) { + $value = $files[$value]; + }); + } + } + + /** + * @param $boundary + * @param $section_start_offset + * @return int + */ + protected function parseUploadFile($boundary, $section_start_offset, &$post_encode_string, &$files_encode_str, &$files) + { + $file = []; + $boundary = "\r\n$boundary"; + if (\strlen($this->_buffer) < $section_start_offset) { + return 0; + } + $section_end_offset = \strpos($this->_buffer, $boundary, $section_start_offset); + if (!$section_end_offset) { + return 0; + } + $content_lines_end_offset = \strpos($this->_buffer, "\r\n\r\n", $section_start_offset); + if (!$content_lines_end_offset || $content_lines_end_offset + 4 > $section_end_offset) { + return 0; + } + $content_lines_str = \substr($this->_buffer, $section_start_offset, $content_lines_end_offset - $section_start_offset); + $content_lines = \explode("\r\n", trim($content_lines_str . "\r\n")); + $boundary_value = \substr($this->_buffer, $content_lines_end_offset + 4, $section_end_offset - $content_lines_end_offset - 4); + $upload_key = false; + foreach ($content_lines as $content_line) { + if (!\strpos($content_line, ': ')) { + return 0; + } + list($key, $value) = \explode(': ', $content_line); + switch (strtolower($key)) { + case "content-disposition": + // Is file data. + if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) { + $error = 0; + $tmp_file = ''; + $file_name = $match[2]; + $size = \strlen($boundary_value); + $tmp_upload_dir = HTTP::uploadTmpDir(); + if (!$tmp_upload_dir) { + $error = UPLOAD_ERR_NO_TMP_DIR; + } else if ($boundary_value === '' && $file_name === '') { + $error = UPLOAD_ERR_NO_FILE; + } else { + $tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.'); + if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) { + $error = UPLOAD_ERR_CANT_WRITE; + } + } + $upload_key = $match[1]; + // Parse upload files. + $file = [ + 'name' => $file_name, + 'tmp_name' => $tmp_file, + 'size' => $size, + 'error' => $error, + 'type' => '', + ]; + break; + } // Is post field. + else { + // Parse $_POST. + if (\preg_match('/name="(.*?)"$/', $value, $match)) { + $k = $match[1]; + $post_encode_string .= \urlencode($k) . "=" . \urlencode($boundary_value) . '&'; + } + return $section_end_offset + \strlen($boundary) + 2; + } + break; + case "content-type": + $file['type'] = \trim($value); + break; + } + } + if ($upload_key === false) { + return 0; + } + $files_encode_str .= \urlencode($upload_key) . '=' . \count($files) . '&'; + $files[] = $file; + + return $section_end_offset + \strlen($boundary) + 2; + } + + /** + * Create session id. + * + * @return string + */ + protected static function createSessionId() + { + return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8)); + } + + /** + * Setter. + * + * @param string $name + * @param mixed $value + * @return void + */ + public function __set($name, $value) + { + $this->properties[$name] = $value; + } + + /** + * Getter. + * + * @param string $name + * @return mixed|null + */ + public function __get($name) + { + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Isset. + * + * @param string $name + * @return bool + */ + public function __isset($name) + { + return isset($this->properties[$name]); + } + + /** + * Unset. + * + * @param string $name + * @return void + */ + public function __unset($name) + { + unset($this->properties[$name]); + } + + /** + * __toString. + */ + public function __toString() + { + return $this->_buffer; + } + + /** + * __wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_isSafe = false; + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + if (isset($this->_data['files']) && $this->_isSafe) { + \clearstatcache(); + \array_walk_recursive($this->_data['files'], function($value, $key){ + if ($key === 'tmp_name') { + if (\is_file($value)) { + \unlink($value); + } + } + }); + } + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Response.php b/vendor/workerman/workerman/Protocols/Http/Response.php new file mode 100644 index 0000000..e423727 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Response.php @@ -0,0 +1,458 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class Response + * @package Workerman\Protocols\Http + */ +class Response +{ + /** + * Header data. + * + * @var array + */ + protected $_header = null; + + /** + * Http status. + * + * @var int + */ + protected $_status = null; + + /** + * Http reason. + * + * @var string + */ + protected $_reason = null; + + /** + * Http version. + * + * @var string + */ + protected $_version = '1.1'; + + /** + * Http body. + * + * @var string + */ + protected $_body = null; + + /** + * Send file info + * + * @var array + */ + public $file = null; + + /** + * Mine type map. + * @var array + */ + protected static $_mimeTypeMap = null; + + /** + * Phrases. + * + * @var array + */ + protected static $_phrases = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 511 => 'Network Authentication Required', + ); + + /** + * Init. + * + * @return void + */ + public static function init() { + static::initMimeTypeMap(); + } + + /** + * Response constructor. + * + * @param int $status + * @param array $headers + * @param string $body + */ + public function __construct( + $status = 200, + $headers = array(), + $body = '' + ) { + $this->_status = $status; + $this->_header = $headers; + $this->_body = (string)$body; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return $this + */ + public function header($name, $value) { + $this->_header[$name] = $value; + return $this; + } + + /** + * Set header. + * + * @param string $name + * @param string $value + * @return Response + */ + public function withHeader($name, $value) { + return $this->header($name, $value); + } + + /** + * Set headers. + * + * @param array $headers + * @return $this + */ + public function withHeaders($headers) { + $this->_header = \array_merge_recursive($this->_header, $headers); + return $this; + } + + /** + * Remove header. + * + * @param string $name + * @return $this + */ + public function withoutHeader($name) { + unset($this->_header[$name]); + return $this; + } + + /** + * Get header. + * + * @param string $name + * @return null|array|string + */ + public function getHeader($name) { + if (!isset($this->_header[$name])) { + return null; + } + return $this->_header[$name]; + } + + /** + * Get headers. + * + * @return array + */ + public function getHeaders() { + return $this->_header; + } + + /** + * Set status. + * + * @param int $code + * @param string|null $reason_phrase + * @return $this + */ + public function withStatus($code, $reason_phrase = null) { + $this->_status = $code; + $this->_reason = $reason_phrase; + return $this; + } + + /** + * Get status code. + * + * @return int + */ + public function getStatusCode() { + return $this->_status; + } + + /** + * Get reason phrase. + * + * @return string + */ + public function getReasonPhrase() { + return $this->_reason; + } + + /** + * Set protocol version. + * + * @param int $version + * @return $this + */ + public function withProtocolVersion($version) { + $this->_version = $version; + return $this; + } + + /** + * Set http body. + * + * @param string $body + * @return $this + */ + public function withBody($body) { + $this->_body = $body; + return $this; + } + + /** + * Get http raw body. + * + * @return string + */ + public function rawBody() { + return $this->_body; + } + + /** + * Send file. + * + * @param string $file + * @param int $offset + * @param int $length + * @return $this + */ + public function withFile($file, $offset = 0, $length = 0) { + if (!\is_file($file)) { + return $this->withStatus(404)->withBody('

404 Not Found

'); + } + $this->file = array('file' => $file, 'offset' => $offset, 'length' => $length); + return $this; + } + + /** + * Set cookie. + * + * @param $name + * @param string $value + * @param int $max_age + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $http_only + * @param string $same_site + * @return $this + */ + public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = '') + { + $this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . ($max_age === null ? '' : '; Max-Age=' . $max_age) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$http_only ? '' : '; HttpOnly') + . (empty($same_site) ? '' : '; SameSite=' . $same_site); + return $this; + } + + /** + * Create header for file. + * + * @param array $file_info + * @return string + */ + protected function createHeadForFile($file_info) + { + $file = $file_info['file']; + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + $file_info = \pathinfo($file); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $base_name = isset($file_info['basename']) ? $file_info['basename'] : 'unknown'; + if (!isset($headers['Content-Type'])) { + if (isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Type: " . self::$_mimeTypeMap[$extension] . "\r\n"; + } else { + $head .= "Content-Type: application/octet-stream\r\n"; + } + } + + if (!isset($headers['Content-Disposition']) && !isset(self::$_mimeTypeMap[$extension])) { + $head .= "Content-Disposition: attachment; filename=\"$base_name\"\r\n"; + } + + if (!isset($headers['Last-Modified'])) { + if ($mtime = \filemtime($file)) { + $head .= 'Last-Modified: '. \gmdate('D, d M Y H:i:s', $mtime) . ' GMT' . "\r\n"; + } + } + + return "{$head}\r\n"; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + if (isset($this->file)) { + return $this->createHeadForFile($this->file); + } + + $reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status]; + $body_len = \strlen($this->_body); + if (empty($this->_header)) { + return "HTTP/{$this->_version} {$this->_status} $reason\r\nServer: workerman\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\nConnection: keep-alive\r\n\r\n{$this->_body}"; + } + + $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n"; + $headers = $this->_header; + if (!isset($headers['Server'])) { + $head .= "Server: workerman\r\n"; + } + foreach ($headers as $name => $value) { + if (\is_array($value)) { + foreach ($value as $item) { + $head .= "$name: $item\r\n"; + } + continue; + } + $head .= "$name: $value\r\n"; + } + + if (!isset($headers['Connection'])) { + $head .= "Connection: keep-alive\r\n"; + } + + if (!isset($headers['Content-Type'])) { + $head .= "Content-Type: text/html;charset=utf-8\r\n"; + } else if ($headers['Content-Type'] === 'text/event-stream') { + return $head . $this->_body; + } + + if (!isset($headers['Transfer-Encoding'])) { + $head .= "Content-Length: $body_len\r\n\r\n"; + } else { + return $body_len ? "$head\r\n" . dechex($body_len) . "\r\n{$this->_body}\r\n" : "$head\r\n"; + } + + // The whole http package + return $head . $this->_body; + } + + /** + * Init mime map. + * + * @return void + */ + public static function initMimeTypeMap() + { + $mime_file = __DIR__ . '/mime.types'; + $items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + foreach ($items as $content) { + if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $extension_var = $match[2]; + $extension_array = \explode(' ', \substr($extension_var, 0, -1)); + foreach ($extension_array as $file_extension) { + static::$_mimeTypeMap[$file_extension] = $mime_type; + } + } + } + } +} +Response::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php new file mode 100644 index 0000000..a6e9e0d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php @@ -0,0 +1,64 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http; + +/** + * Class ServerSentEvents + * @package Workerman\Protocols\Http + */ +class ServerSentEvents +{ + /** + * Data. + * @var array + */ + protected $_data = null; + + /** + * ServerSentEvents constructor. + * $data for example ['event'=>'ping', 'data' => 'some thing', 'id' => 1000, 'retry' => 5000] + * @param array $data + */ + public function __construct(array $data) + { + $this->_data = $data; + } + + /** + * __toString. + * + * @return string + */ + public function __toString() + { + $buffer = ''; + $data = $this->_data; + if (isset($data[''])) { + $buffer = ": {$data['']}\n"; + } + if (isset($data['event'])) { + $buffer .= "event: {$data['event']}\n"; + } + if (isset($data['id'])) { + $buffer .= "id: {$data['id']}\n"; + } + if (isset($data['retry'])) { + $buffer .= "retry: {$data['retry']}\n"; + } + if (isset($data['data'])) { + $buffer .= 'data: ' . str_replace("\n", "\ndata: ", $data['data']) . "\n"; + } + return $buffer . "\n"; + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session.php b/vendor/workerman/workerman/Protocols/Http/Session.php new file mode 100644 index 0000000..a0c2417 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session.php @@ -0,0 +1,461 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http; + +use Workerman\Protocols\Http\Session\SessionHandlerInterface; + +/** + * Class Session + * @package Workerman\Protocols\Http + */ +class Session +{ + /** + * Session andler class which implements SessionHandlerInterface. + * + * @var string + */ + protected static $_handlerClass = 'Workerman\Protocols\Http\Session\FileSessionHandler'; + + /** + * Parameters of __constructor for session handler class. + * + * @var null + */ + protected static $_handlerConfig = null; + + /** + * Session name. + * + * @var string + */ + public static $name = 'PHPSID'; + + /** + * Auto update timestamp. + * + * @var bool + */ + public static $autoUpdateTimestamp = false; + + /** + * Session lifetime. + * + * @var int + */ + public static $lifetime = 1440; + + /** + * Cookie lifetime. + * + * @var int + */ + public static $cookieLifetime = 1440; + + /** + * Session cookie path. + * + * @var string + */ + public static $cookiePath = '/'; + + /** + * Session cookie domain. + * + * @var string + */ + public static $domain = ''; + + /** + * HTTPS only cookies. + * + * @var bool + */ + public static $secure = false; + + /** + * HTTP access only. + * + * @var bool + */ + public static $httpOnly = true; + + /** + * Same-site cookies. + * + * @var string + */ + public static $sameSite = ''; + + /** + * Gc probability. + * + * @var int[] + */ + public static $gcProbability = [1, 1000]; + + /** + * Session handler instance. + * + * @var SessionHandlerInterface + */ + protected static $_handler = null; + + /** + * Session data. + * + * @var array + */ + protected $_data = []; + + /** + * Session changed and need to save. + * + * @var bool + */ + protected $_needSave = false; + + /** + * Session id. + * + * @var null + */ + protected $_sessionId = null; + + /** + * Is safe. + * + * @var bool + */ + protected $_isSafe = true; + + /** + * Session constructor. + * + * @param string $session_id + */ + public function __construct($session_id) + { + static::checkSessionId($session_id); + if (static::$_handler === null) { + static::initHandler(); + } + $this->_sessionId = $session_id; + if ($data = static::$_handler->read($session_id)) { + $this->_data = \unserialize($data); + } + } + + /** + * Get session id. + * + * @return string + */ + public function getId() + { + return $this->_sessionId; + } + + /** + * Get session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function get($name, $default = null) + { + return isset($this->_data[$name]) ? $this->_data[$name] : $default; + } + + /** + * Store data in the session. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->_data[$name] = $value; + $this->_needSave = true; + } + + /** + * Delete an item from the session. + * + * @param string $name + */ + public function delete($name) + { + unset($this->_data[$name]); + $this->_needSave = true; + } + + /** + * Retrieve and delete an item from the session. + * + * @param string $name + * @param mixed|null $default + * @return mixed|null + */ + public function pull($name, $default = null) + { + $value = $this->get($name, $default); + $this->delete($name); + return $value; + } + + /** + * Store data in the session. + * + * @param string|array $key + * @param mixed|null $value + */ + public function put($key, $value = null) + { + if (!\is_array($key)) { + $this->set($key, $value); + return; + } + + foreach ($key as $k => $v) { + $this->_data[$k] = $v; + } + $this->_needSave = true; + } + + /** + * Remove a piece of data from the session. + * + * @param string $name + */ + public function forget($name) + { + if (\is_scalar($name)) { + $this->delete($name); + return; + } + if (\is_array($name)) { + foreach ($name as $key) { + unset($this->_data[$key]); + } + } + $this->_needSave = true; + } + + /** + * Retrieve all the data in the session. + * + * @return array + */ + public function all() + { + return $this->_data; + } + + /** + * Remove all data from the session. + * + * @return void + */ + public function flush() + { + $this->_needSave = true; + $this->_data = []; + } + + /** + * Determining If An Item Exists In The Session. + * + * @param string $name + * @return bool + */ + public function has($name) + { + return isset($this->_data[$name]); + } + + /** + * To determine if an item is present in the session, even if its value is null. + * + * @param string $name + * @return bool + */ + public function exists($name) + { + return \array_key_exists($name, $this->_data); + } + + /** + * Save session to store. + * + * @return void + */ + public function save() + { + if ($this->_needSave) { + if (empty($this->_data)) { + static::$_handler->destroy($this->_sessionId); + } else { + static::$_handler->write($this->_sessionId, \serialize($this->_data)); + } + } elseif (static::$autoUpdateTimestamp) { + static::refresh(); + } + $this->_needSave = false; + } + + /** + * Refresh session expire time. + * + * @return bool + */ + public function refresh() + { + static::$_handler->updateTimestamp($this->getId()); + } + + /** + * Init. + * + * @return void + */ + public static function init() + { + if (($gc_probability = (int)\ini_get('session.gc_probability')) && ($gc_divisor = (int)\ini_get('session.gc_divisor'))) { + static::$gcProbability = [$gc_probability, $gc_divisor]; + } + + if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) { + self::$lifetime = (int)$gc_max_life_time; + } + + $session_cookie_params = \session_get_cookie_params(); + static::$cookieLifetime = $session_cookie_params['lifetime']; + static::$cookiePath = $session_cookie_params['path']; + static::$domain = $session_cookie_params['domain']; + static::$secure = $session_cookie_params['secure']; + static::$httpOnly = $session_cookie_params['httponly']; + } + + /** + * Set session handler class. + * + * @param mixed|null $class_name + * @param mixed|null $config + * @return string + */ + public static function handlerClass($class_name = null, $config = null) + { + if ($class_name) { + static::$_handlerClass = $class_name; + } + if ($config) { + static::$_handlerConfig = $config; + } + return static::$_handlerClass; + } + + /** + * Get cookie params. + * + * @return array + */ + public static function getCookieParams() + { + return [ + 'lifetime' => static::$cookieLifetime, + 'path' => static::$cookiePath, + 'domain' => static::$domain, + 'secure' => static::$secure, + 'httponly' => static::$httpOnly, + 'samesite' => static::$sameSite, + ]; + } + + /** + * Init handler. + * + * @return void + */ + protected static function initHandler() + { + if (static::$_handlerConfig === null) { + static::$_handler = new static::$_handlerClass(); + } else { + static::$_handler = new static::$_handlerClass(static::$_handlerConfig); + } + } + + /** + * GC sessions. + * + * @return void + */ + public function gc() + { + static::$_handler->gc(static::$lifetime); + } + + /** + * __wakeup. + * + * @return void + */ + public function __wakeup() + { + $this->_isSafe = false; + } + + /** + * __destruct. + * + * @return void + */ + public function __destruct() + { + if (!$this->_isSafe) { + return; + } + $this->save(); + if (\random_int(1, static::$gcProbability[1]) <= static::$gcProbability[0]) { + $this->gc(); + } + } + + /** + * Check session id. + * + * @param string $session_id + */ + protected static function checkSessionId($session_id) + { + if (!\preg_match('/^[a-zA-Z0-9"]+$/', $session_id)) { + throw new SessionException("session_id $session_id is invalid"); + } + } +} + +/** + * Class SessionException + * @package Workerman\Protocols\Http + */ +class SessionException extends \RuntimeException +{ + +} + +// Init session. +Session::init(); diff --git a/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php new file mode 100644 index 0000000..a7cefbd --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php @@ -0,0 +1,183 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +/** + * Class FileSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class FileSessionHandler implements SessionHandlerInterface +{ + /** + * Session save path. + * + * @var string + */ + protected static $_sessionSavePath = null; + + /** + * Session file prefix. + * + * @var string + */ + protected static $_sessionFilePrefix = 'session_'; + + /** + * Init. + */ + public static function init() { + $save_path = @\session_save_path(); + if (!$save_path || \strpos($save_path, 'tcp://') === 0) { + $save_path = \sys_get_temp_dir(); + } + static::sessionSavePath($save_path); + } + + /** + * FileSessionHandler constructor. + * @param array $config + */ + public function __construct($config = array()) { + if (isset($config['save_path'])) { + static::sessionSavePath($config['save_path']); + } + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + $session_file = static::sessionFile($session_id); + \clearstatcache(); + if (\is_file($session_file)) { + if (\time() - \filemtime($session_file) > Session::$lifetime) { + \unlink($session_file); + return ''; + } + $data = \file_get_contents($session_file); + return $data ? $data : ''; + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + $temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true); + if (!\file_put_contents($temp_file, $session_data)) { + return false; + } + return \rename($temp_file, static::sessionFile($session_id)); + } + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * @see https://www.php.net/manual/zh/function.touch.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = "") + { + $session_file = static::sessionFile($id); + if (!file_exists($session_file)) { + return false; + } + // set file modify time to current time + $set_modify_time = \touch($session_file); + // clear file stat cache + \clearstatcache(); + return $set_modify_time; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $session_file = static::sessionFile($session_id); + if (\is_file($session_file)) { + \unlink($session_file); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) { + $time_now = \time(); + foreach (\glob(static::$_sessionSavePath . static::$_sessionFilePrefix . '*') as $file) { + if(\is_file($file) && $time_now - \filemtime($file) > $maxlifetime) { + \unlink($file); + } + } + } + + /** + * Get session file path. + * + * @param string $session_id + * @return string + */ + protected static function sessionFile($session_id) { + return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id; + } + + /** + * Get or set session file path. + * + * @param string $path + * @return string + */ + public static function sessionSavePath($path) { + if ($path) { + if ($path[\strlen($path)-1] !== DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + static::$_sessionSavePath = $path; + if (!\is_dir($path)) { + \mkdir($path, 0777, true); + } + } + return $path; + } +} + +FileSessionHandler::init(); \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php new file mode 100644 index 0000000..281759a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php @@ -0,0 +1,46 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; + +class RedisClusterSessionHandler extends RedisSessionHandler +{ + public function __construct($config) + { + $timeout = isset($config['timeout']) ? $config['timeout'] : 2; + $read_timeout = isset($config['read_timeout']) ? $config['read_timeout'] : $timeout; + $persistent = isset($config['persistent']) ? $config['persistent'] : false; + $auth = isset($config['auth']) ? $config['auth'] : ''; + if ($auth) { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent, $auth); + } else { + $this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + return $this->_redis->get($session_id); + } + +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php new file mode 100644 index 0000000..e1b5bd5 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php @@ -0,0 +1,154 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +use Workerman\Protocols\Http\Session; +use Workerman\Timer; +use RedisException; + +/** + * Class RedisSessionHandler + * @package Workerman\Protocols\Http\Session + */ +class RedisSessionHandler implements SessionHandlerInterface +{ + + /** + * @var \Redis + */ + protected $_redis; + + /** + * @var array + */ + protected $_config; + + /** + * RedisSessionHandler constructor. + * @param array $config = [ + * 'host' => '127.0.0.1', + * 'port' => 6379, + * 'timeout' => 2, + * 'auth' => '******', + * 'database' => 2, + * 'prefix' => 'redis_session_', + * 'ping' => 55, + * ] + */ + public function __construct($config) + { + if (false === extension_loaded('redis')) { + throw new \RuntimeException('Please install redis extension.'); + } + + if (!isset($config['timeout'])) { + $config['timeout'] = 2; + } + + $this->_config = $config; + + $this->connect(); + + Timer::add(!empty($config['ping']) ? $config['ping'] : 55, function () { + $this->_redis->get('ping'); + }); + } + + public function connect() + { + $config = $this->_config; + + $this->_redis = new \Redis(); + if (false === $this->_redis->connect($config['host'], $config['port'], $config['timeout'])) { + throw new \RuntimeException("Redis connect {$config['host']}:{$config['port']} fail."); + } + if (!empty($config['auth'])) { + $this->_redis->auth($config['auth']); + } + if (!empty($config['database'])) { + $this->_redis->select($config['database']); + } + if (empty($config['prefix'])) { + $config['prefix'] = 'redis_session_'; + } + $this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']); + } + + /** + * {@inheritdoc} + */ + public function open($save_path, $name) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($session_id) + { + try { + return $this->_redis->get($session_id); + } catch (RedisException $e) { + $msg = strtolower($e->getMessage()); + if ($msg === 'connection lost' || strpos($msg, 'went away')) { + $this->connect(); + return $this->_redis->get($session_id); + } + throw $e; + } + + } + + /** + * {@inheritdoc} + */ + public function write($session_id, $session_data) + { + return true === $this->_redis->setex($session_id, Session::$lifetime, $session_data); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($id, $data = "") + { + return true === $this->_redis->expire($id, Session::$lifetime); + } + + /** + * {@inheritdoc} + */ + public function destroy($session_id) + { + $this->_redis->del($session_id); + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php new file mode 100644 index 0000000..23a47f2 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php @@ -0,0 +1,114 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols\Http\Session; + +interface SessionHandlerInterface +{ + /** + * Close the session + * @link http://php.net/manual/en/sessionhandlerinterface.close.php + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function close(); + + /** + * Destroy a session + * @link http://php.net/manual/en/sessionhandlerinterface.destroy.php + * @param string $session_id The session ID being destroyed. + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function destroy($session_id); + + /** + * Cleanup old sessions + * @link http://php.net/manual/en/sessionhandlerinterface.gc.php + * @param int $maxlifetime

+ * Sessions that have not updated for + * the last maxlifetime seconds will be removed. + *

+ * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function gc($maxlifetime); + + /** + * Initialize session + * @link http://php.net/manual/en/sessionhandlerinterface.open.php + * @param string $save_path The path where to store/retrieve the session. + * @param string $name The session name. + * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function open($save_path, $name); + + + /** + * Read session data + * @link http://php.net/manual/en/sessionhandlerinterface.read.php + * @param string $session_id The session id to read data for. + * @return string

+ * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function read($session_id); + + /** + * Write session data + * @link http://php.net/manual/en/sessionhandlerinterface.write.php + * @param string $session_id The session id. + * @param string $session_data

+ * The encoded session data. This data is the + * result of the PHP internally encoding + * the $_SESSION superglobal to a serialized + * string and passing it as this parameter. + * Please note sessions use an alternative serialization method. + *

+ * @return bool

+ * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + *

+ * @since 5.4.0 + */ + public function write($session_id, $session_data); + + /** + * Update sesstion modify time. + * + * @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php + * + * @param string $id Session id. + * @param string $data Session Data. + * + * @return bool + */ + public function updateTimestamp($id, $data = ""); + +} diff --git a/vendor/workerman/workerman/Protocols/Http/mime.types b/vendor/workerman/workerman/Protocols/Http/mime.types new file mode 100644 index 0000000..e6ccf0a --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Http/mime.types @@ -0,0 +1,90 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/font-woff woff; + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; + font/ttf ttf; +} diff --git a/vendor/workerman/workerman/Protocols/ProtocolInterface.php b/vendor/workerman/workerman/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..4fea87d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param string $recv_buffer + * @param ConnectionInterface $connection + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param mixed $data + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/vendor/workerman/workerman/Protocols/Text.php b/vendor/workerman/workerman/Protocols/Text.php new file mode 100644 index 0000000..407ea2d --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Judge whether the package length exceeds the limit. + if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = \strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return \rtrim($buffer, "\r\n"); + } +} \ No newline at end of file diff --git a/vendor/workerman/workerman/Protocols/Websocket.php b/vendor/workerman/workerman/Protocols/Websocket.php new file mode 100644 index 0000000..0f94de6 --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Websocket.php @@ -0,0 +1,564 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB_DEFLATE = "\xc1"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER_DEFLATE = "\xc2"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = \strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->context->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $data_len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $masked = $second_byte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $first_byte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + $close_cb = $connection->onWebSocketClose ?? $connection->worker->onWebSocketClose ?? false; + if ($close_cb) { + try { + $close_cb($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close("\x88\x02\x03\xe8", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $data_len = $arr['c1'] * 4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = \strlen($connection->context->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + $ping_cb = $connection->onWebSocketPing ?? $connection->worker->onWebSocketPing ?? false; + if ($ping_cb) { + try { + $ping_cb($connection, $ping_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + $pong_cb = $connection->onWebSocketPong ?? $connection->worker->onWebSocketPong ?? false; + if ($pong_cb) { + try { + $pong_cb($connection, $pong_data); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(\substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->context->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recv_len) { + static::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $current_frame_length = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(\substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . \gettype($buffer) . ") to client, you need to convert it to a string. "); + } + + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + // permessage-deflate + if (\ord($connection->websocketType) & 64) { + $buffer = static::deflate($connection, $buffer); + } + + $first_byte = $connection->websocketType; + $len = \strlen($buffer); + + if ($len <= 125) { + $encode_buffer = $first_byte . \chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->context->websocketHandshake)) { + if (empty($connection->context->tmpWebsocketData)) { + $connection->context->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $first_byte = \ord($buffer[0]); + $second_byte = \ord($buffer[1]); + $len = $second_byte & 127; + $is_fin_frame = $first_byte >> 7; + $rsv1 = 64 === ($first_byte & 64); + + if ($len === 126) { + $masks = \substr($buffer, 4, 4); + $data = \substr($buffer, 8); + } else { + if ($len === 127) { + $masks = \substr($buffer, 10, 4); + $data = \substr($buffer, 14); + } else { + $masks = \substr($buffer, 2, 4); + $data = \substr($buffer, 6); + } + } + $dataLength = \strlen($data); + $masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4); + $decoded = $data ^ $masks; + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decoded; + if ($rsv1) { + return static::inflate($connection, $connection->context->websocketDataBuffer, $is_fin_frame); + } + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decoded = $connection->context->websocketDataBuffer . $decoded; + $connection->context->websocketDataBuffer = ''; + } + if ($rsv1) { + return static::inflate($connection, $decoded, $is_fin_frame); + } + return $decoded; + } + } + + /** + * Inflate. + * + * @param $connection + * @param $buffer + * @param $is_fin_frame + * @return false|string + */ + protected static function inflate($connection, $buffer, $is_fin_frame) + { + if (!isset($connection->context->inflator)) { + $connection->context->inflator = \inflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 9, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + if ($is_fin_frame) { + $buffer .= "\x00\x00\xff\xff"; + } + return \inflate_add($connection->context->inflator, $buffer); + } + + /** + * Deflate. + * + * @param $connection + * @param $buffer + * @return false|string + */ + protected static function deflate($connection, $buffer) + { + if (!isset($connection->context->deflator)) { + $connection->context->deflator = \deflate_init( + \ZLIB_ENCODING_RAW, + [ + 'level' => -1, + 'memory' => 8, + 'window' => 9, + 'strategy' => \ZLIB_DEFAULT_STRATEGY + ] + ); + } + return \substr(\deflate_add($connection->context->deflator, $buffer), 0, -4); + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === \strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $header_end_pos = \strpos($buffer, "\r\n\r\n"); + if (!$header_end_pos) { + return 0; + } + $header_length = $header_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->close("HTTP/1.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n

WebSocket


workerman/" . Worker::VERSION . "
", + true); + return 0; + } + // Calculation websocket key. + $new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n" + . "Upgrade: websocket\r\n" + . "Sec-WebSocket-Version: 13\r\n" + . "Connection: Upgrade\r\n" + . "Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->context->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->context->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->context->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // Try to emit onWebSocketConnect callback. + $on_websocket_connect = $connection->onWebSocketConnect ?? $connection->worker->onWebSocketConnect ?? false; + if ($on_websocket_connect) { + static::parseHttpHeader($buffer); + try { + \call_user_func($on_websocket_connect, $connection, $buffer); + } catch (\Exception $e) { + Worker::stopAll(250, $e); + } catch (\Error $e) { + Worker::stopAll(250, $e); + } + if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + } + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + if (isset($connection->headers)) { + if (\is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (\stripos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + if (\stripos($connection->headers, 'Server:') !== false) { + $has_server_header = true; + } + $handshake_message .= "$connection->headers\r\n"; + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/" . Worker::VERSION . "\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->context->websocketHandshake = true; + + // There are data waiting to be sent. + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $header_length) { + return static::input(\substr($buffer, $header_length), $connection); + } + return 0; + } + // Bad websocket handshake request. + $connection->close("HTTP/1.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n

WebSocket


workerman/" . Worker::VERSION . "
", + true); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = \explode("\r\n\r\n", $buffer, 2); + $header_data = \explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = \explode(':', $content, 2); + $key = \str_replace('-', '_', \strtoupper($key)); + $value = \trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = \explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + \parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + \parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } + +} diff --git a/vendor/workerman/workerman/Protocols/Ws.php b/vendor/workerman/workerman/Protocols/Ws.php new file mode 100644 index 0000000..3db887e --- /dev/null +++ b/vendor/workerman/workerman/Protocols/Ws.php @@ -0,0 +1,432 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Timer; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\ConnectionInterface; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + if (empty($connection->context->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->context->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recvLen = \strlen($buffer); + if ($recvLen < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->context->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->context->websocketCurrentFrameLength > $recvLen) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = \ord($buffer[0]); + $secondbyte = \ord($buffer[1]); + $dataLen = $secondbyte & 127; + $isFinFrame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + // Blob type. + case 0x1: + // Arraybuffer type. + case 0x2: + // Ping package. + case 0x9: + // Pong package. + case 0xa: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + ($connection->onWebSocketClose)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($dataLen === 126) { + if (\strlen($buffer) < 4) { + return 0; + } + $pack = \unpack('nn/ntotal_len', $buffer); + $currentFrameLength = $pack['total_len'] + 4; + } else if ($dataLen === 127) { + if (\strlen($buffer) < 10) { + return 0; + } + $arr = \unpack('n/N2c', $buffer); + $currentFrameLength = $arr['c1'] * 4294967296 + $arr['c2'] + 10; + } else { + $currentFrameLength = $dataLen + 2; + } + + $totalPackageSize = \strlen($connection->context->websocketDataBuffer) + $currentFrameLength; + if ($totalPackageSize > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$totalPackageSize\n"); + $connection->close(); + return 0; + } + + if ($isFinFrame) { + if ($opcode === 0x9) { + if ($recvLen >= $currentFrameLength) { + $pingData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + ($connection->onWebSocketPing)($connection, $pingData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } else { + $connection->send($pingData); + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recvLen >= $currentFrameLength) { + $pongData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection); + $connection->consumeRecvBuffer($currentFrameLength); + $tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + ($connection->onWebSocketPong)($connection, $pongData); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + $connection->websocketType = $tmpConnectionType; + if ($recvLen > $currentFrameLength) { + return static::input(\substr($buffer, $currentFrameLength), $connection); + } + } + return 0; + } + return $currentFrameLength; + } else { + $connection->context->websocketCurrentFrameLength = $currentFrameLength; + } + } + // Received just a frame length data. + if ($connection->context->websocketCurrentFrameLength === $recvLen) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $connection->context->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->context->websocketCurrentFrameLength < $recvLen) { + self::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength); + $currentFrameLength = $connection->context->websocketCurrentFrameLength; + $connection->context->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(\substr($buffer, $currentFrameLength), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, ConnectionInterface $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->context->handshakeStep)) { + static::sendHandshake($connection); + } + + $maskKey = "\x00\x00\x00\x00"; + $length = \strlen($payload); + + if (strlen($payload) < 126) { + $head = chr(0x80 | $length); + } elseif ($length < 0xFFFF) { + $head = chr(0x80 | 126) . pack("n", $length); + } else { + $head = chr(0x80 | 127) . pack("N", 0) . pack("N", $length); + } + + $frame = $connection->websocketType . $head . $maskKey; + // append payload to frame: + $maskKey = \str_repeat($maskKey, \floor($length / 4)) . \substr($maskKey, 0, $length % 4); + $frame .= $payload ^ $maskKey; + if ($connection->context->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + ($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + return ''; + } + $connection->context->tmpWebsocketData = $connection->context->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + ($connection->onBufferFull)($connection); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, ConnectionInterface $connection) + { + $dataLength = \ord($bytes[1]); + + if ($dataLength === 126) { + $decodedData = \substr($bytes, 4); + } else if ($dataLength === 127) { + $decodedData = \substr($bytes, 10); + } else { + $decodedData = \substr($bytes, 2); + } + if ($connection->context->websocketCurrentFrameLength) { + $connection->context->websocketDataBuffer .= $decodedData; + return $connection->context->websocketDataBuffer; + } else { + if ($connection->context->websocketDataBuffer !== '') { + $decodedData = $connection->context->websocketDataBuffer . $decodedData; + $connection->context->websocketDataBuffer = ''; + } + return $decodedData; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + static::sendHandshake($connection); + } + + /** + * Clean + * + * @param TcpConnection $connection + */ + public static function onClose($connection) + { + $connection->context->handshakeStep = null; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->tmpWebsocketData = ''; + $connection->context->websocketDataBuffer = ''; + if (!empty($connection->context->websocketPingTimer)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param TcpConnection $connection + * @return void + */ + public static function sendHandshake(ConnectionInterface $connection) + { + if (!empty($connection->context->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->context->websocketSecKey = \base64_encode(random_bytes(16)); + $userHeader = $connection->headers ?? null; + $userHeaderStr = ''; + if (!empty($userHeader)) { + if (\is_array($userHeader)) { + foreach ($userHeader as $k => $v) { + $userHeaderStr .= "$k: $v\r\n"; + } + } else { + $userHeaderStr .= $userHeader; + } + $userHeaderStr = "\r\n" . \trim($userHeaderStr); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n" . + (!\preg_match("/\nHost:/i", $userHeaderStr) ? "Host: $host\r\n" : '') . + "Connection: Upgrade\r\n" . + "Upgrade: websocket\r\n" . + (isset($connection->websocketOrigin) ? "Origin: " . $connection->websocketOrigin . "\r\n" : '') . + (isset($connection->websocketClientProtocol) ? "Sec-WebSocket-Protocol: " . $connection->websocketClientProtocol . "\r\n" : '') . + "Sec-WebSocket-Version: 13\r\n" . + "Sec-WebSocket-Key: " . $connection->context->websocketSecKey . $userHeaderStr . "\r\n\r\n"; + $connection->send($header, true); + $connection->context->handshakeStep = 1; + $connection->context->websocketCurrentFrameLength = 0; + $connection->context->websocketDataBuffer = ''; + $connection->context->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, ConnectionInterface $connection) + { + $pos = \strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (\preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== \base64_encode(\sha1($connection->context->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . \substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (\preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->websocketServerProtocol = \trim($match[1]); + } + + $connection->context->handshakeStep = 2; + $handshakeResponseLength = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + ($connection->onWebSocketConnect)($connection, \substr($buffer, 0, $handshakeResponseLength)); + } catch (\Throwable $e) { + Worker::stopAll(250, $e); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->context->websocketPingTimer = Timer::add($connection->websocketPingInterval, function () use ($connection) { + if (false === $connection->send(\pack('H*', '898000000000'), true)) { + Timer::del($connection->context->websocketPingTimer); + $connection->context->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshakeResponseLength); + if (!empty($connection->context->tmpWebsocketData)) { + $connection->send($connection->context->tmpWebsocketData, true); + $connection->context->tmpWebsocketData = ''; + } + if (\strlen($buffer) > $handshakeResponseLength) { + return self::input(\substr($buffer, $handshakeResponseLength), $connection); + } + } + return 0; + } + +} diff --git a/vendor/workerman/workerman/README.md b/vendor/workerman/workerman/README.md new file mode 100644 index 0000000..6038c02 --- /dev/null +++ b/vendor/workerman/workerman/README.md @@ -0,0 +1,342 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. +Workerman supports HTTP, Websocket, SSL and other custom protocols. +Workerman supports event extension. + +## Requires +PHP 7.0 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions required +Event extension recommended for better performance + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +// Emitted when data received +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +count = 4; + +// Emitted when data received +$http_worker->onMessage = function ($connection, $request) { + //$request->get(); + //$request->post(); + //$request->header(); + //$request->cookie(); + //$request->session(); + //$request->uri(); + //$request->path(); + //$request->method(); + + // Send data to client + $connection->send("Hello World"); +}; + +// Run all workers +Worker::runAll(); +``` + +### A tcp server +```php +count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function ($connection) { + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello $data \n"); +}; + +// Emitted when connection is closed +$tcp_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### A udp server + +```php +count = 4; + +// Emitted when data received +$worker->onMessage = function($connection, $data) +{ + $connection->send($data); +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker('websocket://0.0.0.0:2346', $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function ($connection, $data) { + // Send hello $data + $connection->send('Hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +onConnect = function ($connection) { + echo "New connection\n"; +}; + +$text_worker->onMessage = function ($connection, $data) { + // Send data to client + $connection->send("Hello world\n"); +}; + +$text_worker->onClose = function ($connection) { + echo "Connection closed\n"; +}; + +// Run all workers +Worker::runAll(); +``` + +### Timer +```php +onWorkerStart = function ($task) { + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, function () { + echo "Timer run\n"; + }); +}; + +// Run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +onWorkerStart = function () { + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection('ws://echo.websocket.org:80'); + $ws_connection->onConnect = function ($connection) { + $connection->send('Hello'); + }; + $ws_connection->onMessage = function ($connection, $data) { + echo "Recv: $data\n"; + }; + $ws_connection->onError = function ($connection, $code, $msg) { + echo "Error: $msg\n"; + }; + $ws_connection->onClose = function ($connection) { + echo "Connection closed\n"; + }; + $ws_connection->connect(); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](https://www.workerman.net) + +中文文档: [https://www.workerman.net/doc/workerman](https://www.workerman.net/doc/workerman) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/SUMMARY.md) + +# Benchmarks +https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=db&l=yyku7z-e7&a=2 +![image](https://user-images.githubusercontent.com/6073368/146704320-1559fe97-aa67-4ee3-95d6-61e341b3c93b.png) + +## Sponsors +[opencollective.com/walkor](https://opencollective.com/walkor) + +[patreon.com/walkor](https://patreon.com/walkor) + +## Donate + + + +## Other links with workerman + +[webman](https://github.com/walkor/webman) +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/vendor/workerman/workerman/Timer.php b/vendor/workerman/workerman/Timer.php new file mode 100644 index 0000000..9f152f3 --- /dev/null +++ b/vendor/workerman/workerman/Timer.php @@ -0,0 +1,220 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use \Exception; + +/** + * Timer. + * + * example: + * Workerman\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var EventInterface + */ + protected static $_event = null; + + /** + * timer id + * + * @var int + */ + protected static $_timerId = 0; + + /** + * timer status + * [ + * timer_id1 => bool, + * timer_id2 => bool, + * ...................., + * ] + * + * @var array + */ + protected static $_status = array(); + + /** + * Init. + * + * @param EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + return; + } + if (\function_exists('pcntl_signal')) { + \pcntl_signal(\SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + \pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int|bool + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if ($args === null) { + $args = array(); + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + // If not workerman runtime just return. + if (!Worker::getAllWorkers()) { + return; + } + + if (!\is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + \pcntl_alarm(1); + } + + $run_time = \time() + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + + self::$_timerId = self::$_timerId == \PHP_INT_MAX ? 1 : ++self::$_timerId; + self::$_status[self::$_timerId] = true; + self::$_tasks[$run_time][self::$_timerId] = array($func, (array)$args, $persistent, $time_interval); + + return self::$_timerId; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + \pcntl_alarm(0); + return; + } + $time_now = \time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + \call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if($persistent && !empty(self::$_status[$index])) { + $new_run_time = \time() + $time_interval; + if(!isset(self::$_tasks[$new_run_time])) self::$_tasks[$new_run_time] = array(); + self::$_tasks[$new_run_time][$index] = array($task_func, (array)$task_args, $persistent, $time_interval); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + foreach(self::$_tasks as $run_time => $task_data) + { + if(array_key_exists($timer_id, $task_data)) unset(self::$_tasks[$run_time][$timer_id]); + } + + if(array_key_exists($timer_id, self::$_status)) unset(self::$_status[$timer_id]); + + return true; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = self::$_status = array(); + if (\function_exists('pcntl_alarm')) { + \pcntl_alarm(0); + } + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/vendor/workerman/workerman/Worker.php b/vendor/workerman/workerman/Worker.php new file mode 100644 index 0000000..860d728 --- /dev/null +++ b/vendor/workerman/workerman/Worker.php @@ -0,0 +1,2672 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use \Exception; + +/** + * Worker class + * A container for listening ports + */ +#[\AllowDynamicProperties] +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '4.1.15'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callable + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callable + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callable + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callable + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callable + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callable + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callable + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stopped. + * + * @var callable + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callable + */ + public $onWorkerReload = null; + + /** + * Emitted when worker processes exited. + * + * @var callable + */ + public $onWorkerExit = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * The file used to store the master process status file. + * + * @var string + */ + public static $statusFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callable + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callable + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * Process title + * + * @var string + */ + public static $processTitle = 'WorkerMan'; + + /** + * After sending the stop command to the child process stopTimeout seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + public static $stopTimeout = 2; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** parse from _socketName avoid parse again in master or worker + * LocalSocket The format is like tcp://0.0.0.0:8080 + * @var string + */ + + protected $_localSocket=null; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker processes pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = \OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'event' => '\Workerman\Events\Event', + 'libevent' => '\Workerman\Events\Libevent' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * PHP built-in error types. + * + * @var array + */ + protected static $_errorType = array( + \E_ERROR => 'E_ERROR', // 1 + \E_WARNING => 'E_WARNING', // 2 + \E_PARSE => 'E_PARSE', // 4 + \E_NOTICE => 'E_NOTICE', // 8 + \E_CORE_ERROR => 'E_CORE_ERROR', // 16 + \E_CORE_WARNING => 'E_CORE_WARNING', // 32 + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', // 64 + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', // 128 + \E_USER_ERROR => 'E_USER_ERROR', // 256 + \E_USER_WARNING => 'E_USER_WARNING', // 512 + \E_USER_NOTICE => 'E_USER_NOTICE', // 1024 + \E_STRICT => 'E_STRICT', // 2048 + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // 4096 + \E_DEPRECATED => 'E_DEPRECATED', // 8192 + \E_USER_DEPRECATED => 'E_USER_DEPRECATED' // 16384 + ); + + /** + * Graceful stop or not. + * + * @var bool + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::parseCommand(); + static::lock(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::lock(\LOCK_UN); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli and micro. + if (!in_array(\PHP_SAPI, ['cli', 'micro'])) { + exit("Only run in command line mode \n"); + } + if (\DIRECTORY_SEPARATOR === '\\') { + self::$_OS = \OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + \set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = \debug_backtrace(); + static::$_startFile = $backtrace[\count($backtrace) - 1]['file']; + + + $unique_prefix = \str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!\is_file($log_file)) { + \touch($log_file); + \chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = \time(); + + // Process title. + static::setProcessTitle(static::$processTitle . ': master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Lock. + * + * @return void + */ + protected static function lock($flag = \LOCK_EX) + { + static $fd; + if (\DIRECTORY_SEPARATOR !== '/') { + return; + } + $lock_file = static::$pidFile . '.lock'; + $fd = $fd ?: \fopen($lock_file, 'a+'); + if ($fd) { + flock($fd, $flag); + if ($flag === \LOCK_UN) { + fclose($fd); + $fd = null; + clearstatcache(); + if (\is_file($lock_file)) { + unlink($lock_file); + } + } + } + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_statisticsFile = static::$statusFile ? static::$statusFile : __DIR__ . '/../workerman-' .posix_getpid().'.status'; + + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get column mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + !isset($worker->{$prop}) && $worker->{$prop} = 'NNNN'; + $prop_length = \strlen((string) $worker->{$prop}); + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + static::$$key = \max(static::$$key, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * Reload all worker instances. + * + * @return void + */ + public static function reloadAllWorkers() + { + static::init(); + static::initWorkers(); + static::displayUI(); + static::$_status = static::STATUS_RELOADING; + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Get main socket resource + * @return resource + */ + public function getMainSocket(){ + return $this->_mainSocket; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count < 1 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = \posix_getpwuid(\posix_getuid()); + return $user_info['name'] ?? 'unknown'; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (\in_array('-q', $argv)) { + return; + } + if (static::$_OS !== \OS_TYPE_LINUX) { + static::safeEcho("---------------------------------------------- WORKERMAN -----------------------------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. ' PHP version:'. \PHP_VERSION. "\r\n"); + static::safeEcho("----------------------------------------------- WORKERS ------------------------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . \str_pad('PHP version:', 22, ' ', \STR_PAD_LEFT) . \PHP_VERSION; + $line_version .= \str_pad('Event-Loop:', 22, ' ', \STR_PAD_LEFT) . static::getEventLoopName() . \PHP_EOL; + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', \strlen($line_version)); + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . \str_pad(' WORKERMAN ', $total_length + \strlen(''), '-', \STR_PAD_BOTH) . ''. \PHP_EOL; + $line_two = \str_pad(' WORKERS ' , $total_length + \strlen(''), '-', \STR_PAD_BOTH) . \PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + //just keep compatible with listen name + $column_name === 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . \str_pad('', static::$$key + static::UI_SAFE_LENGTH - \strlen($column_name)); + } + $title && static::safeEcho($title . \PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + \preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", (string) $worker->{$prop}, $matches); + $place_holder_length = !empty($matches) ? \strlen(\implode('', $matches[0])) : 0; + $content .= \str_pad((string) $worker->{$prop}, static::$$key + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . \PHP_EOL); + } + + //Show last line + $line_last = \str_pad('', static::getSingleLineTotalLength(), '-') . \PHP_EOL; + !empty($content) && static::safeEcho($line_last); + + if (static::$daemonize) { + $tmpArgv = $argv; + foreach ($tmpArgv as $index => $value) { + if ($value == '-d') { + unset($tmpArgv[$index]); + } elseif ($value == 'start' || $value == 'restart') { + $tmpArgv[$index] = 'stop'; + } + } + static::safeEcho("Input \"php ".implode(' ', $tmpArgv)."\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + return array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; + $total_length += static::$$key + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + !\defined('LINE_VERSIOIN_LENGTH') && \define('LINE_VERSIOIN_LENGTH', 0); + $total_length <= LINE_VERSIOIN_LENGTH && $total_length = LINE_VERSIOIN_LENGTH; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $available_mode = array( + '-d', + '-g' + ); + $command = $mode = ''; + foreach ($argv as $value) { + if (\in_array($value, $available_commands)) { + $command = $value; + } elseif (\in_array($value, $available_mode)) { + $mode = $value; + } + } + + if (!$command) { + exit($usage); + } + + // Start command. + $mode_str = ''; + if ($command === 'start') { + if ($mode === '-d' || static::$daemonize) { + $mode_str = 'in DAEMON mode'; + } else { + $mode_str = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode_str"); + + // Get master process PID. + $master_pid = \is_file(static::$pidFile) ? (int)\file_get_contents(static::$pidFile) : 0; + // Master is still alive? + if (static::checkMasterIsAlive($master_pid)) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + $statistics_file = static::$statusFile ? static::$statusFile : __DIR__ . "/../workerman-$master_pid.status"; + + // execute command. + switch ($command) { + case 'start': + if ($mode === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (\is_file($statistics_file)) { + @\unlink($statistics_file); + } + // Master process will send SIGIOT signal to all child processes. + \posix_kill($master_pid, SIGIOT); + // Sleep 1 second. + \sleep(1); + // Clear terminal. + if ($mode === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData($statistics_file)); + if ($mode !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (\is_file($statistics_file) && \is_writable($statistics_file)) { + \unlink($statistics_file); + } + // Master process will send SIGIO signal to all child processes. + \posix_kill($master_pid, SIGIO); + // Waiting amoment. + \usleep(500000); + // Display statisitcs data from a disk file. + if(\is_readable($statistics_file)) { + \readfile($statistics_file); + } + exit(0); + case 'restart': + case 'stop': + if ($mode === '-g') { + static::$_gracefulStop = true; + $sig = \SIGQUIT; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = \SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && \posix_kill($master_pid, $sig); + // Timeout. + $timeout = static::$stopTimeout + 3; + $start_time = \time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && \time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + \usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($mode === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($mode === '-g'){ + $sig = \SIGUSR2; + }else{ + $sig = \SIGUSR1; + } + \posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @param $statistics_file + * @return string + */ + protected static function formatStatusData($statistics_file) + { + static $total_request_cache = array(); + if (!\is_readable($statistics_file)) { + return ''; + } + $info = \file($statistics_file, \FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $workerInfo = []; + try { + $workerInfo = unserialize($info[0], ['allowed_classes' => false]); + } catch (Throwable $exception) {} + if (!is_array($workerInfo)) { + $workerInfo = []; + } + \ksort($workerInfo, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (\preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(\preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(\preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += \intval(\str_ireplace('M','',$match[1])); + $maxLen1 = \max($maxLen1,\strlen($match[2])); + $maxLen2 = \max($maxLen2,\strlen($match[3])); + $total_connections += \intval($match[4]); + $total_fails += \intval($match[5]); + $total_timers += \intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += \intval($match[7]); + } + } + } + foreach($workerInfo as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . \str_pad('N/A', 7) . " " + . \str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . \str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . \str_pad('N/A', 11) . " " . \str_pad('N/A', 9) . " " + . \str_pad('N/A', 7) . " " . \str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . \str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . \str_pad($total_memory.'M', 7) . " " + . \str_pad('-', $maxLen1) . " " + . \str_pad('-', $maxLen2) . " " + . \str_pad($total_connections, 11) . " " . \str_pad($total_fails, 9) . " " + . \str_pad($total_timers, 7) . " " . \str_pad($total_requests, 13) . " " + . \str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // stop + \pcntl_signal(\SIGINT, $signalHandler, false); + // stop + \pcntl_signal(\SIGTERM, $signalHandler, false); + // stop + \pcntl_signal(\SIGHUP, $signalHandler, false); + // stop + \pcntl_signal(\SIGTSTP, $signalHandler, false); + // graceful stop + \pcntl_signal(\SIGQUIT, $signalHandler, false); + // reload + \pcntl_signal(\SIGUSR1, $signalHandler, false); + // graceful reload + \pcntl_signal(\SIGUSR2, $signalHandler, false); + // status + \pcntl_signal(\SIGIOT, $signalHandler, false); + // connection status + \pcntl_signal(\SIGIO, $signalHandler, false); + // ignore + \pcntl_signal(\SIGPIPE, \SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + $signalHandler = '\Workerman\Worker::signalHandler'; + // uninstall stop signal handler + \pcntl_signal(\SIGINT, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTERM, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGHUP, \SIG_IGN, false); + // uninstall stop signal handler + \pcntl_signal(\SIGTSTP, \SIG_IGN, false); + // uninstall graceful stop signal handler + \pcntl_signal(\SIGQUIT, \SIG_IGN, false); + // uninstall reload signal handler + \pcntl_signal(\SIGUSR1, \SIG_IGN, false); + // uninstall graceful reload signal handler + \pcntl_signal(\SIGUSR2, \SIG_IGN, false); + // uninstall status signal handler + \pcntl_signal(\SIGIOT, \SIG_IGN, false); + // uninstall connections status signal handler + \pcntl_signal(\SIGIO, \SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(\SIGINT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGQUIT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGHUP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful stop signal handler + static::$globalEvent->add(\SIGTSTP, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall reload signal handler + static::$globalEvent->add(\SIGUSR1, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall graceful reload signal handler + static::$globalEvent->add(\SIGUSR2, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall status signal handler + static::$globalEvent->add(\SIGIOT, EventInterface::EV_SIGNAL, $signalHandler); + // reinstall connection status signal handler + static::$globalEvent->add(\SIGIO, EventInterface::EV_SIGNAL, $signalHandler); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case \SIGINT: + case \SIGTERM: + case \SIGHUP: + case \SIGTSTP: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case \SIGQUIT: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case \SIGUSR2: + case \SIGUSR1: + if (static::$_status === static::STATUS_SHUTDOWN || static::$_status === static::STATUS_RELOADING) { + return; + } + static::$_gracefulStop = $signal === \SIGUSR2; + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case \SIGIOT: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case \SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as daemon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { + return; + } + \umask(0); + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception('Fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === \posix_setsid()) { + throw new Exception("Setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = \pcntl_fork(); + if (-1 === $pid) { + throw new Exception("Fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || \DIRECTORY_SEPARATOR !== '/') { + return; + } + global $STDOUT, $STDERR; + $handle = \fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + \set_error_handler(function(){}); + if ($STDOUT) { + \fclose($STDOUT); + } + if ($STDERR) { + \fclose($STDERR); + } + if (\is_resource(\STDOUT)) { + \fclose(\STDOUT); + } + if (\is_resource(\STDERR)) { + \fclose(\STDERR); + } + $STDOUT = \fopen(static::$stdoutFile, "a"); + $STDERR = \fopen(static::$stdoutFile, "a"); + // Fix standard output cannot redirect of PHP 8.1.8's bug + if (\function_exists('posix_isatty') && \posix_isatty(2)) { + \ob_start(function ($string) { + \file_put_contents(static::$stdoutFile, $string, FILE_APPEND); + }, 1); + } + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + \restore_error_handler(); + return; + } + + throw new Exception('Can not open stdoutFile ' . static::$stdoutFile); + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== \OS_TYPE_LINUX) { + return; + } + + static::$_masterPid = \posix_getpid(); + if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!\class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (\extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } else { + static::$eventLoopClass = '\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = \strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(\in_array('-q', $argv) || \count($files) === 1) + { + if(\count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ See http://doc.workerman.net/faq/multi-woker-for-windows.html @@@\r\n"); + } + elseif(\count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + \reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + \Workerman\Timer::delAll(); + + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function([__CLASS__, 'checkErrors']); + + // Create a global event loop. + if (!static::$globalEvent) { + $eventLoopClass = static::getEventLoopName(); + static::$globalEvent = new $eventLoopClass; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + // Add an empty timer to prevent the event-loop from exiting. + Timer::add(1000000, function (){}); + + // Display UI. + static::safeEcho(\str_pad($worker->name, 48) . \str_pad($worker->getSocketName(), 36) . \str_pad('1', 10) . " [ok]\n"); + $worker->listen(); + $worker->run(); + static::$globalEvent->loop(); + if (static::$_status !== self::STATUS_SHUTDOWN) { + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } + exit(0); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(\is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = \realpath($start_file); + + $descriptorspec = array( + STDIN, STDOUT, STDOUT + ); + + $pipes = array(); + $process = \proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $status = \proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + \proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param self $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux(self $worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = \pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + \srand(); + \mt_srand(); + static::$_gracefulStop = false; + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + \register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + \restore_error_handler(); + + static::setProcessTitle(self::$processTitle . ': worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + // Main loop. + static::$globalEvent->loop(); + if (strpos(static::$eventLoopClass, 'Workerman\Events\Swoole') !== false) { + exit(0); + } + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param string $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return \array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = \posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exists"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = \posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exists"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) { + if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + \set_error_handler(function(){}); + // >=php 5.5 + if (\function_exists('cli_set_process_title')) { + \cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) { + \setproctitle($title); + } + \restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === \OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + \pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = \pcntl_wait($status, \WUNTRACED); + // Calls signal handlers for pending signals again. + \pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out which worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Fix exit with status 2 for php8.2 + if ($status === \SIGINT && static::$_status === static::STATUS_SHUTDOWN) { + $status = 0; + } + // Exit status. + if ($status !== 0) { + static::log("worker[{$worker->name}:$pid] exit with status $status"); + } + + // onWorkerExit + if ($worker->onWorkerExit) { + try { + ($worker->onWorkerExit)($worker, $status, $pid); + } catch (\Throwable $exception) { + static::log("worker[{$worker->name}] onWorkerExit $exception"); + } + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status]; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = \explode(':', $socket_name, 2); + $address = substr($address, strpos($address, '/') + 2); + @\unlink($address); + } + } + @\unlink(static::$pidFile); + static::log("Workerman[" . \basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + \call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + if (static::$_gracefulStop) { + $sig = \SIGUSR2; + } else { + $sig = \SIGUSR1; + } + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . \basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + \call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + static::initId(); + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + \posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = \array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + } + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = \current(static::$_pidsToRestart); + // Send reload signal to a worker process. + \posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::$stopTimeout seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($one_worker_pid, \SIGKILL), false); + } + } // For child processes. + else { + \reset(static::$_workers); + $worker = \current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + \call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop all. + * + * @param int $code + * @param string $log + */ + public static function stopAll($code = 0, $log = '') + { + if ($log) { + static::log($log); + } + + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (\DIRECTORY_SEPARATOR === '/' && static::$_masterPid === \posix_getpid()) { + static::log("Workerman[" . \basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = \SIGQUIT; + } else { + $sig = \SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + if (static::$daemonize) { + \posix_kill($worker_pid, $sig); + } else { + Timer::add(1, '\posix_kill', array($worker_pid, $sig), false); + } + if(!static::$_gracefulStop){ + Timer::add(static::$stopTimeout, '\posix_kill', array($worker_pid, \SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (\is_file(static::$_statisticsFile)) { + @\unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + $workers = array_reverse(static::$_workers); + foreach ($workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + + try { + exit($code); + } catch (Exception $e) { + + } + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!\posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return bool + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var /Workerman/Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + \file_put_contents(static::$_statisticsFile, \serialize($all_worker_info)."\n", \FILE_APPEND); + $loadavg = \function_exists('sys_getloadavg') ? \array_map('round', \sys_getloadavg(), array(2,2,2)) : array('-', '-', '-'); + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . \PHP_VERSION . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, 'start time:' . \date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . \floor((\time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . \floor(((\time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . \implode(", ", $loadavg); + \file_put_contents(static::$_statisticsFile, + \str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \count(static::$_pidMap) . ' workers ' . \count(static::getAllWorkerPids()) . " processes\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + \str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", \FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", \FILE_APPEND); + } + } else { + \file_put_contents(static::$_statisticsFile, + \str_pad($worker->name, static::$_maxWorkerNameLength) . " " . \str_pad(0, 16) . " 0\n", + \FILE_APPEND); + } + } + \file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, + "pid\tmemory " . \str_pad('listening', static::$_maxSocketNameLength) . " " . \str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . \str_pad('send_fail', 9) . " " + . \str_pad('timers', 8) . \str_pad('total_request', 13) ." qps status\n", \FILE_APPEND); + + \chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIOT); + } + return; + } + + // For child processes. + \gc_collect_cycles(); + if (\function_exists('gc_mem_caches')) { + \gc_mem_caches(); + } + \reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = \posix_getpid() . "\t" . \str_pad(round(memory_get_usage(false) / (1024 * 1024), 2) . "M", 7) + . " " . \str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . \str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= \str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . \str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . \str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . \str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + \file_put_contents(static::$_statisticsFile, $worker_status_str, \FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === \posix_getpid()) { + \file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", \FILE_APPEND); + \file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", \FILE_APPEND); + \chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + \posix_kill($worker_pid, \SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = \posix_getpid(); + $str = ''; + \reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = \trim($connection->getLocalAddress()); + $remote_address = \trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = \strrpos($protocol, '\\'); + if ($pos) { + $protocol = \substr($protocol, $pos+1); + } + if (\strlen($protocol) > 15) { + $protocol = \substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (\strlen($worker_name) > 14) { + $worker_name = \substr($worker_name, 0, 12) . '..'; + } + $str .= \str_pad($pid, 9) . \str_pad($worker_name, 16) . \str_pad($id, 10) . \str_pad($transport, 8) + . \str_pad($protocol, 16) . \str_pad($ipv4, 7) . \str_pad($ipv6, 7) . \str_pad($recv_q, 13) + . \str_pad($send_q, 13) . \str_pad($bytes_read, 13) . \str_pad($bytes_written, 13) . ' ' + . \str_pad($state, 14) . ' ' . \str_pad($local_address, 22) . ' ' . \str_pad($remote_address, 22) ."\n"; + } + if ($str) { + \file_put_contents(static::$_statisticsFile, $str, \FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN !== static::$_status) { + $error_msg = static::$_OS === \OS_TYPE_LINUX ? 'Worker['. \posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === \E_ERROR || + $errors['type'] === \E_PARSE || + $errors['type'] === \E_CORE_ERROR || + $errors['type'] === \E_COMPILE_ERROR || + $errors['type'] === \E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + if(isset(self::$_errorType[$type])) { + return self::$_errorType[$type]; + } + + return ''; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + \file_put_contents((string)static::$logFile, \date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === \OS_TYPE_LINUX ? \posix_getpid() : 1) . ' ' . $msg, \FILE_APPEND | \LOCK_EX); + } + + /** + * Safe Echo. + * @param string $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = \str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = \str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + \fwrite($stream, $msg); + \fflush($stream); + return true; + } + + /** + * @param resource|null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : \STDOUT; + } + if (!$stream || !\is_resource($stream) || 'stream' !== \get_resource_type($stream)) { + return false; + } + $stat = \fstat($stream); + if (!$stat) { + return false; + } + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === \OS_TYPE_LINUX && + \function_exists('posix_isatty') && + \posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', array $context_option = array()) + { + // Save all worker instances. + $this->workerId = \spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = \debug_backtrace(); + $this->_autoloadRootPath = \dirname($backtrace[0]['file']); + Autoloader::setRootPath($this->_autoloadRootPath); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = \stream_context_create($context_option); + } + + // Turn reusePort on. + /*if (static::$_OS === \OS_TYPE_LINUX // if linux + && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0 + && \version_compare(php_uname('r'), '3.9', 'ge') // if kernel >=3.9 + && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS + && strpos($socket_name,'unix') !== 0) { // if not unix socket + + $this->reusePort = true; + }*/ + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + + $local_socket = $this->parseSocketAddress(); + + // Flag. + $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + \stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socket_file = \substr($local_socket, 7); + if ($this->user) { + \chown($socket_file, $this->user); + } + if ($this->group) { + \chgrp($socket_file, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + \set_error_handler(function(){}); + $socket = \socket_import_stream($this->_mainSocket); + \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); + \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1); + \restore_error_handler(); + } + + // Non blocking. + \stream_set_blocking($this->_mainSocket, false); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + \set_error_handler(function(){}); + \fclose($this->_mainSocket); + \restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Parse local socket address. + * + * @throws Exception + */ + protected function parseSocketAddress() { + if (!$this->_socketName) { + return; + } + // Get the application layer communication protocol and listening address. + list($scheme, $address) = \explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = \ucfirst($scheme); + $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : 'Protocols\\' . $scheme; + if (!\class_exists($this->protocol)) { + $this->protocol = "Workerman\\Protocols\\$scheme"; + if (!\class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new Exception('Bad worker->transport ' . \var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + //local socket + return static::$_builtinTransports[$this->transport] . ":" . $address; + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? \lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + * @throws Exception + */ + public function run() + { + $this->listen(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + \call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } catch (\Error $e) { + // Avoid rapid infinite loop exit. + sleep(1); + static::stopAll(250, $e); + } + } + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + \call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Remove worker. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId === $this->workerId) { + unset(static::$_workers[$key]); + } + } + + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + \set_error_handler(function(){}); + $new_socket = \stream_socket_accept($socket, 0, $remote_address); + \restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + \call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + \set_error_handler(function(){}); + $recv_buffer = \stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + \restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + if ($parser && \method_exists($parser, 'input')) { + while ($recv_buffer !== '') { + $len = $parser::input($recv_buffer, $connection); + if ($len === 0) + return true; + $package = \substr($recv_buffer, 0, $len); + $recv_buffer = \substr($recv_buffer, $len); + $data = $parser::decode($package, $connection); + if ($data === false) + continue; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + $data = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($data === false) + return true; + \call_user_func($this->onMessage, $connection, $data); + } + } else { + \call_user_func($this->onMessage, $connection, $recv_buffer); + } + ++ConnectionInterface::$statistics['total_request']; + } catch (\Exception $e) { + static::stopAll(250, $e); + } catch (\Error $e) { + static::stopAll(250, $e); + } + } + return true; + } + + /** + * Check master process is alive + * + * @param int $master_pid + * @return bool + */ + protected static function checkMasterIsAlive($master_pid) + { + if (empty($master_pid)) { + return false; + } + + $master_is_alive = $master_pid && \posix_kill((int) $master_pid, 0) && \posix_getpid() !== $master_pid; + if (!$master_is_alive) { + return false; + } + + $cmdline = "/proc/{$master_pid}/cmdline"; + if (!is_readable($cmdline) || empty(static::$processTitle)) { + return true; + } + + $content = file_get_contents($cmdline); + if (empty($content)) { + return true; + } + + return stripos($content, static::$processTitle) !== false || stripos($content, 'php') !== false; + } +} + diff --git a/vendor/workerman/workerman/composer.json b/vendor/workerman/workerman/composer.json new file mode 100644 index 0000000..19e2984 --- /dev/null +++ b/vendor/workerman/workerman/composer.json @@ -0,0 +1,38 @@ +{ + "name": "workerman/workerman", + "type": "library", + "keywords": [ + "event-loop", + "asynchronous" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://doc.workerman.net/", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=7.0" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "minimum-stability": "dev" +}