Repository: swoole/swoole-src Branch: master Commit: 79a82ecf36c3 Files: 2870 Total size: 10.9 MB Directory structure: gitextract_tv1z7_zt/ ├── .clang-format ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE │ ├── dependabot.yml │ └── workflows/ │ ├── alpine.Dockerfile │ ├── core.yml │ ├── coverity.yml │ ├── ext.yml │ ├── framework.yml │ ├── iouring.yml │ ├── thread.yml │ └── unit.yml ├── .gitignore ├── .php-cs-fixer.dist.php ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── Makefile.frag ├── README.md ├── codecov.yml ├── composer.json ├── config.m4 ├── core-tests/ │ ├── .gitignore │ ├── code-stats.sh │ ├── docker-compose.yml │ ├── fuzz/ │ │ ├── project.json │ │ └── src/ │ │ └── main.cpp │ ├── include/ │ │ ├── httplib_client.h │ │ ├── httplib_server.h │ │ ├── redis_client.h │ │ ├── test_core.h │ │ ├── test_coroutine.h │ │ ├── test_process.h │ │ └── test_server.h │ ├── js/ │ │ ├── .gitignore │ │ ├── mqtt.js │ │ ├── package.json │ │ ├── ws_1.js │ │ └── ws_2.js │ ├── samples/ │ │ ├── CMakeLists.txt │ │ └── src/ │ │ └── s1.cc │ └── src/ │ ├── _lib/ │ │ ├── http.cpp │ │ ├── process.cpp │ │ ├── redis.cpp │ │ ├── server.cpp │ │ └── ssl.cpp │ ├── core/ │ │ ├── base.cpp │ │ ├── channel.cpp │ │ ├── hash.cpp │ │ ├── heap.cpp │ │ ├── log.cpp │ │ ├── string.cpp │ │ ├── time.cpp │ │ └── util.cpp │ ├── coroutine/ │ │ ├── accept.cpp │ │ ├── async.cpp │ │ ├── base.cpp │ │ ├── channel.cpp │ │ ├── file.cpp │ │ ├── gethostbyname.cpp │ │ ├── hook.cpp │ │ ├── http_server.cpp │ │ ├── iouring.cpp │ │ ├── socket.cpp │ │ ├── system.cpp │ │ └── uring_socket.cpp │ ├── lock/ │ │ └── lock.cpp │ ├── main.cpp │ ├── memory/ │ │ ├── buffer.cpp │ │ ├── fixed_pool.cpp │ │ ├── global_memory.cpp │ │ ├── lru_cache.cpp │ │ ├── ringbuffer.cpp │ │ └── table.cpp │ ├── network/ │ │ ├── address.cpp │ │ ├── client.cpp │ │ ├── dns.cpp │ │ ├── socket.cpp │ │ └── stream.cpp │ ├── os/ │ │ ├── async.cpp │ │ ├── file.cpp │ │ ├── msg_queue.cpp │ │ ├── os.cpp │ │ ├── pipe.cpp │ │ ├── process_pool.cpp │ │ ├── signal.cpp │ │ ├── timer.cpp │ │ └── wait.cpp │ ├── protocol/ │ │ ├── base.cpp │ │ ├── base64.cpp │ │ ├── http2.cpp │ │ ├── mime_type.cpp │ │ ├── redis.cpp │ │ └── ssl.cpp │ ├── reactor/ │ │ └── base.cpp │ └── server/ │ ├── buffer.cpp │ ├── http_parser.cpp │ ├── http_server.cpp │ ├── message_bus.cpp │ ├── mqtt.cpp │ ├── multipart_parser.cpp │ ├── port.cpp │ └── server.cpp ├── docs/ │ ├── API.md │ ├── CHANGELOG.md │ ├── CODE-STYLE.md │ ├── CPPLINT.cfg │ ├── ISSUE.md │ ├── SUPPORTED.md │ ├── TESTS.md │ ├── WORKFLOW-PARAMETERS.md │ ├── google-style.xml │ └── sponsors.md ├── examples/ │ ├── atomic/ │ │ ├── long.php │ │ ├── test.php │ │ └── wait.php │ ├── client/ │ │ ├── c10k.php │ │ ├── get_socket.php │ │ ├── long_tcp.php │ │ ├── recv_1m.php │ │ ├── recv_file.php │ │ ├── select.php │ │ ├── simple.php │ │ ├── sync.php │ │ ├── test.txt │ │ └── udp_sync.php │ ├── coroutine/ │ │ ├── backtrace.php │ │ ├── before_server_start.php │ │ ├── behavior/ │ │ │ ├── do-while.php │ │ │ ├── for.php │ │ │ ├── foreach.php │ │ │ ├── goto.php │ │ │ ├── preemptive_timer.php │ │ │ ├── tick.php │ │ │ ├── while.php │ │ │ └── while2.php │ │ ├── cancel_throw.php │ │ ├── channel/ │ │ │ └── test.php │ │ ├── client_send_yield.php │ │ ├── client_send_yield_server.php │ │ ├── coro_array_map.php │ │ ├── coro_call_user.php │ │ ├── coro_channel.php │ │ ├── coro_destruct.php │ │ ├── coro_destuct.php │ │ ├── coro_empty.php │ │ ├── coro_gethost.php │ │ ├── coro_include.php │ │ ├── coro_invoke.php │ │ ├── coro_nested.php │ │ ├── coro_nested_empty.php │ │ ├── coro_serialize.php │ │ ├── coro_set_stack_size.php │ │ ├── coro_sleep.php │ │ ├── coro_stackless.php │ │ ├── coro_util.php │ │ ├── csp.php │ │ ├── deadlock.php │ │ ├── defer.php │ │ ├── defer_client.php │ │ ├── enable_coroutine.php │ │ ├── exception/ │ │ │ └── empty.php │ │ ├── exec.php │ │ ├── exit_in_coroutine.php │ │ ├── exit_with_status.php │ │ ├── fgets.php │ │ ├── fread.php │ │ ├── fwrite.php │ │ ├── gethostbyname.php │ │ ├── http/ │ │ │ ├── server.php │ │ │ └── write_func.php │ │ ├── http2_client.php │ │ ├── http_backend_serv.php │ │ ├── http_client.php │ │ ├── http_download.php │ │ ├── http_server.php │ │ ├── httpmulti.php │ │ ├── join.php │ │ ├── library/ │ │ │ └── base.php │ │ ├── list_coroutines.php │ │ ├── mysql_chan.php │ │ ├── mysql_escape.php │ │ ├── mysql_execute_empty.php │ │ ├── mysql_prepare.php │ │ ├── mysql_prepare_2.php │ │ ├── mysql_procedure_exec.php │ │ ├── mysql_query.php │ │ ├── mysql_unixsocket.php │ │ ├── pdo/ │ │ │ └── pdo_persistent.phpt │ │ ├── proc_open.php │ │ ├── reconnect_test.php │ │ ├── redis/ │ │ │ ├── auth.php │ │ │ ├── defer.php │ │ │ ├── eval.php │ │ │ ├── get.php │ │ │ ├── multi.php │ │ │ ├── pipeline.php │ │ │ ├── pub.php │ │ │ ├── request.php │ │ │ ├── serialize.php │ │ │ └── sub.php │ │ ├── redis_pool.php │ │ ├── redis_subscribe.php │ │ ├── scheduler.php │ │ ├── select/ │ │ │ ├── 1.php │ │ │ ├── 2.php │ │ │ ├── 3.php │ │ │ ├── 4.php │ │ │ ├── 5.php │ │ │ ├── 6.php │ │ │ ├── 7.php │ │ │ ├── 8.php │ │ │ ├── 9.php │ │ │ ├── poptimeout.php │ │ │ └── test.php │ │ ├── send_yield.php │ │ ├── send_yield_client.php │ │ ├── server/ │ │ │ └── tcp.php │ │ ├── sleep.php │ │ ├── socket/ │ │ │ ├── accept.php │ │ │ └── sendto.php │ │ ├── stack/ │ │ │ ├── 1.php │ │ │ └── 2.php │ │ ├── stack.php │ │ ├── statvfs.php │ │ ├── task_co.php │ │ ├── tcp_backend_serv.php │ │ ├── tcp_echo.php │ │ ├── test.php │ │ ├── timer_test.php │ │ ├── udp_client.php │ │ ├── udp_tcp_timeout.php │ │ ├── user_coroutine.php │ │ ├── util/ │ │ │ ├── resume001.php │ │ │ ├── resume002.php │ │ │ └── resume003.php │ │ ├── waitgroup.php │ │ └── websocket/ │ │ ├── client.php │ │ ├── co_server.php │ │ └── server.php │ ├── cpp/ │ │ ├── Makefile │ │ ├── co.cc │ │ ├── repeat.cc │ │ └── test_server.cc │ ├── curl/ │ │ ├── hook.php │ │ ├── multi.php │ │ └── server.php │ ├── dtls/ │ │ ├── client.php │ │ └── server.php │ ├── eof/ │ │ ├── client.php │ │ └── server.php │ ├── event/ │ │ ├── cycle.php │ │ ├── inotify.php │ │ ├── sockets.php │ │ ├── stdin.php │ │ ├── stream.php │ │ └── test.php │ ├── http/ │ │ ├── UPPER.TXT │ │ ├── client.php │ │ ├── curl.php │ │ ├── detach.php │ │ ├── download.php │ │ ├── empty.txt │ │ ├── event-stream.php │ │ ├── moc.moc │ │ ├── no-compression.php │ │ ├── post.data │ │ ├── raw.php │ │ ├── redirect.php │ │ ├── server.php │ │ ├── static_handler.php │ │ ├── test.txt │ │ └── url_rewrite.php │ ├── http2/ │ │ ├── server.php │ │ ├── streaming.php │ │ └── test.html │ ├── ipv6/ │ │ ├── tcp_client.php │ │ ├── tcp_server.php │ │ ├── udp_client.php │ │ └── udp_server.php │ ├── length/ │ │ ├── client.php │ │ ├── config.php │ │ ├── func.php │ │ └── server.php │ ├── lock/ │ │ └── lock.php │ ├── misc/ │ │ ├── get_local_ip.php │ │ ├── get_local_mac.php │ │ └── version.php │ ├── multicast/ │ │ ├── client.php │ │ └── server.php │ ├── php/ │ │ ├── buf_size.php │ │ ├── debug_server.php │ │ ├── error.php │ │ ├── exception.php │ │ ├── func.php │ │ ├── inotify.php │ │ ├── proc.php │ │ ├── socket_client.php │ │ ├── socket_server.php │ │ ├── stream_client.php │ │ ├── stream_server.php │ │ └── tick.php │ ├── postgresql/ │ │ └── postgresql_coro.php │ ├── process/ │ │ ├── alarm.php │ │ ├── async_master.php │ │ ├── client.php │ │ ├── client3.php │ │ ├── close.php │ │ ├── daemon.php │ │ ├── echo.py │ │ ├── exec.php │ │ ├── func_timeout.php │ │ ├── msg_pop.php │ │ ├── msg_push.php │ │ ├── msgqueue.php │ │ ├── msgqueue2.php │ │ ├── msgqueue_client.php │ │ ├── msgqueue_pool.php │ │ ├── pool_socket.php │ │ ├── python.php │ │ ├── select.php │ │ ├── set_cpu_affinity.php │ │ ├── stdin_stdout.php │ │ ├── test.php │ │ └── worker.php │ ├── process_pool/ │ │ ├── detach.php │ │ └── send.php │ ├── runtime/ │ │ ├── coroutine.php │ │ ├── curl.php │ │ ├── file.php │ │ ├── gethostbyname.php │ │ ├── include.php │ │ ├── mkdir.php │ │ ├── mongodb.php │ │ ├── oci.php │ │ ├── odbc.php │ │ ├── read.php │ │ ├── rename.phpt │ │ ├── select.php │ │ ├── sleep.php │ │ ├── sqlite.php │ │ ├── ssl.php │ │ ├── stream.php │ │ ├── sync.php │ │ ├── time_sleep_until.php │ │ └── unlink.phpt │ ├── server/ │ │ ├── db_pool.php │ │ ├── dispatch_func.php │ │ ├── dispatch_stream.php │ │ ├── echo.php │ │ ├── eof_client.php │ │ ├── eof_server.php │ │ ├── getReceivedTime.php │ │ ├── host_update.php │ │ ├── hot_update_class.php │ │ ├── ip_dispatch.php │ │ ├── length_client.php │ │ ├── length_server.php │ │ ├── listen_1k_port.php │ │ ├── local_listener.php │ │ ├── manager_timer.php │ │ ├── mixed.php │ │ ├── multi_instance.php │ │ ├── multi_port.php │ │ ├── pipe_message.php │ │ ├── proxy_sync.php │ │ ├── redis_pool.php │ │ ├── reload_aysnc.php │ │ ├── reload_force.php │ │ ├── reload_force2.php │ │ ├── send_1m.php │ │ ├── sendfile.php │ │ ├── single.php │ │ ├── tcp_client.php │ │ ├── tcp_server.php │ │ ├── trace.php │ │ ├── uid_dispatch.php │ │ ├── unix_stream.php │ │ └── zmq.php │ ├── socket_coro/ │ │ ├── client.php │ │ ├── server.php │ │ └── udp.php │ ├── ssl/ │ │ ├── ca/ │ │ │ ├── ca-cert.pem │ │ │ ├── ca-key.pem │ │ │ ├── ca-req.csr │ │ │ ├── client-cert.pem │ │ │ ├── client-key.pem │ │ │ ├── client-req.csr │ │ │ ├── client.crt │ │ │ ├── client.key │ │ │ ├── server-cert.pem │ │ │ ├── server-key.pem │ │ │ └── server-req.csr │ │ ├── client.c │ │ ├── client.php │ │ ├── co_client.php │ │ ├── gen_cert.md │ │ ├── http_client.php │ │ ├── passphrase.php │ │ ├── server.php │ │ ├── ssl.crt │ │ ├── ssl.csr │ │ ├── ssl.key │ │ ├── ssl_passwd/ │ │ │ ├── ssl.crt │ │ │ ├── ssl.csr │ │ │ └── ssl.key │ │ ├── stream_client.php │ │ ├── swoole.log │ │ ├── webserver.php │ │ ├── websocket_client.html │ │ └── websocket_server.php │ ├── stdext/ │ │ ├── array.php │ │ ├── foreach.php │ │ ├── ref.php │ │ ├── string.php │ │ ├── typed_array.php │ │ └── typed_array_map.php │ ├── table/ │ │ ├── deadlock.php │ │ ├── iterator.php │ │ ├── server.php │ │ ├── set.php │ │ ├── simulation.php │ │ └── usage.php │ ├── task/ │ │ ├── http.php │ │ ├── msg_push.php │ │ ├── shared_client.php │ │ ├── shared_server.php │ │ ├── task.php │ │ ├── task_coro.php │ │ ├── task_num.php │ │ ├── task_queue.php │ │ └── task_stream.php │ ├── thread/ │ │ ├── aio.php │ │ ├── argv.php │ │ ├── array.php │ │ ├── atomic.php │ │ ├── benchmark.php │ │ ├── co.php │ │ ├── exit.php │ │ ├── hook.php │ │ ├── lock.php │ │ ├── map.php │ │ ├── mt.php │ │ ├── nested_map.php │ │ ├── pipe.php │ │ ├── run_test.php │ │ ├── server.php │ │ ├── signal.php │ │ ├── socket.php │ │ ├── test.php │ │ ├── thread_pool.php │ │ └── thread_server.php │ ├── timer/ │ │ ├── after.php │ │ ├── clear.php │ │ ├── enable_coroutine.php │ │ └── tick.php │ ├── tracer/ │ │ ├── blocking.php │ │ ├── cli.php │ │ ├── co.php │ │ ├── co_server.php │ │ ├── co_socket.php │ │ ├── fn_call.php │ │ └── gc.php │ ├── udp/ │ │ ├── client.php │ │ └── server.php │ ├── unixsock/ │ │ ├── dgram_client.php │ │ ├── dgram_server.php │ │ ├── stream_client.php │ │ └── stream_server.php │ ├── websocket/ │ │ ├── client.html │ │ ├── client.php │ │ └── server.php │ └── www/ │ ├── dir1/ │ │ ├── file1.txt │ │ └── file2.txt │ ├── dir2/ │ │ ├── file1.txt │ │ ├── file2.txt │ │ └── index.txt │ ├── file1.txt │ ├── file2.txt │ ├── index.html │ └── index.txt ├── ext-src/ │ ├── php_swoole.cc │ ├── php_swoole_call_stack.h │ ├── php_swoole_client.h │ ├── php_swoole_coroutine.h │ ├── php_swoole_coroutine_system.h │ ├── php_swoole_curl.h │ ├── php_swoole_cxx.cc │ ├── php_swoole_cxx.h │ ├── php_swoole_firebird.h │ ├── php_swoole_ftp_def.h │ ├── php_swoole_http.h │ ├── php_swoole_http_server.h │ ├── php_swoole_library.h │ ├── php_swoole_odbc.h │ ├── php_swoole_oracle.h │ ├── php_swoole_pgsql.h │ ├── php_swoole_private.h │ ├── php_swoole_process.h │ ├── php_swoole_server.h │ ├── php_swoole_sqlite.h │ ├── php_swoole_ssh2.h │ ├── php_swoole_ssh2_def.h │ ├── php_swoole_ssh2_hook.h │ ├── php_swoole_stdext.h │ ├── php_swoole_thread.h │ ├── php_swoole_websocket.h │ ├── stubs/ │ │ ├── php_swoole.stub.php │ │ ├── php_swoole_arginfo.h │ │ ├── php_swoole_atomic.stub.php │ │ ├── php_swoole_atomic_arginfo.h │ │ ├── php_swoole_channel_coro.stub.php │ │ ├── php_swoole_channel_coro_arginfo.h │ │ ├── php_swoole_client.stub.php │ │ ├── php_swoole_client_arginfo.h │ │ ├── php_swoole_client_async.stub.php │ │ ├── php_swoole_client_async_arginfo.h │ │ ├── php_swoole_client_coro.stub.php │ │ ├── php_swoole_client_coro_arginfo.h │ │ ├── php_swoole_coroutine.stub.php │ │ ├── php_swoole_coroutine_arginfo.h │ │ ├── php_swoole_coroutine_lock.stub.php │ │ ├── php_swoole_coroutine_lock_arginfo.h │ │ ├── php_swoole_coroutine_scheduler.stub.php │ │ ├── php_swoole_coroutine_scheduler_arginfo.h │ │ ├── php_swoole_coroutine_system.stub.php │ │ ├── php_swoole_coroutine_system_arginfo.h │ │ ├── php_swoole_event.stub.php │ │ ├── php_swoole_event_arginfo.h │ │ ├── php_swoole_ex.stub.php │ │ ├── php_swoole_ex_arginfo.h │ │ ├── php_swoole_ftp.stub.php │ │ ├── php_swoole_ftp_arginfo.h │ │ ├── php_swoole_http2_client_coro.stub.php │ │ ├── php_swoole_http2_client_coro_arginfo.h │ │ ├── php_swoole_http_client_coro.stub.php │ │ ├── php_swoole_http_client_coro_arginfo.h │ │ ├── php_swoole_http_cookie.stub.php │ │ ├── php_swoole_http_cookie_arginfo.h │ │ ├── php_swoole_http_request.stub.php │ │ ├── php_swoole_http_request_arginfo.h │ │ ├── php_swoole_http_response.stub.php │ │ ├── php_swoole_http_response_arginfo.h │ │ ├── php_swoole_http_server_coro.stub.php │ │ ├── php_swoole_http_server_coro_arginfo.h │ │ ├── php_swoole_lock.stub.php │ │ ├── php_swoole_lock_arginfo.h │ │ ├── php_swoole_name_resolver.stub.php │ │ ├── php_swoole_name_resolver_arginfo.h │ │ ├── php_swoole_process.stub.php │ │ ├── php_swoole_process_arginfo.h │ │ ├── php_swoole_process_pool.stub.php │ │ ├── php_swoole_process_pool_arginfo.h │ │ ├── php_swoole_redis_server.stub.php │ │ ├── php_swoole_redis_server_arginfo.h │ │ ├── php_swoole_runtime.stub.php │ │ ├── php_swoole_runtime_arginfo.h │ │ ├── php_swoole_server.stub.php │ │ ├── php_swoole_server_arginfo.h │ │ ├── php_swoole_server_port.stub.php │ │ ├── php_swoole_server_port_arginfo.h │ │ ├── php_swoole_socket_coro.stub.php │ │ ├── php_swoole_socket_coro_arginfo.h │ │ ├── php_swoole_ssh2.stub.php │ │ ├── php_swoole_ssh2_arginfo.h │ │ ├── php_swoole_stdext.stub.php │ │ ├── php_swoole_stdext_arginfo.h │ │ ├── php_swoole_table.stub.php │ │ ├── php_swoole_table_arginfo.h │ │ ├── php_swoole_thread.stub.php │ │ ├── php_swoole_thread_arginfo.h │ │ ├── php_swoole_thread_arraylist.stub.php │ │ ├── php_swoole_thread_arraylist_arginfo.h │ │ ├── php_swoole_thread_atomic.stub.php │ │ ├── php_swoole_thread_atomic_arginfo.h │ │ ├── php_swoole_thread_barrier.stub.php │ │ ├── php_swoole_thread_barrier_arginfo.h │ │ ├── php_swoole_thread_lock.stub.php │ │ ├── php_swoole_thread_lock_arginfo.h │ │ ├── php_swoole_thread_map.stub.php │ │ ├── php_swoole_thread_map_arginfo.h │ │ ├── php_swoole_thread_queue.stub.php │ │ ├── php_swoole_thread_queue_arginfo.h │ │ ├── php_swoole_timer.stub.php │ │ ├── php_swoole_timer_arginfo.h │ │ ├── php_swoole_tracer.stub.php │ │ ├── php_swoole_tracer_arginfo.h │ │ ├── php_swoole_websocket.stub.php │ │ └── php_swoole_websocket_arginfo.h │ ├── swoole_admin_server.cc │ ├── swoole_async_coro.cc │ ├── swoole_atomic.cc │ ├── swoole_channel_coro.cc │ ├── swoole_client.cc │ ├── swoole_client_async.cc │ ├── swoole_client_coro.cc │ ├── swoole_coroutine.cc │ ├── swoole_coroutine_lock.cc │ ├── swoole_coroutine_scheduler.cc │ ├── swoole_coroutine_system.cc │ ├── swoole_curl.cc │ ├── swoole_curl_interface.h │ ├── swoole_event.cc │ ├── swoole_firebird.cc │ ├── swoole_http2_client_coro.cc │ ├── swoole_http2_server.cc │ ├── swoole_http_client_coro.cc │ ├── swoole_http_cookie.cc │ ├── swoole_http_request.cc │ ├── swoole_http_response.cc │ ├── swoole_http_server.cc │ ├── swoole_http_server_coro.cc │ ├── swoole_lock.cc │ ├── swoole_name_resolver.cc │ ├── swoole_odbc.cc │ ├── swoole_oracle.cc │ ├── swoole_pgsql.cc │ ├── swoole_process.cc │ ├── swoole_process_pool.cc │ ├── swoole_redis_server.cc │ ├── swoole_runtime.cc │ ├── swoole_server.cc │ ├── swoole_server_port.cc │ ├── swoole_socket_coro.cc │ ├── swoole_sqlite.cc │ ├── swoole_stdext.cc │ ├── swoole_table.cc │ ├── swoole_thread.cc │ ├── swoole_thread_arraylist.cc │ ├── swoole_thread_atomic.cc │ ├── swoole_thread_barrier.cc │ ├── swoole_thread_lock.cc │ ├── swoole_thread_map.cc │ ├── swoole_thread_queue.cc │ ├── swoole_timer.cc │ ├── swoole_tracer.cc │ └── swoole_websocket_server.cc ├── gdbinit ├── include/ │ ├── helper/ │ │ └── kqueue.h │ ├── swoole.h │ ├── swoole_api.h │ ├── swoole_asm_context.h │ ├── swoole_async.h │ ├── swoole_atomic.h │ ├── swoole_base64.h │ ├── swoole_buffer.h │ ├── swoole_channel.h │ ├── swoole_client.h │ ├── swoole_config.h │ ├── swoole_coroutine.h │ ├── swoole_coroutine_api.h │ ├── swoole_coroutine_channel.h │ ├── swoole_coroutine_context.h │ ├── swoole_coroutine_socket.h │ ├── swoole_coroutine_system.h │ ├── swoole_dtls.h │ ├── swoole_error.h │ ├── swoole_file.h │ ├── swoole_file_hook.h │ ├── swoole_hash.h │ ├── swoole_heap.h │ ├── swoole_http.h │ ├── swoole_http2.h │ ├── swoole_iouring.h │ ├── swoole_llhttp.h │ ├── swoole_lock.h │ ├── swoole_log.h │ ├── swoole_lru_cache.h │ ├── swoole_memory.h │ ├── swoole_message_bus.h │ ├── swoole_mime_type.h │ ├── swoole_mqtt.h │ ├── swoole_msg_queue.h │ ├── swoole_pipe.h │ ├── swoole_process_pool.h │ ├── swoole_protocol.h │ ├── swoole_proxy.h │ ├── swoole_reactor.h │ ├── swoole_redis.h │ ├── swoole_server.h │ ├── swoole_signal.h │ ├── swoole_socket.h │ ├── swoole_socket_hook.h │ ├── swoole_socket_impl.h │ ├── swoole_ssl.h │ ├── swoole_static_handler.h │ ├── swoole_string.h │ ├── swoole_table.h │ ├── swoole_thread.h │ ├── swoole_timer.h │ ├── swoole_uring_socket.h │ ├── swoole_util.h │ ├── swoole_version.h │ └── swoole_websocket.h ├── package.xml ├── php-cs-fix ├── php_swoole.h ├── php_swoole_api.h ├── run-core-tests.sh ├── scripts/ │ ├── .gitignore │ ├── clear.sh │ ├── code-format.sh │ ├── code-stats.sh │ ├── debug/ │ │ ├── swoole_info.php │ │ └── swoole_table_implements.php │ ├── docker-compile-with-iouring.sh │ ├── docker-compile-with-thread.sh │ ├── docker-compile.sh │ ├── docker-compose.yml │ ├── docker-iouring-route.sh │ ├── docker-route.sh │ ├── docker-thread-route.sh │ ├── format-changed-files.sh │ ├── install-deps-on-ubuntu.sh │ ├── install-liburing.sh │ ├── library.sh │ ├── make.sh │ ├── pecl-install.sh │ ├── rename.php │ ├── route.sh │ ├── run-tests.sh │ ├── simple-compile-on-github.sh │ └── simple-compile.sh ├── src/ │ ├── core/ │ │ ├── base.cc │ │ ├── base64.cc │ │ ├── buffer.cc │ │ ├── channel.cc │ │ ├── crc32.cc │ │ ├── error.cc │ │ ├── heap.cc │ │ ├── log.cc │ │ ├── misc.cc │ │ ├── string.cc │ │ └── timer.cc │ ├── coroutine/ │ │ ├── base.cc │ │ ├── channel.cc │ │ ├── context.cc │ │ ├── file.cc │ │ ├── file_lock.cc │ │ ├── hook.cc │ │ ├── iouring.cc │ │ ├── socket.cc │ │ ├── system.cc │ │ ├── thread_context.cc │ │ └── uring_socket.cc │ ├── lock/ │ │ ├── barrier.cc │ │ ├── coroutine_lock.cc │ │ ├── mutex.cc │ │ ├── rw_lock.cc │ │ └── spin_lock.cc │ ├── memory/ │ │ ├── fixed_pool.cc │ │ ├── global_memory.cc │ │ ├── ring_buffer.cc │ │ ├── shared_memory.cc │ │ └── table.cc │ ├── network/ │ │ ├── address.cc │ │ ├── client.cc │ │ ├── dns.cc │ │ ├── socket.cc │ │ └── stream.cc │ ├── os/ │ │ ├── async_thread.cc │ │ ├── base.cc │ │ ├── file.cc │ │ ├── msg_queue.cc │ │ ├── pipe.cc │ │ ├── process_pool.cc │ │ ├── sendfile.cc │ │ ├── signal.cc │ │ ├── timer.cc │ │ ├── unix_socket.cc │ │ └── wait.cc │ ├── protocol/ │ │ ├── base.cc │ │ ├── dtls.cc │ │ ├── http.cc │ │ ├── http2.cc │ │ ├── message_bus.cc │ │ ├── mime_type.cc │ │ ├── mqtt.cc │ │ ├── redis.cc │ │ ├── socks5.cc │ │ ├── ssl.cc │ │ └── websocket.cc │ ├── reactor/ │ │ ├── base.cc │ │ ├── epoll.cc │ │ ├── kqueue.cc │ │ └── poll.cc │ ├── server/ │ │ ├── base.cc │ │ ├── manager.cc │ │ ├── master.cc │ │ ├── port.cc │ │ ├── process.cc │ │ ├── reactor_process.cc │ │ ├── reactor_thread.cc │ │ ├── static_handler.cc │ │ ├── task_worker.cc │ │ ├── thread.cc │ │ └── worker.cc │ └── wrapper/ │ ├── event.cc │ ├── http.cc │ └── timer.cc ├── tests/ │ ├── CONTRIBUTION │ ├── README.md │ ├── clean │ ├── include/ │ │ ├── api/ │ │ │ ├── curl_multi.php │ │ │ ├── exit.php │ │ │ ├── http_server.php │ │ │ ├── http_test_cases.php │ │ │ ├── swoole_callback/ │ │ │ │ └── swoole_cannot_destroy_active_lambda_function.php │ │ │ ├── swoole_client/ │ │ │ │ ├── connect_timeout.php │ │ │ │ ├── connect_twice.php │ │ │ │ ├── http_get.php │ │ │ │ ├── opcode_client.php │ │ │ │ ├── simple_client.php │ │ │ │ └── socket_free.php │ │ │ ├── swoole_http_server/ │ │ │ │ ├── http_server.php │ │ │ │ ├── http_server_without_response.php │ │ │ │ ├── simple_http_server.php │ │ │ │ └── simple_https_server.php │ │ │ ├── swoole_server/ │ │ │ │ ├── TestServer.php │ │ │ │ ├── multi_protocol_server.php │ │ │ │ ├── opcode_server.php │ │ │ │ ├── reconnect_fail/ │ │ │ │ │ ├── tcp_client.php │ │ │ │ │ └── tcp_serv.php │ │ │ │ ├── server_manager_process_exit.php │ │ │ │ ├── server_send_fast_recv_slow.php │ │ │ │ ├── simple_server.php │ │ │ │ ├── simple_tcp_server.php │ │ │ │ ├── simple_udp_server.php │ │ │ │ ├── tcp_serv.php │ │ │ │ ├── tcp_task_server.php │ │ │ │ └── testsendfile.txt │ │ │ ├── swoole_thread/ │ │ │ │ ├── putenv.php │ │ │ │ └── sleep.php │ │ │ ├── swoole_timer/ │ │ │ │ ├── accurate_test.php │ │ │ │ ├── fixRate_vs_fixDelay.php │ │ │ │ ├── invalid_args.php │ │ │ │ ├── multi_timer.php │ │ │ │ └── register_shutdown_priority.php │ │ │ ├── swoole_websocket_server/ │ │ │ │ ├── send_large_request_data.php │ │ │ │ ├── send_small_request_data.php │ │ │ │ ├── swoole_websocket_server.php │ │ │ │ └── websocket_client.php │ │ │ ├── syntax_error.txt │ │ │ ├── tcp_server.php │ │ │ └── test_classes/ │ │ │ ├── A.php │ │ │ ├── A2.php │ │ │ └── B.php │ │ ├── bootstrap.php │ │ ├── config.php │ │ ├── functions.php │ │ ├── lib/ │ │ │ ├── composer.json │ │ │ └── src/ │ │ │ ├── Assert.php │ │ │ ├── ChildProcess.php │ │ │ ├── CoServer.php │ │ │ ├── CurlManager.php │ │ │ ├── DbWrapper.php │ │ │ ├── LengthServer.php │ │ │ ├── MQTT/ │ │ │ │ └── Helper.php │ │ │ ├── MysqlPool.php │ │ │ ├── ProcessManager.php │ │ │ ├── RandStr.php │ │ │ ├── Redis/ │ │ │ │ ├── DBConnectException.php │ │ │ │ ├── Lock.php │ │ │ │ ├── Redis.php │ │ │ │ └── SQLPool.php │ │ │ ├── Samtleben/ │ │ │ │ └── WebsocketClient.php │ │ │ ├── ServerManager.php │ │ │ ├── TcpStat.php │ │ │ ├── ThreadManager.php │ │ │ ├── WaitRef.php │ │ │ └── responder/ │ │ │ └── get.php │ │ ├── macos/ │ │ │ └── phpstorm.py │ │ ├── skipif.inc │ │ └── ssl_certs/ │ │ ├── ca-cert.pem │ │ ├── ca-key.pem │ │ ├── ca-req.csr │ │ ├── ca.crt │ │ ├── ca.csr │ │ ├── ca.key │ │ ├── ca.srl │ │ ├── client-cert.pem │ │ ├── client-expired.crt │ │ ├── client-expired.csr │ │ ├── client-expired.key │ │ ├── client-key.pem │ │ ├── client-req.csr │ │ ├── client.crt │ │ ├── client.csr │ │ ├── client.key │ │ ├── client.pem │ │ ├── dhparams.pem │ │ ├── mosquitto.org.crt │ │ ├── passwd.crt │ │ ├── passwd_key.pem │ │ ├── server-cert.pem │ │ ├── server-key.pem │ │ ├── server-req.csr │ │ ├── server.crt │ │ ├── server.csr │ │ ├── server.key │ │ ├── server.pem │ │ ├── sni_server_ca.pem │ │ ├── sni_server_cs.pem │ │ ├── sni_server_cs_cert.pem │ │ ├── sni_server_cs_key.pem │ │ ├── sni_server_uk.pem │ │ ├── sni_server_uk_cert.pem │ │ ├── sni_server_uk_key.pem │ │ ├── sni_server_us.pem │ │ ├── sni_server_us_cert.pem │ │ └── sni_server_us_key.pem │ ├── init │ ├── new │ ├── pgsql.sql │ ├── run-tests │ ├── start.sh │ ├── swoole_atomic/ │ │ ├── atomic.phpt │ │ ├── dtor_in_child.phpt │ │ ├── multi_wakeup.phpt │ │ ├── wait.phpt │ │ ├── wait_and_wakeup.phpt │ │ └── wait_ex.phpt │ ├── swoole_channel_coro/ │ │ ├── 1.phpt │ │ ├── 10.phpt │ │ ├── 2.phpt │ │ ├── 3.phpt │ │ ├── 4.phpt │ │ ├── 5.phpt │ │ ├── 6.phpt │ │ ├── 7.phpt │ │ ├── 8.phpt │ │ ├── 9.phpt │ │ ├── basic.phpt │ │ ├── benchmark.phpt │ │ ├── blocking_timeout.phpt │ │ ├── bug_1947.phpt │ │ ├── bug_clear_timer.phpt │ │ ├── chan_select_timeout.phpt │ │ ├── chan_stats.phpt │ │ ├── close.phpt │ │ ├── coro_wait.phpt │ │ ├── discard.phpt │ │ ├── fibonacci.phpt │ │ ├── http2.phpt │ │ ├── hybird_chan.phpt │ │ ├── hybird_chan2.phpt │ │ ├── hybird_chan3.phpt │ │ ├── lock.phpt │ │ ├── no_ctor.phpt │ │ ├── pool.phpt │ │ ├── pop_after_close.phpt │ │ ├── pop_close1.phpt │ │ ├── pop_timeout1.phpt │ │ ├── pop_timeout2.phpt │ │ ├── pop_timeout3.phpt │ │ ├── pop_timeout4.phpt │ │ ├── pop_timeout5.phpt │ │ ├── pop_timeout6.phpt │ │ ├── pop_timeout7.phpt │ │ ├── pop_timeout8.phpt │ │ ├── push_close1.phpt │ │ ├── push_timeout1.phpt │ │ ├── push_timeout2.phpt │ │ ├── push_timeout3.phpt │ │ ├── push_timeout4.phpt │ │ └── type.phpt │ ├── swoole_client_async/ │ │ ├── base.phpt │ │ ├── big_package_memory_leak.phpt │ │ ├── buffer_full.phpt │ │ ├── connect_dns.phpt │ │ ├── connect_refuse.phpt │ │ ├── connect_refuse_udg.phpt │ │ ├── connect_refuse_unix.phpt │ │ ├── connect_timeout.phpt │ │ ├── connect_twice.phpt │ │ ├── enableSSL.phpt │ │ ├── enableSSL_bad_callback.phpt │ │ ├── enableSSL_before_connect.phpt │ │ ├── eof.phpt │ │ ├── eof_close.phpt │ │ ├── getSocket_bug.phpt │ │ ├── getpeername.phpt │ │ ├── getsockname.phpt │ │ ├── length_protocol.phpt │ │ ├── length_protocol_func.phpt │ │ ├── port_invalid.phpt │ │ ├── sendfile.phpt │ │ └── sleep_wake.phpt │ ├── swoole_client_coro/ │ │ ├── bug_2346.phpt │ │ ├── close.phpt │ │ ├── close_in_other_co.phpt │ │ ├── close_resume.phpt │ │ ├── close_socket_property.phpt │ │ ├── close_twice.phpt │ │ ├── connect_dns_timeout.phpt │ │ ├── connect_timeout.phpt │ │ ├── connect_with_dns.phpt │ │ ├── dtls.phpt │ │ ├── enableSSL.phpt │ │ ├── eof.phpt │ │ ├── eof_02.phpt │ │ ├── eof_03.phpt │ │ ├── eof_04.phpt │ │ ├── export_socket.phpt │ │ ├── fixed_package.phpt │ │ ├── getpeername.phpt │ │ ├── getsockname.phpt │ │ ├── isConnected.phpt │ │ ├── length_01.phpt │ │ ├── length_02.phpt │ │ ├── length_03.phpt │ │ ├── length_04.phpt │ │ ├── length_protocol_func.phpt │ │ ├── length_types.phpt │ │ ├── read_and_write.phpt │ │ ├── reconnect.phpt │ │ ├── reconnect_2.phpt │ │ ├── reconnect_3.phpt │ │ ├── recv_after_close.phpt │ │ ├── recv_bad_packet.phpt │ │ ├── recv_timeout.phpt │ │ ├── recv_timeout2.phpt │ │ ├── recvfrom.phpt │ │ ├── recvfrom_2.phpt │ │ ├── recvfrom_timeout.phpt │ │ ├── recvfrom_timeout2.phpt │ │ ├── send_big.phpt │ │ ├── sendfile.phpt │ │ ├── sendto.phpt │ │ ├── ssl.phpt │ │ ├── ssl_verify.phpt │ │ ├── tcp_client.phpt │ │ ├── tcp_nodelay.phpt │ │ ├── timeout.phpt │ │ ├── udp_client.phpt │ │ ├── udp_recv_failed.phpt │ │ ├── unsock_dgram.phpt │ │ └── unsock_stream.phpt │ ├── swoole_client_sync/ │ │ ├── bind_address.phpt │ │ ├── connect_1.phpt │ │ ├── connect_2.phpt │ │ ├── connect_3.phpt │ │ ├── enableSSL.phpt │ │ ├── enableSSL_2.phpt │ │ ├── eof.phpt │ │ ├── eof_close.phpt │ │ ├── eof_timeout.phpt │ │ ├── http_proxy.phpt │ │ ├── keep1.phpt │ │ ├── keep2.phpt │ │ ├── keep3.phpt │ │ ├── keep4.phpt │ │ ├── keep5.phpt │ │ ├── keep6.phpt │ │ ├── length_protocol.phpt │ │ ├── length_protocol_02.phpt │ │ ├── length_protocol_func.phpt │ │ ├── recv_in_task.phpt │ │ ├── recv_timeout.phpt │ │ ├── recv_with_open_eof_check.phpt │ │ ├── select.phpt │ │ ├── select_null.phpt │ │ ├── send_recv.phpt │ │ ├── sendfile.phpt │ │ ├── sendto.phpt │ │ ├── socks5_proxy.phpt │ │ ├── ssl_recv_timeout.phpt │ │ ├── sync_send_recv.phpt │ │ ├── udg_send_timeout.phpt │ │ └── udp_client_sendto.phpt │ ├── swoole_coroutine/ │ │ ├── after_start_server_1.phpt │ │ ├── all_asleep.phpt │ │ ├── array_walk.phpt │ │ ├── array_walk_deep.phpt │ │ ├── async_callback/ │ │ │ ├── event_cycle.phpt │ │ │ ├── signal.phpt │ │ │ └── timer.phpt │ │ ├── autoload.phpt │ │ ├── autoload_not_found.phpt │ │ ├── autoload_not_found_not_in_coroutine.phpt │ │ ├── autoload_not_in_coroutine.phpt │ │ ├── bailout/ │ │ │ ├── co_redis_in_shutdown_function.phpt │ │ │ ├── error.phpt │ │ │ ├── error_in.phpt │ │ │ ├── error_internal.phpt │ │ │ ├── error_internal2.phpt │ │ │ ├── error_out.phpt │ │ │ └── exit.phpt │ │ ├── before_create_server_1.phpt │ │ ├── before_create_server_2.phpt │ │ ├── before_create_server_3.phpt │ │ ├── bug_2387.phpt │ │ ├── bug_5699.phpt │ │ ├── c_stack_size.phpt │ │ ├── call_not_exists_func.phpt │ │ ├── call_user_func_array.phpt │ │ ├── call_user_func_array2.phpt │ │ ├── call_with_args.phpt │ │ ├── callback.phpt │ │ ├── cancel/ │ │ │ ├── channel_pop.phpt │ │ │ ├── channel_push.phpt │ │ │ ├── error.phpt │ │ │ ├── gethostbyname.phpt │ │ │ ├── kill.phpt │ │ │ ├── sleep.phpt │ │ │ ├── socket.phpt │ │ │ ├── suspend.phpt │ │ │ ├── throw_exception.phpt │ │ │ ├── wait.phpt │ │ │ ├── wait_event.phpt │ │ │ └── wait_signal.phpt │ │ ├── check.phpt │ │ ├── cid.phpt │ │ ├── create_after_rshutdown.phpt │ │ ├── current.phpt │ │ ├── dead_lock.phpt │ │ ├── defer/ │ │ │ ├── defer.phpt │ │ │ ├── defer_close.phpt │ │ │ ├── defer_exception.phpt │ │ │ └── defer_in_try.phpt │ │ ├── destruct/ │ │ │ ├── destruct1.phpt │ │ │ ├── destruct2.phpt │ │ │ └── destruct3.phpt │ │ ├── dnslookup_1.phpt │ │ ├── dnslookup_2.phpt │ │ ├── dnslookup_3.phpt │ │ ├── dnslookup_4.phpt │ │ ├── dnslookup_5.phpt │ │ ├── dnslookup_ipv6.phpt │ │ ├── dnslookup_query_hosts.phpt │ │ ├── empty.phpt │ │ ├── eval.phpt │ │ ├── exception/ │ │ │ ├── core_error.phpt │ │ │ ├── defer1.phpt │ │ │ ├── defer2.phpt │ │ │ ├── dtor.phpt │ │ │ ├── empty.phpt │ │ │ ├── error.phpt │ │ │ ├── error1.phpt │ │ │ ├── error2.phpt │ │ │ ├── error3.phpt │ │ │ ├── fatal_error.phpt │ │ │ ├── nested.phpt │ │ │ ├── yield.phpt │ │ │ └── yield1.phpt │ │ ├── exception.phpt │ │ ├── execute_time.phpt │ │ ├── exists.phpt │ │ ├── exit.phpt │ │ ├── exit_84.phpt │ │ ├── exit_exception_backtrace.phpt │ │ ├── exit_exception_backtrace_84.phpt │ │ ├── fatal_error.phpt │ │ ├── forbidden_case/ │ │ │ ├── array_map.phpt │ │ │ ├── call_user.phpt │ │ │ └── invoke.phpt │ │ ├── getContext.phpt │ │ ├── getElasped.phpt │ │ ├── getPcid.phpt │ │ ├── getPcid_by_random_cid.phpt.phpt │ │ ├── gethostbyname.phpt │ │ ├── iterator.phpt │ │ ├── join/ │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5.phpt │ │ │ ├── 6.phpt │ │ │ ├── 7.phpt │ │ │ └── 8.phpt │ │ ├── kernel_coroutine.phpt │ │ ├── list_and_backtrace.phpt │ │ ├── max_num.phpt │ │ ├── max_num_limit.phpt │ │ ├── nested1.phpt │ │ ├── nested2.phpt │ │ ├── nested3.phpt │ │ ├── nested_empty.phpt │ │ ├── nested_uid.phpt │ │ ├── new_process.phpt │ │ ├── new_server.phpt │ │ ├── no_inline_func.phpt │ │ ├── output/ │ │ │ ├── concurrency.phpt │ │ │ ├── create.phpt │ │ │ ├── in_nested_co.phpt │ │ │ ├── ob_main.phpt │ │ │ └── output_control.phpt │ │ ├── parallel1.phpt │ │ ├── parallel2.phpt │ │ ├── parallel3.phpt │ │ ├── pdo_error_handing.phpt │ │ ├── private_access.phpt │ │ ├── resume_loop.phpt │ │ ├── scheduler.phpt │ │ ├── signal_listener.phpt │ │ ├── stats.phpt │ │ ├── timeout.phpt │ │ ├── use_process.phpt │ │ ├── user_coroutine.phpt │ │ └── user_coroutine_2.phpt │ ├── swoole_coroutine_lock/ │ │ ├── lock.phpt │ │ ├── trylock.phpt │ │ └── trylock2.phpt │ ├── swoole_coroutine_scheduler/ │ │ ├── getOptions.phpt │ │ ├── hook_flags.phpt │ │ ├── hook_flags_2.phpt │ │ ├── parallel.phpt │ │ ├── preemptive/ │ │ │ ├── disable.phpt │ │ │ ├── disable2.phpt │ │ │ ├── do-while.phpt │ │ │ ├── do-while2.phpt │ │ │ ├── do-while3.phpt │ │ │ ├── for.phpt │ │ │ ├── for2.phpt │ │ │ ├── goto.phpt │ │ │ ├── goto2.phpt │ │ │ ├── timer.phpt │ │ │ ├── while.phpt │ │ │ ├── while2.phpt │ │ │ └── while3.phpt │ │ ├── repeat.phpt │ │ ├── resume1.phpt │ │ ├── resume2.phpt │ │ ├── resume3.phpt │ │ ├── resume4.phpt │ │ ├── resume5.phpt │ │ ├── resume6.phpt │ │ ├── start.phpt │ │ ├── start_in_server_shutdown.phpt │ │ └── start_in_server_worker_stop.phpt │ ├── swoole_coroutine_system/ │ │ ├── aio_thread_num.phpt │ │ ├── getaddrinfo.phpt │ │ ├── getaddrinfo_timeout.phpt │ │ ├── gethostbyname.phpt │ │ ├── gethostbyname_ipv6.phpt │ │ ├── gethostbyname_timeout.phpt │ │ ├── readfile.phpt │ │ ├── sleep.phpt │ │ ├── wait.phpt │ │ ├── waitEvent.phpt │ │ ├── waitPid.phpt │ │ ├── waitSignal.phpt │ │ ├── waitSignal_2.phpt │ │ ├── writefile.phpt │ │ └── writefile_append.phpt │ ├── swoole_coroutine_util/ │ │ ├── dns_lookup.phpt │ │ ├── exec.phpt │ │ ├── exec_sleep.phpt │ │ ├── fgets.phpt │ │ ├── fread.phpt │ │ ├── fwrite.phpt │ │ ├── list_coroutine.phpt │ │ └── task_worker.phpt │ ├── swoole_coroutine_wait_group/ │ │ ├── base.phpt │ │ ├── defer.phpt │ │ ├── empty.phpt │ │ └── logic.phpt │ ├── swoole_curl/ │ │ ├── abnormal_response/ │ │ │ ├── 1.phpt │ │ │ └── 2.phpt │ │ ├── basic/ │ │ │ ├── 1.phpt │ │ │ ├── 10.phpt │ │ │ ├── 11.phpt │ │ │ ├── 12.phpt │ │ │ ├── 13.phpt │ │ │ ├── 14.phpt │ │ │ ├── 15.phpt │ │ │ ├── 19.phpt │ │ │ ├── 2.phpt │ │ │ ├── 20.phpt │ │ │ ├── 21.phpt │ │ │ ├── 22.phpt │ │ │ ├── 23.phpt │ │ │ ├── 24.phpt │ │ │ ├── 25.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5.phpt │ │ │ ├── 7.phpt │ │ │ ├── 8.phpt │ │ │ └── 9.phpt │ │ ├── cancel.phpt │ │ ├── close_before_resume.phpt │ │ ├── concurrent.phpt │ │ ├── coro_read.phpt │ │ ├── coro_write.phpt │ │ ├── coro_write_header.phpt │ │ ├── error.phpt │ │ ├── event_exit.phpt │ │ ├── exec_twice.phpt │ │ ├── fatal_error_in_callback.phpt │ │ ├── ftp.phpt │ │ ├── guzzle/ │ │ │ ├── cancel.phpt │ │ │ ├── cannot_cancel_finished.phpt │ │ │ ├── cookie.phpt │ │ │ ├── promise.phpt │ │ │ ├── request_async.phpt │ │ │ ├── request_on_stats.phpt │ │ │ └── send_async.phpt │ │ ├── guzzle.phpt │ │ ├── https.phpt │ │ ├── keepalive.phpt │ │ ├── multi/ │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5.phpt │ │ │ ├── 6.phpt │ │ │ ├── add_after_easy_exec.phpt │ │ │ ├── bug4393.phpt │ │ │ ├── bug48203_multi.phpt │ │ │ ├── bug67643.phpt │ │ │ ├── bug71523.phpt │ │ │ ├── bug76675.phpt │ │ │ ├── bug77535.phpt │ │ │ ├── bug77946.phpt │ │ │ ├── close.phpt │ │ │ ├── curl_basic_018.phpt │ │ │ ├── curl_copy_handle_variation4.phpt │ │ │ ├── curl_int_cast.phpt │ │ │ ├── curl_multi_close_basic.phpt │ │ │ ├── curl_multi_close_basic001.phpt │ │ │ ├── curl_multi_close_reference.phpt │ │ │ ├── curl_multi_errno_strerror_001.phpt │ │ │ ├── curl_multi_getcontent_basic3.phpt │ │ │ ├── curl_multi_info_read.phpt │ │ │ ├── curl_multi_init_basic.phpt │ │ │ ├── curl_multi_segfault.phpt │ │ │ ├── curl_multi_select_basic1.phpt │ │ │ ├── curl_multi_setopt_basic001.phpt │ │ │ ├── dtor.phpt │ │ │ ├── multiple_binding.phpt │ │ │ ├── no_hook.phpt │ │ │ ├── select_cancel.phpt │ │ │ ├── select_timeout.phpt │ │ │ └── select_twice.phpt │ │ ├── non_exclusive.phpt │ │ ├── setopt/ │ │ │ ├── 1.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5_skip.phpt │ │ │ ├── filetime_1.phpt │ │ │ ├── filetime_2.phpt │ │ │ ├── header_out.phpt │ │ │ ├── infile.phpt │ │ │ └── nobody.phpt │ │ ├── share/ │ │ │ ├── 1.phpt │ │ │ └── 5.phpt │ │ ├── sleep.phpt │ │ ├── ssl/ │ │ │ └── version.phpt │ │ ├── symfony-noco.phpt │ │ ├── symfony.phpt │ │ ├── template │ │ ├── timer_coredump.phpt │ │ ├── undefined_behavior/ │ │ │ ├── 0.phpt │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5.phpt │ │ │ ├── 6.phpt │ │ │ ├── 7.phpt │ │ │ └── 8.phpt │ │ ├── upload/ │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ ├── 3.phpt │ │ │ ├── curl_testdata1.txt │ │ │ └── curl_testdata2.txt │ │ └── yield_in_callback.phpt │ ├── swoole_event/ │ │ ├── add_after_server_start.phpt │ │ ├── cycle.phpt │ │ ├── defer.phpt │ │ ├── defer_with_sleep.phpt │ │ ├── defer_without_io.phpt │ │ ├── del.phpt │ │ ├── del_after_close.phpt │ │ ├── deprecated_event_wait.phpt │ │ ├── dispatch.phpt │ │ ├── function_alias.phpt │ │ ├── isset.phpt │ │ ├── kqueue.phpt │ │ ├── rshutdown.phpt │ │ ├── set.phpt │ │ ├── simple.phpt │ │ ├── sockets.phpt │ │ ├── sync_client_1.phpt │ │ ├── sync_client_2.phpt │ │ ├── wait.phpt │ │ └── write.phpt │ ├── swoole_feature/ │ │ ├── cross_close/ │ │ │ ├── client.phpt │ │ │ ├── client_by_server.phpt │ │ │ ├── full_duplex.phpt │ │ │ ├── full_duplex_by_server.phpt │ │ │ ├── http.phpt │ │ │ ├── http_by_server.phpt │ │ │ ├── php_stream_full_duplex.phpt │ │ │ ├── php_stream_full_duplex_by_server.phpt │ │ │ ├── redis.phpt │ │ │ ├── redis_by_server.phpt │ │ │ ├── stream.phpt │ │ │ └── stream_by_server.phpt │ │ └── full_duplex/ │ │ ├── client.phpt │ │ ├── socket.phpt │ │ ├── socket_ssl.phpt │ │ └── websocket.phpt │ ├── swoole_ftp/ │ │ ├── 001.phpt │ │ ├── 002.phpt │ │ ├── 003.phpt │ │ ├── 004.phpt │ │ ├── 005.phpt │ │ ├── 007.phpt │ │ ├── bug27809.phpt │ │ ├── bug37799.phpt │ │ ├── bug39458-2.phpt │ │ ├── bug39458.phpt │ │ ├── bug39583-2.phpt │ │ ├── bug39583.phpt │ │ ├── bug7216-2.phpt │ │ ├── bug7216.phpt │ │ ├── bug79100.phpt │ │ ├── bug80901.phpt │ │ ├── cert.pem │ │ ├── dead-resource.phpt │ │ ├── filesize_large.phpt │ │ ├── ftp_alloc_basic1.phpt │ │ ├── ftp_alloc_basic2.phpt │ │ ├── ftp_append.phpt │ │ ├── ftp_chmod_basic.phpt │ │ ├── ftp_connect_001.phpt │ │ ├── ftp_constructor.phpt │ │ ├── ftp_delete.phpt │ │ ├── ftp_exec_basic.phpt │ │ ├── ftp_fget_basic.phpt │ │ ├── ftp_fget_basic1.phpt │ │ ├── ftp_fget_basic2.phpt │ │ ├── ftp_fget_basic3.phpt │ │ ├── ftp_fput.phpt │ │ ├── ftp_fput_ascii_over_4_kib.phpt │ │ ├── ftp_get_basic.phpt │ │ ├── ftp_get_option.phpt │ │ ├── ftp_mdtm_basic.phpt │ │ ├── ftp_mlsd.phpt │ │ ├── ftp_mlsd_empty_directory.phpt │ │ ├── ftp_mlsd_missing_directory.phpt │ │ ├── ftp_nb_continue.phpt │ │ ├── ftp_nb_fget_basic1.phpt │ │ ├── ftp_nb_fget_basic2.phpt │ │ ├── ftp_nb_fget_basic3.phpt │ │ ├── ftp_nb_fput.phpt │ │ ├── ftp_nb_get_large.phpt │ │ ├── ftp_nb_put.phpt │ │ ├── ftp_pasv.phpt │ │ ├── ftp_rawlist_basic1.phpt │ │ ├── ftp_rawlist_basic2.phpt │ │ ├── ftp_rename_basic1.phpt │ │ ├── ftp_rmdir_basic.phpt │ │ ├── ftp_set_option.phpt │ │ ├── ftp_set_option_errors.phpt │ │ ├── ftp_site_basic.phpt │ │ ├── ftp_ssl_connect_error.phpt │ │ ├── gh10521.phpt │ │ ├── gh10562.phpt │ │ └── server.inc │ ├── swoole_function/ │ │ ├── substr_json_decode.phpt │ │ ├── substr_unserialize.phpt │ │ ├── swoole_clear_dns_cache.phpt │ │ ├── swoole_cpu_num.phpt │ │ ├── swoole_error_log.phpt │ │ ├── swoole_get_local_ip.phpt │ │ ├── swoole_get_local_mac.phpt │ │ ├── swoole_set_process_name.phpt │ │ ├── swoole_strerror.phpt │ │ └── swoole_version.phpt │ ├── swoole_global/ │ │ ├── channel_construct_check.phpt │ │ ├── closed_stdout.phpt │ │ ├── create_deny.phpt │ │ ├── function_alias.phpt │ │ ├── serialize_deny.phpt │ │ ├── socket_construct_check.phpt │ │ ├── too_many_objects.phpt │ │ ├── unset_deny.phpt │ │ ├── unset_property_01.phpt │ │ ├── unset_property_02.phpt │ │ └── unset_property_03.phpt │ ├── swoole_http2_client_coro/ │ │ ├── bug_5127.phpt │ │ ├── connect_twice.phpt │ │ ├── cookies.phpt │ │ ├── error.phpt │ │ ├── goaway.phpt │ │ ├── headers.phpt │ │ ├── host.phpt │ │ ├── http_proxy.phpt │ │ ├── huge_headers.phpt │ │ ├── issues_2374.phpt │ │ ├── max-frame-size.phpt │ │ ├── multi.phpt │ │ ├── no-gzip.phpt │ │ ├── number.phpt │ │ ├── ping.phpt │ │ ├── pipeline.phpt │ │ ├── post.phpt │ │ ├── send-cookies.phpt │ │ ├── send_only_bug.phpt │ │ ├── set-cookies.phpt │ │ ├── sock_type_unix.phpt │ │ └── wrong_headers.phpt │ ├── swoole_http2_server/ │ │ ├── 413.phpt │ │ ├── big_data.phpt │ │ ├── compression.phpt │ │ ├── compression_types.phpt │ │ ├── getMethod.phpt │ │ ├── goaway.phpt │ │ ├── http2_headers.phpt │ │ ├── issue_4365.phpt │ │ ├── max_concurrency.phpt │ │ ├── nghttp2_big_data.phpt │ │ ├── no_compression.phpt │ │ ├── ping.phpt │ │ ├── sendfile.phpt │ │ ├── sendfile_content_type.phpt │ │ ├── sendfile_set_content_type.phpt │ │ ├── server_addr.phpt │ │ ├── static_handler.phpt │ │ ├── streaming.phpt │ │ ├── streaming_2.phpt │ │ ├── trailer.phpt │ │ └── worker_max_concurrency.phpt │ ├── swoole_http2_server_coro/ │ │ └── cookies.phpt │ ├── swoole_http_client_coro/ │ │ ├── 204.phpt │ │ ├── addData.phpt │ │ ├── alias.phpt │ │ ├── another_coroutine.phpt │ │ ├── auto_reconnect.phpt │ │ ├── bind_address.phpt │ │ ├── bug_2661.phpt │ │ ├── bug_3118.phpt │ │ ├── compression_with_big_data.phpt │ │ ├── connect_timeout.phpt │ │ ├── connection_close.phpt │ │ ├── construct_failed.phpt │ │ ├── cookies_set_bug.phpt │ │ ├── defer.phpt │ │ ├── defer_02.phpt │ │ ├── disable_keep_alive.phpt │ │ ├── download.phpt │ │ ├── download_302.phpt │ │ ├── download_failed.phpt │ │ ├── download_filename_bug.phpt │ │ ├── duplicate_header.phpt │ │ ├── error_handler.phpt │ │ ├── get.phpt │ │ ├── get_header_out_after_close.phpt │ │ ├── get_twice.phpt │ │ ├── get_twice_keepalive.phpt │ │ ├── get_without_content_length.phpt │ │ ├── h2c_upgrade.phpt │ │ ├── head_method.phpt │ │ ├── host.phpt │ │ ├── http_chunk.phpt │ │ ├── http_proxy.phpt │ │ ├── http_proxy_443.phpt │ │ ├── http_proxy_with_host_port.phpt │ │ ├── http_upload_big.phpt │ │ ├── https.phpt │ │ ├── https_upload_big.phpt │ │ ├── issue_2664.phpt │ │ ├── keep_alive.phpt │ │ ├── long_domain.phpt │ │ ├── lowercase_header.phpt │ │ ├── multi.phpt │ │ ├── multi_and_reuse.phpt │ │ ├── parser.phpt │ │ ├── post_array.phpt │ │ ├── reconnect_but_failed.phpt │ │ ├── recv_slow_timeout.phpt │ │ ├── recv_timeout.phpt │ │ ├── set_basic_auth.phpt │ │ ├── slow_server.phpt │ │ ├── socks5_proxy.phpt │ │ ├── socks5_proxy_ipv6.phpt │ │ ├── ssl.phpt │ │ ├── ssl_host_name.phpt │ │ ├── ssl_verify_peer_1.phpt │ │ ├── ssl_verify_peer_2.phpt │ │ ├── timeout_before_connect.phpt │ │ ├── timeout_when_recv.phpt │ │ ├── unixsocket.phpt │ │ ├── upload.phpt │ │ ├── upload_huge.phpt │ │ ├── upload_with_null_args.phpt │ │ ├── websocket/ │ │ │ ├── 1.phpt │ │ │ ├── auto_pong.phpt │ │ │ ├── bug_01.phpt │ │ │ ├── bug_02.phpt │ │ │ ├── close_socket.phpt │ │ │ ├── continue_frame_finish_flag.phpt │ │ │ ├── continue_frame_finish_flag2.phpt │ │ │ ├── continue_frames.phpt │ │ │ ├── continue_frames2.phpt │ │ │ ├── continue_frames3.phpt │ │ │ ├── continue_frames4.phpt │ │ │ ├── control_frame_compress.phpt │ │ │ ├── control_frame_fragmented.phpt │ │ │ ├── disconnect.phpt │ │ │ ├── open_websocket_frame.phpt │ │ │ ├── ping.phpt │ │ │ ├── priority.phpt │ │ │ ├── priority1.phpt │ │ │ ├── send_more_continue_frame.phpt │ │ │ ├── send_more_continue_frame2.phpt │ │ │ ├── server_push_first.phpt │ │ │ ├── ssl_1.phpt │ │ │ ├── ssl_2.phpt │ │ │ ├── timeout.phpt │ │ │ └── upgrade_after_get.phpt │ │ ├── write_func_1.phpt │ │ └── write_func_2.phpt │ ├── swoole_http_server/ │ │ ├── 0.phpt │ │ ├── 100-continue.phpt │ │ ├── If_Modified_Since.phpt │ │ ├── accept_encoding.phpt │ │ ├── buffer_output_size.phpt │ │ ├── bug_2368.phpt │ │ ├── bug_2444.phpt │ │ ├── bug_2608.phpt │ │ ├── bug_2751.phpt │ │ ├── bug_2786.phpt │ │ ├── bug_2947.phpt │ │ ├── bug_2988.phpt │ │ ├── bug_4261.phpt │ │ ├── bug_4857.phpt │ │ ├── bug_5107.phpt │ │ ├── bug_5114.phpt │ │ ├── bug_5146.phpt │ │ ├── bug_5186.phpt │ │ ├── bug_6007.phpt │ │ ├── bug_compression_level.phpt │ │ ├── bug_get_request_data_after_end.phpt │ │ ├── callback_new_obj_method.phpt │ │ ├── callback_new_static_method.phpt │ │ ├── callback_string.phpt │ │ ├── callback_with_internal_function.phpt │ │ ├── callback_with_private.phpt │ │ ├── callback_with_protected.phpt │ │ ├── chunk.phpt │ │ ├── chunk_with_end_data.phpt │ │ ├── chunked_pipeline_request.phpt │ │ ├── client_ca.phpt │ │ ├── client_compress.phpt │ │ ├── co_switching.phpt │ │ ├── compression.phpt │ │ ├── compression_min_length.phpt │ │ ├── compression_types.phpt │ │ ├── cookieAlias.phpt │ │ ├── cookie_delete.phpt │ │ ├── cookie_samesite.phpt │ │ ├── cookie_vs_rawcookie.phpt │ │ ├── cookies.phpt │ │ ├── cookies_parse.phpt │ │ ├── create_request.phpt │ │ ├── data_parse.phpt │ │ ├── disable_compression.phpt │ │ ├── disable_coroutine.phpt │ │ ├── dispatch_mode_7.phpt │ │ ├── duplicate_header.phpt │ │ ├── enable_coroutine.phpt │ │ ├── error_1203.phpt │ │ ├── error_413.phpt │ │ ├── event_stream.phpt │ │ ├── event_stream2.phpt │ │ ├── form_data_1.phpt │ │ ├── form_data_with_charset.phpt │ │ ├── head_method.phpt │ │ ├── headers_sent.phpt │ │ ├── headers_sent_coroutine.phpt │ │ ├── http_autoindex.phpt │ │ ├── http_index_files.phpt │ │ ├── http_index_files_autoindex.phpt │ │ ├── https.phpt │ │ ├── issue_2360.phpt │ │ ├── json_encode.phpt │ │ ├── json_encode2.phpt │ │ ├── large_url.phpt │ │ ├── max-age.phpt │ │ ├── max_concurrency.phpt │ │ ├── max_coro_num.phpt │ │ ├── max_execution_time.phpt │ │ ├── max_input_vars.phpt │ │ ├── mixed_server.phpt │ │ ├── new_cookie.phpt │ │ ├── no_compression.phpt │ │ ├── numeric_header_name.phpt │ │ ├── objectCookie.phpt │ │ ├── objectCookieMemory.phpt │ │ ├── octane_bug_651.phpt │ │ ├── pipeline.phpt │ │ ├── post.phpt │ │ ├── purge_method.phpt │ │ ├── range.phpt │ │ ├── range2.phpt │ │ ├── rawContent.phpt │ │ ├── rawCookie.phpt │ │ ├── redirect.phpt │ │ ├── reset_concurrency_with_base.phpt │ │ ├── reset_concurrency_with_process.phpt │ │ ├── response_create.phpt │ │ ├── send_500_to_client.phpt │ │ ├── send_empty_file.phpt │ │ ├── send_yield.phpt │ │ ├── sendfile.phpt │ │ ├── sendfile_client_reset.phpt │ │ ├── sendfile_dir.phpt │ │ ├── sendfile_link.phpt │ │ ├── sendfile_no_keepalive.phpt │ │ ├── sendfile_with_dispatch_mode_7.phpt │ │ ├── sendfile_with_ssl.phpt │ │ ├── server_addr.phpt │ │ ├── server_addr2.phpt │ │ ├── set_content_length.phpt │ │ ├── shutdown_in_event_worker.phpt │ │ ├── shutdown_in_task_worker.phpt │ │ ├── slow_client.phpt │ │ ├── slow_large_post.phpt │ │ ├── sni/ │ │ │ └── server.phpt │ │ ├── static_handler/ │ │ │ ├── locations.phpt │ │ │ ├── mimetype_not_exists.phpt │ │ │ ├── read_link_2.phpt │ │ │ ├── read_link_file.phpt │ │ │ ├── relative_path.phpt │ │ │ ├── relative_path_2.phpt │ │ │ ├── relative_path_3.phpt │ │ │ └── urldecode.phpt │ │ ├── static_handler.phpt │ │ ├── task/ │ │ │ ├── enable_coroutine.phpt │ │ │ ├── enable_coroutine_with_wrong_usage.phpt │ │ │ └── use_object.phpt │ │ ├── tmp-content-type.phpt │ │ ├── too_many_special_chars_in_cookie.phpt │ │ ├── trailer.phpt │ │ ├── unixsocket.phpt │ │ ├── unixsocket2.phpt │ │ ├── unset_response_header.phpt │ │ ├── upload.phpt │ │ ├── upload4.phpt │ │ ├── uploadFile.phpt │ │ ├── upload_02.phpt │ │ ├── upload_03.phpt │ │ ├── upload_big_file.phpt │ │ ├── upload_file_array_default.phpt │ │ ├── upload_file_array_parsed.phpt │ │ ├── upload_file_empty.phpt │ │ ├── upload_max_filesize.phpt │ │ ├── url_rewrite.phpt │ │ ├── worker_max_concurrency.phpt │ │ └── zstd.phpt │ ├── swoole_http_server_coro/ │ │ ├── bad_request.phpt │ │ ├── buffer_clear.phpt │ │ ├── bug_2682.phpt │ │ ├── bug_3025.phpt │ │ ├── bug_4519.phpt │ │ ├── bug_no_handle.phpt │ │ ├── check_cookie_crlf.phpt │ │ ├── check_http_header_crlf.phpt │ │ ├── chunked_pipeline_request.phpt │ │ ├── close_socket.phpt │ │ ├── compress_continue_frames.phpt │ │ ├── compression_min_length.phpt │ │ ├── compression_types.phpt │ │ ├── continue_frames.phpt │ │ ├── continue_frames2.phpt │ │ ├── continue_frames3.phpt │ │ ├── continue_frames4.phpt │ │ ├── control_frame_compress.phpt │ │ ├── control_frame_fragmented.phpt │ │ ├── crash-bad-return-type.phpt │ │ ├── create_response.phpt │ │ ├── create_response_2.phpt │ │ ├── disconnect.phpt │ │ ├── error_404.phpt │ │ ├── error_413.phpt │ │ ├── form_data_1.phpt │ │ ├── form_data_2.phpt │ │ ├── graceful_shutdown.phpt │ │ ├── handle.phpt │ │ ├── http2.phpt │ │ ├── http2_ssl.phpt │ │ ├── https.phpt │ │ ├── ipv6.phpt │ │ ├── keepalive.phpt │ │ ├── open_frame.phpt │ │ ├── ping.phpt │ │ ├── pipeline.phpt │ │ ├── post_array.phpt │ │ ├── random_port.phpt │ │ ├── rawContent_get_big_data.phpt │ │ ├── remote_addr.phpt │ │ ├── restart.phpt │ │ ├── reuse_port.phpt │ │ ├── sendfile.phpt │ │ ├── server_addr.phpt │ │ ├── slow_client.phpt │ │ ├── slow_large_post.phpt │ │ ├── ssl_bad_client.phpt │ │ ├── tcp_nodelay.phpt │ │ ├── upload.phpt │ │ ├── websocket.phpt │ │ ├── websocket_close.phpt │ │ ├── websocket_compression.phpt │ │ ├── websocket_mixed.phpt │ │ ├── websocket_ping.phpt │ │ └── websocket_ping_pong.phpt │ ├── swoole_iouring/ │ │ ├── mix.phpt │ │ └── setting.phpt │ ├── swoole_library/ │ │ ├── array_object/ │ │ │ └── base.phpt │ │ ├── database/ │ │ │ ├── mysqli.phpt │ │ │ ├── pdo.phpt │ │ │ └── redis.phpt │ │ ├── exec/ │ │ │ ├── exec/ │ │ │ │ ├── 1.phpt │ │ │ │ └── 2.phpt │ │ │ └── shell_exec/ │ │ │ └── 1.phpt │ │ ├── multibyte_string_object/ │ │ │ └── base.phpt │ │ ├── name_resolver/ │ │ │ ├── 1.phpt │ │ │ └── lookup.phpt │ │ ├── string_object/ │ │ │ └── base.phpt │ │ └── wait_group/ │ │ ├── normal.phpt │ │ └── timeout.phpt │ ├── swoole_lock/ │ │ ├── bug_5984.phpt │ │ ├── lock_nb.phpt │ │ ├── lock_read.phpt │ │ ├── lock_timeout.phpt │ │ ├── mutex.phpt │ │ └── spinlock_timeout.phpt │ ├── swoole_pdo_firebird/ │ │ ├── base.phpt │ │ ├── pdo_firebird.inc │ │ ├── query.phpt │ │ ├── sleep.phpt │ │ └── transaction.phpt │ ├── swoole_pdo_odbc/ │ │ ├── base.phpt │ │ ├── blocking.phpt │ │ ├── query.phpt │ │ ├── race.phpt │ │ ├── server_info.phpt │ │ ├── sleep.phpt │ │ └── transaction.phpt │ ├── swoole_pdo_oracle/ │ │ ├── bug41996.phpt │ │ ├── bug44301.phpt │ │ ├── bug46274.phpt │ │ ├── bug46274_2.phpt │ │ ├── bug54379.phpt │ │ ├── bug57702.phpt │ │ ├── bug60994.phpt │ │ ├── bug_33707.phpt │ │ ├── checkliveness.phpt │ │ ├── coroutint.phpt │ │ ├── oci_success_with_info.phpt │ │ ├── pdo_oci_attr_action.phpt │ │ ├── pdo_oci_attr_autocommit_1.phpt │ │ ├── pdo_oci_attr_autocommit_2.phpt │ │ ├── pdo_oci_attr_autocommit_3.phpt │ │ ├── pdo_oci_attr_call_timeout.phpt │ │ ├── pdo_oci_attr_case.phpt │ │ ├── pdo_oci_attr_client.phpt │ │ ├── pdo_oci_attr_client_identifier.phpt │ │ ├── pdo_oci_attr_client_info.phpt │ │ ├── pdo_oci_attr_drivername.phpt │ │ ├── pdo_oci_attr_module.phpt │ │ ├── pdo_oci_attr_nulls_1.phpt │ │ ├── pdo_oci_attr_prefetch_1.phpt │ │ ├── pdo_oci_attr_prefetch_2.phpt │ │ ├── pdo_oci_attr_server.phpt │ │ ├── pdo_oci_class_constants.phpt │ │ ├── pdo_oci_debugdumpparams.phpt │ │ ├── pdo_oci_fread_1.phpt │ │ ├── pdo_oci_phpinfo.phpt │ │ ├── pdo_oci_quote1.phpt │ │ ├── pdo_oci_stmt_getcolumnmeta.phpt │ │ ├── pdo_oci_stream_1.phpt │ │ ├── pdo_oci_stream_2.phpt │ │ ├── pdo_oci_templob_1.phpt │ │ ├── pdo_oracle.inc │ │ ├── pecl_bug_11345.phpt │ │ ├── pecl_bug_6364.phpt │ │ ├── transcation.phpt │ │ └── transcation2.phpt │ ├── swoole_pdo_pgsql/ │ │ ├── base.phpt │ │ ├── blocking.phpt │ │ ├── bug_5635.phpt │ │ ├── libpq_version.phpt │ │ ├── pdo_pgsql.inc │ │ ├── query.phpt │ │ ├── race.phpt │ │ ├── sleep.phpt │ │ └── transaction.phpt │ ├── swoole_pdo_sqlite/ │ │ ├── bug33841.phpt │ │ ├── bug35336.phpt │ │ ├── bug38334.phpt │ │ ├── bug43831.phpt │ │ ├── bug44327_2_1.phpt │ │ ├── bug44327_2_2.phpt │ │ ├── bug44327_3_1.phpt │ │ ├── bug44327_3_2.phpt │ │ ├── bug46139.phpt │ │ ├── bug46542.phpt │ │ ├── bug48773.phpt │ │ ├── bug50728.phpt │ │ ├── bug52487.phpt │ │ ├── bug66033.phpt │ │ ├── bug70862.phpt │ │ ├── bug70862_1.phpt │ │ ├── bug78192_1.phpt │ │ ├── bug78192_2.phpt │ │ ├── bug79664_1.phpt │ │ ├── bug79664_2.phpt │ │ ├── bug81740.phpt │ │ ├── bug_42589.phpt │ │ ├── bug_44159_sqlite_version_1.phpt │ │ ├── bug_44159_sqlite_version_2.phpt │ │ ├── bug_47769.phpt │ │ ├── bug_63916-2.phpt │ │ ├── bug_63916_1.phpt │ │ ├── bug_63916_2.phpt │ │ ├── bug_64705.phpt │ │ ├── coroutine.phpt │ │ ├── coroutine2.phpt │ │ ├── debugdumpparams_001.phpt │ │ ├── gh9032.phpt │ │ ├── open_basedir.phpt │ │ ├── pdo_035.phpt │ │ ├── pdo_fetch_func_001.phpt │ │ ├── pdo_fetch_func_001_1.phpt │ │ ├── pdo_sqlite.inc │ │ ├── pdo_sqlite_extendederror_attr.phpt │ │ ├── pdo_sqlite_filename_uri.phpt │ │ ├── pdo_sqlite_get_attribute.phpt │ │ ├── pdo_sqlite_lastinsertid.phpt │ │ ├── pdo_sqlite_open_flags.phpt │ │ ├── pdo_sqlite_statement_getattribute.phpt │ │ ├── pdo_sqlite_tostring_exception.phpt │ │ └── pdo_sqlite_transaction.phpt │ ├── swoole_process/ │ │ ├── alarm.phpt │ │ ├── close.phpt │ │ ├── coro/ │ │ │ ├── ipc.phpt │ │ │ ├── set_protocol.phpt │ │ │ ├── signal.phpt │ │ │ └── start.phpt │ │ ├── ctor.phpt │ │ ├── daemon.phpt │ │ ├── deamon.phpt │ │ ├── echo.py │ │ ├── enable_coroutine.phpt │ │ ├── exception.phpt │ │ ├── exec.phpt │ │ ├── exit.phpt │ │ ├── freeQueue.phpt │ │ ├── getaffinity.phpt │ │ ├── ignore_sigpipe.phpt │ │ ├── ignore_sigpipe_2.phpt │ │ ├── kill.phpt │ │ ├── msgq_capacity.phpt │ │ ├── name.phpt │ │ ├── null_callback.phpt │ │ ├── pop.phpt │ │ ├── priority.phpt │ │ ├── priority_error.phpt │ │ ├── process_exec.phpt │ │ ├── process_id.phpt │ │ ├── process_msgqueue.phpt │ │ ├── process_push.phpt │ │ ├── process_select.phpt │ │ ├── push.phpt │ │ ├── read.phpt │ │ ├── redirect.phpt │ │ ├── setaffinity.phpt │ │ ├── signal.phpt │ │ ├── signal_in_manager.phpt │ │ ├── signal_in_manager_2.phpt │ │ ├── signal_in_manager_3.phpt │ │ ├── signal_in_task_worker.phpt │ │ ├── signal_twice.phpt │ │ ├── start.phpt │ │ ├── timeout.phpt │ │ ├── useQueue.phpt │ │ ├── wait.phpt │ │ ├── wait_signal.phpt │ │ ├── write.phpt │ │ └── write_in_worker.phpt │ ├── swoole_process_pool/ │ │ ├── bug_2652.phpt │ │ ├── create_websocket_server.phpt │ │ ├── detach.phpt │ │ ├── enable_coroutine.phpt │ │ ├── enable_coroutine2.phpt │ │ ├── export_socket.phpt │ │ ├── getprocess_1.phpt │ │ ├── getprocess_2.phpt │ │ ├── getprocess_3.phpt │ │ ├── getprocess_4.phpt │ │ ├── getprocess_5.phpt │ │ ├── master_callback.phpt │ │ ├── master_pid.phpt │ │ ├── max_wait_time.phpt │ │ ├── message.phpt │ │ ├── message_async.phpt │ │ ├── message_bus.phpt │ │ ├── message_bus_sync.phpt │ │ ├── msgqueue.phpt │ │ ├── msgqueue_2.phpt │ │ ├── reload.phpt │ │ ├── reuse_port.phpt │ │ ├── shutdown.phpt │ │ ├── socket_coro.phpt │ │ ├── start_pool_twice_in_same_process.phpt │ │ ├── start_twice.phpt │ │ └── worker_exit_1.phpt │ ├── swoole_redis_server/ │ │ ├── big_packet.phpt │ │ ├── empty.phpt │ │ ├── format.phpt │ │ ├── getHandler.phpt │ │ ├── large_data_map.phpt │ │ └── nested_map.phpt │ ├── swoole_runtime/ │ │ ├── accept.phpt │ │ ├── accept_timeout.phpt │ │ ├── async_protocol.phpt │ │ ├── base.phpt │ │ ├── bindto.phpt │ │ ├── bindto2.phpt │ │ ├── block.phpt │ │ ├── bug_4657.phpt │ │ ├── bug_5104.phpt │ │ ├── cancel_sleep.phpt │ │ ├── destruct.phpt │ │ ├── enable_crypto.phpt │ │ ├── exec/ │ │ │ └── exec.phpt │ │ ├── file_hook/ │ │ │ ├── a.inc │ │ │ ├── async_file.phpt │ │ │ ├── b.inc │ │ │ ├── bug_4327.phpt │ │ │ ├── bug_ftell_2g.phpt │ │ │ ├── bug_scandir.phpt │ │ │ ├── fgets.phpt │ │ │ ├── flock.phpt │ │ │ ├── fread.phpt │ │ │ ├── include.phpt │ │ │ ├── include_2.phpt │ │ │ ├── lock_ex.phpt │ │ │ ├── lock_nb_1.phpt │ │ │ ├── lock_nb_2.phpt │ │ │ ├── lock_sh.phpt │ │ │ ├── open_basedir.phpt │ │ │ ├── read.phpt │ │ │ └── readdir.phpt │ │ ├── file_lock/ │ │ │ ├── async_file.phpt │ │ │ ├── lock_ex.phpt │ │ │ ├── lock_nb.phpt │ │ │ └── lock_sh_1.phpt │ │ ├── ftp_fopen_wrapper.phpt │ │ ├── get_hook_flags.phpt │ │ ├── gethostbyname.phpt │ │ ├── hook_default.phpt │ │ ├── hook_enable_coroutine.phpt │ │ ├── hook_set_flags.phpt │ │ ├── library.phpt │ │ ├── nonblock.phpt │ │ ├── out_of_coroutine.phpt │ │ ├── pdo.phpt │ │ ├── persistent.phpt │ │ ├── proc/ │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ ├── 3.phpt │ │ │ ├── 4.phpt │ │ │ ├── 5.phpt │ │ │ ├── close_after_wait.phpt │ │ │ └── proc_open_pipes.inc │ │ ├── redis_connect.phpt │ │ ├── redis_pconnect.phpt │ │ ├── remote_object/ │ │ │ └── dns.phpt │ │ ├── sento.phpt │ │ ├── set_hook_flags.phpt │ │ ├── sleep.phpt │ │ ├── sleep_yield.phpt │ │ ├── sockets/ │ │ │ ├── basic/ │ │ │ │ ├── bug46360.phpt │ │ │ │ ├── bug49341.phpt │ │ │ │ ├── bug63000.phpt │ │ │ │ ├── bug76839.phpt │ │ │ │ ├── ipv4loop.phpt │ │ │ │ ├── ipv6_skipif.inc │ │ │ │ ├── ipv6loop.phpt │ │ │ │ ├── mcast_helpers.php.inc │ │ │ │ ├── mcast_ipv4_recv.phpt │ │ │ │ ├── mcast_ipv4_send.phpt │ │ │ │ ├── mcast_ipv4_send_error.phpt │ │ │ │ ├── mcast_ipv6_recv.phpt │ │ │ │ ├── mcast_ipv6_recv_limited.phpt │ │ │ │ ├── mcast_ipv6_send.phpt │ │ │ │ ├── socket_accept_failure.phpt │ │ │ │ ├── socket_bind.phpt │ │ │ │ ├── socket_clear_error.phpt │ │ │ │ ├── socket_connect_params.phpt │ │ │ │ ├── socket_create_listen-nobind.phpt │ │ │ │ ├── socket_create_listen.phpt │ │ │ │ ├── socket_create_listen_used.phpt │ │ │ │ ├── socket_create_pair.phpt │ │ │ │ ├── socket_getopt.phpt │ │ │ │ ├── socket_getpeername.phpt │ │ │ │ ├── socket_getpeername_ipv4loop.phpt │ │ │ │ ├── socket_getpeername_ipv6loop.phpt │ │ │ │ ├── socket_getsockname.phpt │ │ │ │ ├── socket_listen-wrongparams.phpt │ │ │ │ ├── socket_read_params.phpt │ │ │ │ ├── socket_select-wrongparams-2.phpt │ │ │ │ ├── socket_select.phpt │ │ │ │ ├── socket_select_error.phpt │ │ │ │ ├── socket_sentto_recvfrom_ipv4_udp.phpt │ │ │ │ ├── socket_sentto_recvfrom_ipv6_udp.phpt │ │ │ │ ├── socket_sentto_recvfrom_unix.phpt │ │ │ │ ├── socket_set_block-retval.phpt │ │ │ │ ├── socket_set_nonblock-retval.phpt │ │ │ │ ├── socket_set_nonblock.phpt │ │ │ │ ├── socket_set_option_bindtodevice.phpt │ │ │ │ ├── socket_set_option_error_socket_option.phpt │ │ │ │ ├── socket_set_option_in6_pktinfo.phpt │ │ │ │ ├── socket_set_option_rcvtimeo.phpt │ │ │ │ ├── socket_set_option_seolinger.phpt │ │ │ │ ├── socket_set_option_sndtimeo.phpt │ │ │ │ ├── socket_setopt_basic.phpt │ │ │ │ ├── socket_shutdown.phpt │ │ │ │ ├── socket_strerror.phpt │ │ │ │ └── unixloop.phpt │ │ │ ├── error.phpt │ │ │ ├── get_name.phpt │ │ │ ├── import.phpt │ │ │ ├── nonblock.phpt │ │ │ ├── socketpair.phpt │ │ │ ├── tcp_client.phpt │ │ │ ├── tcp_server.phpt │ │ │ ├── timeout.phpt │ │ │ └── udp.phpt │ │ ├── ssl/ │ │ │ ├── capture_peer_cert.phpt │ │ │ ├── client.phpt │ │ │ ├── enable_crypto.phpt │ │ │ ├── local_cert.phpt │ │ │ ├── server.phpt │ │ │ └── without_key.phpt │ │ ├── stdin.phpt │ │ ├── stream_context.phpt │ │ ├── stream_context_pass_null.phpt │ │ ├── stream_copy_to_stream_socket.phpt │ │ ├── stream_get_meta_data.phpt │ │ ├── stream_select/ │ │ │ ├── base.phpt │ │ │ ├── blocked.phpt │ │ │ ├── bug46024.phpt │ │ │ ├── bug60120.phpt │ │ │ ├── bug60602.phpt │ │ │ ├── bug64438.phpt │ │ │ ├── bug64770.phpt │ │ │ ├── bug69521.phpt │ │ │ ├── bug72075.phpt │ │ │ ├── conflict.phpt │ │ │ ├── never_timeout.phpt │ │ │ ├── preserve_keys.phpt │ │ │ ├── return_val.phpt │ │ │ ├── rw_events.phpt │ │ │ ├── stream_set_blocking.phpt │ │ │ └── timeout.phpt │ │ ├── stream_socket_pair.phpt │ │ ├── tcp-c10k.phpt │ │ ├── tcp.phpt │ │ ├── udg.phpt │ │ ├── udp-c10k.phpt │ │ ├── udp.phpt │ │ ├── unix.phpt │ │ └── unsafe/ │ │ └── pcntl_fork.phpt │ ├── swoole_server/ │ │ ├── accept_zero.phpt │ │ ├── addListener.phpt │ │ ├── addProcess.phpt │ │ ├── addProcess_base.phpt │ │ ├── addProcess_with_error.phpt │ │ ├── addProcess_with_event_wait.phpt │ │ ├── addProcess_with_tick.phpt │ │ ├── base/ │ │ │ ├── reload_1.phpt │ │ │ ├── reload_2.phpt │ │ │ ├── shutdown.phpt │ │ │ └── shutdown_single.phpt │ │ ├── bigPipeMessage.phpt │ │ ├── big_session_id.phpt │ │ ├── big_udp_packet.phpt │ │ ├── bind.phpt │ │ ├── bug_11000_01.phpt │ │ ├── bug_1864.phpt │ │ ├── bug_2308.phpt │ │ ├── bug_2313.phpt │ │ ├── bug_2639.phpt │ │ ├── bug_2736.phpt │ │ ├── bug_aio.phpt │ │ ├── check_callback.phpt │ │ ├── check_chunk_total_size.phpt │ │ ├── client_close_in_writable_event.phpt │ │ ├── close_force.phpt │ │ ├── close_in_connect_callback.phpt │ │ ├── close_in_manager.phpt │ │ ├── close_in_master.phpt │ │ ├── close_in_non_current_worker.phpt │ │ ├── close_in_other_worker_with_base.phpt │ │ ├── close_in_task_worker.phpt │ │ ├── close_max_fd.phpt │ │ ├── close_queued.phpt │ │ ├── close_reset.phpt │ │ ├── command.phpt │ │ ├── connections.phpt │ │ ├── discard_timeout_packet.phpt │ │ ├── dispatch_fallback.phpt │ │ ├── dispatch_func.phpt │ │ ├── dispatch_func_discard.phpt │ │ ├── dispatch_func_memory_leak.phpt │ │ ├── dispatch_mode_1.phpt │ │ ├── dispatch_mode_10.phpt │ │ ├── dispatch_mode_3.phpt │ │ ├── dispatch_mode_7.phpt │ │ ├── dispatch_mode_8.phpt │ │ ├── dispatch_mode_9.phpt │ │ ├── duplicate_registered.phpt │ │ ├── enable_coroutine.phpt │ │ ├── enable_delay_receive.phpt │ │ ├── enable_reuse_port.phpt │ │ ├── eof_protocol.phpt │ │ ├── eof_server.phpt │ │ ├── event/ │ │ │ ├── before_shutdown.phpt │ │ │ ├── manager_start.phpt │ │ │ ├── manager_stop.phpt │ │ │ ├── shutdown.phpt │ │ │ ├── start.phpt │ │ │ └── worker_exit.phpt │ │ ├── exist.phpt │ │ ├── force_reload.phpt │ │ ├── force_reload2.phpt │ │ ├── force_reload3.phpt │ │ ├── force_reload4.phpt │ │ ├── getCallback.phpt │ │ ├── getClientInfo.phpt │ │ ├── getClientInfo_in_callback_of_close.phpt │ │ ├── getClientInfo_in_callback_of_close_2.phpt │ │ ├── getClientList.phpt │ │ ├── getLastError.phpt │ │ ├── getSocket.phpt │ │ ├── getWorkerStatus.phpt │ │ ├── heartbeat.phpt │ │ ├── heartbeat_true.phpt │ │ ├── heartbeat_with_base.phpt │ │ ├── http_protocol.phpt │ │ ├── idle_worekr_num.phpt │ │ ├── invalid_fd.phpt │ │ ├── invalid_option.phpt │ │ ├── kill_user_process_01.phpt │ │ ├── kill_user_process_02.phpt │ │ ├── kill_worker_01.phpt │ │ ├── kill_worker_02.phpt │ │ ├── last_time.phpt │ │ ├── length/ │ │ │ ├── 00.phpt │ │ │ ├── 01.phpt │ │ │ ├── 02.phpt │ │ │ ├── 03.phpt │ │ │ └── length_func.phpt │ │ ├── listen_fail.phpt │ │ ├── max_idle_time_1.phpt │ │ ├── max_idle_time_2.phpt │ │ ├── max_queued_bytes.phpt │ │ ├── max_request.phpt │ │ ├── max_request_grace_disabled.phpt │ │ ├── max_request_grace_enabled.phpt │ │ ├── max_request_threshold.phpt │ │ ├── memory_leak/ │ │ │ ├── length.phpt │ │ │ ├── pipe_message.phpt │ │ │ ├── task.phpt │ │ │ └── tcp.phpt │ │ ├── mqtt/ │ │ │ ├── length_offset.phpt │ │ │ ├── recv_fail.phpt │ │ │ └── send_big_pack.phpt │ │ ├── new_twice.phpt │ │ ├── object/ │ │ │ ├── event.phpt │ │ │ ├── getManagerPid.phpt │ │ │ ├── getMasterPid.phpt │ │ │ ├── getWorkerId.phpt │ │ │ ├── getWorkerPid.phpt │ │ │ ├── packet.phpt │ │ │ ├── pipe_message.phpt │ │ │ ├── status_info.phpt │ │ │ └── task_result.phpt │ │ ├── onReload.phpt │ │ ├── parse_option_to_size.phpt │ │ ├── pid_file.phpt │ │ ├── protect.phpt │ │ ├── protect_false.phpt │ │ ├── reload.phpt │ │ ├── reload_async.phpt │ │ ├── reload_process.phpt │ │ ├── sendMessage_1.phpt │ │ ├── sendMessage_2.phpt │ │ ├── sendMessage_3.phpt │ │ ├── sendMessage_4.phpt │ │ ├── sendMessage_in_manager.phpt │ │ ├── send_2.phpt │ │ ├── send_2m_in_task_worker.phpt │ │ ├── send_2m_in_user_process.phpt │ │ ├── send_3.phpt │ │ ├── send_big_packet.phpt │ │ ├── send_in_master.phpt │ │ ├── send_in_other_worker_with_base.phpt │ │ ├── sendfile.phpt │ │ ├── sendfile_02.phpt │ │ ├── sendfile_ssl.phpt │ │ ├── sendto_timeout.phpt │ │ ├── shutdown.phpt │ │ ├── shutdown_in_master.phpt │ │ ├── shutdown_in_process.phpt │ │ ├── shutdown_with_base_mode.phpt │ │ ├── sigint_with_base.phpt │ │ ├── sigint_with_process.phpt │ │ ├── single_thread/ │ │ │ ├── heartbeat.phpt │ │ │ ├── large_packet.phpt │ │ │ └── user_setting.phpt │ │ ├── sleep.phpt │ │ ├── slow_client.phpt │ │ ├── slow_master.phpt │ │ ├── slow_worker.phpt │ │ ├── ssl/ │ │ │ ├── 00.phpt │ │ │ ├── bad_client.phpt │ │ │ ├── code/ │ │ │ │ ├── client.go │ │ │ │ └── client.js │ │ │ ├── dtls.phpt │ │ │ ├── dtls_big_packet.phpt │ │ │ ├── dtls_with_length_protocol.phpt │ │ │ ├── golang.phpt │ │ │ ├── heartbeat_1.phpt │ │ │ ├── heartbeat_2.phpt │ │ │ ├── nodejs.phpt │ │ │ ├── ssl_send_wait.phpt │ │ │ ├── verify_01.phpt │ │ │ ├── verify_02.phpt │ │ │ └── verify_03.phpt │ │ ├── start_twice.phpt │ │ ├── stats.phpt │ │ ├── stats_file.phpt │ │ ├── stats_file_json.phpt │ │ ├── stats_file_php.phpt │ │ ├── stop.phpt │ │ ├── stop_in_workerStart.phpt │ │ ├── systemd_fds.phpt │ │ ├── task/ │ │ │ ├── base.phpt │ │ │ ├── bug_2585.phpt │ │ │ ├── callback_is_null.phpt │ │ │ ├── enable_coroutine.phpt │ │ │ ├── finish_timeout.phpt │ │ │ ├── huge_data.phpt │ │ │ ├── idle_worekr_num.phpt │ │ │ ├── invalid_packet.phpt │ │ │ ├── kill_01.phpt │ │ │ ├── kill_02.phpt │ │ │ ├── kill_task_worker_02.phpt │ │ │ ├── scheduler_warning.phpt │ │ │ ├── task_callback.phpt │ │ │ ├── task_co.phpt │ │ │ ├── task_enable_coroutine.phpt │ │ │ ├── task_enable_coroutine_return.phpt │ │ │ ├── task_in_manager.phpt │ │ │ ├── task_in_master.phpt │ │ │ ├── task_in_task_worker.phpt │ │ │ ├── task_in_user_process.phpt │ │ │ ├── task_ipc_mode_2.phpt │ │ │ ├── task_ipc_mode_3.phpt │ │ │ ├── task_max_request.phpt │ │ │ ├── task_pack.phpt │ │ │ ├── task_queue.phpt │ │ │ ├── task_wait.phpt │ │ │ ├── timer.phpt │ │ │ ├── unpack.phpt │ │ │ └── without_onfinish.phpt │ │ ├── taskWaitMulti.phpt │ │ ├── taskwait_01.phpt │ │ ├── taskwait_02.phpt │ │ ├── unregistered_signal.phpt │ │ ├── unsock_dgram.phpt │ │ ├── unsock_stream.phpt │ │ ├── user_process.phpt │ │ ├── user_process_2.phpt │ │ ├── user_process_force_exit.phpt │ │ ├── user_process_pid.phpt │ │ ├── wrong_eof_setting.phpt │ │ └── z_conn_10k.phpt │ ├── swoole_server_coro/ │ │ ├── length_1.phpt │ │ ├── random_port.phpt │ │ ├── reuse_port.phpt │ │ ├── ssl.phpt │ │ └── tcp.phpt │ ├── swoole_server_port/ │ │ ├── connections.phpt │ │ ├── duplicate_registered.phpt │ │ ├── heartbeat_1.phpt │ │ ├── heartbeat_2.phpt │ │ ├── heartbeat_3.phpt │ │ ├── http.phpt │ │ ├── multi_port.phpt │ │ ├── sub_handshake.phpt │ │ └── tcp_eof.phpt │ ├── swoole_socket_coro/ │ │ ├── accept.phpt │ │ ├── all.phpt │ │ ├── bound_error.phpt │ │ ├── cancel.phpt │ │ ├── check_writev_readv_param_type.phpt │ │ ├── closed.phpt │ │ ├── complete_test.phpt │ │ ├── concurrency.phpt │ │ ├── construct_parse_args_failed.phpt │ │ ├── fd.phpt │ │ ├── getBoundCid.phpt │ │ ├── getopt/ │ │ │ ├── get.phpt │ │ │ └── tcpinfo.phpt │ │ ├── getpeername.phpt │ │ ├── getsockname.phpt │ │ ├── icmp.phpt │ │ ├── import_1.phpt │ │ ├── import_2.phpt │ │ ├── import_3.phpt │ │ ├── import_4.phpt │ │ ├── import_5.phpt │ │ ├── iov_max.phpt │ │ ├── peek_and_checkLiveness.phpt │ │ ├── protocol/ │ │ │ ├── bug_3586.phpt │ │ │ └── package_length_func.phpt │ │ ├── readVectorAll.phpt │ │ ├── readVectorAll_ssl.phpt │ │ ├── readVector_ssl.phpt │ │ ├── readVector_ssl_eagain.phpt │ │ ├── readv.phpt │ │ ├── readv_eagain.phpt │ │ ├── recvAll.phpt │ │ ├── recvAll_timeout.phpt │ │ ├── recv_bad_packet.phpt │ │ ├── recv_line.phpt │ │ ├── recv_timeout.phpt │ │ ├── recv_with_buffer.phpt │ │ ├── reuse.phpt │ │ ├── reuse_2.phpt │ │ ├── sendfile.phpt │ │ ├── sendto.phpt │ │ ├── sendto_big.phpt │ │ ├── sendto_large_packet.phpt │ │ ├── server_accept.phpt │ │ ├── setopt/ │ │ │ ├── bindtodevice.phpt │ │ │ ├── ipv6_pktinfo.phpt │ │ │ ├── multicast.phpt │ │ │ ├── recvtimeo.phpt │ │ │ └── reuse.phpt │ │ ├── shutdown.phpt │ │ ├── ssl.phpt │ │ ├── ssl_bad_server.phpt │ │ ├── tcp-c10k.phpt │ │ ├── ulimit.phpt │ │ ├── unix_dgram.phpt │ │ ├── unix_stream.phpt │ │ ├── writeVectorAll.phpt │ │ ├── writeVectorAll_ssl.phpt │ │ ├── writeVector_ssl.phpt │ │ ├── writeVector_ssl_eagain.phpt │ │ ├── writev.phpt │ │ └── writev_eagain.phpt │ ├── swoole_ssh2/ │ │ ├── bug63480.phpt │ │ ├── bug79631.phpt │ │ ├── ssh2_auth.phpt │ │ ├── ssh2_auth_pubkey.phpt │ │ ├── ssh2_auth_pubkey_file.phpt │ │ ├── ssh2_connect.phpt │ │ ├── ssh2_exec.phpt │ │ ├── ssh2_scp_recv.phpt │ │ ├── ssh2_scp_send.phpt │ │ ├── ssh2_send_eof.phpt │ │ ├── ssh2_sftp_001.phpt │ │ ├── ssh2_sftp_002.phpt │ │ ├── ssh2_shell.phpt │ │ ├── ssh2_skip.inc │ │ ├── ssh2_stream_select.phpt │ │ ├── ssh2_test.inc │ │ ├── testkey_ed25519 │ │ └── testkey_ed25519.pub │ ├── swoole_stdext/ │ │ ├── array_method/ │ │ │ ├── 0.phpt │ │ │ ├── 1.phpt │ │ │ ├── 2.phpt │ │ │ └── method.phpt │ │ ├── stream_method/ │ │ │ └── 0.phpt │ │ ├── string_method/ │ │ │ ├── 0.phpt │ │ │ ├── 1.phpt │ │ │ ├── json.phpt │ │ │ ├── marshal.phpt │ │ │ ├── match.phpt │ │ │ ├── mbstring.phpt │ │ │ └── method.phpt │ │ └── typed_array/ │ │ ├── 0.phpt │ │ ├── 1.phpt │ │ ├── 10.phpt │ │ ├── 11.phpt │ │ ├── 2.phpt │ │ ├── 3.phpt │ │ ├── 4.phpt │ │ ├── 5.phpt │ │ ├── 6.phpt │ │ ├── 7.phpt │ │ ├── 8.phpt │ │ ├── 9.phpt │ │ ├── array_pop.phpt │ │ ├── array_push.phpt │ │ ├── array_shift.phpt │ │ ├── array_splice.phpt │ │ ├── array_unshift.phpt │ │ ├── resource.phpt │ │ └── sort.phpt │ ├── swoole_table/ │ │ ├── big_size.phpt │ │ ├── bug_2263.phpt │ │ ├── bug_2290.phpt │ │ ├── create_10k_object.phpt │ │ ├── del.phpt │ │ ├── force_unlock.phpt │ │ ├── foreach.phpt │ │ ├── getMemorySize_1.phpt │ │ ├── get_after_destroy.phpt │ │ ├── get_before_create.phpt │ │ ├── gh-5789.phpt │ │ ├── incr_after_del.phpt │ │ ├── int.phpt │ │ ├── key_value.phpt │ │ ├── negative.phpt │ │ ├── random_bytes.phpt │ │ ├── set_after_del.phpt │ │ ├── stats.phpt │ │ └── type_conv.phpt │ ├── swoole_thread/ │ │ ├── add_update.phpt │ │ ├── affinity.phpt │ │ ├── arraylist.phpt │ │ ├── async-io.phpt │ │ ├── atomic_ctor.phpt │ │ ├── barrier.phpt │ │ ├── co-stream.phpt │ │ ├── co-user-yield.phpt │ │ ├── empty_args.phpt │ │ ├── exit.phpt │ │ ├── fatal_error_1.inc │ │ ├── fatal_error_1.phpt │ │ ├── fatal_error_2.inc │ │ ├── fatal_error_2.phpt │ │ ├── fatal_error_3.phpt │ │ ├── incr.phpt │ │ ├── info.phpt │ │ ├── lock.phpt │ │ ├── map.phpt │ │ ├── map2array.phpt │ │ ├── name.phpt │ │ ├── numeric_strkey.phpt │ │ ├── php_socket.phpt │ │ ├── pipe.phpt │ │ ├── priority.phpt │ │ ├── putenv.phpt │ │ ├── queue.phpt │ │ ├── queue_notify_all.phpt │ │ ├── server/ │ │ │ ├── base.phpt │ │ │ ├── bug_5662.phpt │ │ │ ├── bug_5694.phpt │ │ │ ├── create_response.phpt │ │ │ ├── exit.phpt │ │ │ ├── fatal_error.phpt │ │ │ ├── functions.inc │ │ │ ├── heartbeat.phpt │ │ │ ├── hook_flags.phpt │ │ │ ├── listen.phpt │ │ │ ├── manager_timer.phpt │ │ │ ├── reload.phpt │ │ │ ├── reload_task_workers.phpt │ │ │ ├── reload_task_workers_async.phpt │ │ │ ├── reset_concurrency.phpt │ │ │ ├── send_in_user_process.phpt │ │ │ ├── send_large_packet.phpt │ │ │ ├── setting.phpt │ │ │ ├── stop_worker.phpt │ │ │ ├── taskCo.phpt │ │ │ ├── taskWaitMulti.phpt │ │ │ ├── udp_port.phpt │ │ │ └── websocket.phpt │ │ ├── shell_exec.phpt │ │ ├── signal.phpt │ │ ├── sort.phpt │ │ ├── stdio.phpt │ │ ├── stream.phpt │ │ ├── stream_arg.phpt │ │ ├── thread_status.phpt │ │ └── yield.phpt │ ├── swoole_timer/ │ │ ├── after.phpt │ │ ├── after_fork.phpt │ │ ├── bug_2342.phpt │ │ ├── bug_4794.phpt │ │ ├── bug_4794_2.phpt │ │ ├── bug_4794_3.phpt │ │ ├── bug_4794_4.phpt │ │ ├── bug_4794_5.phpt │ │ ├── bug_4794_6.phpt │ │ ├── bug_4794_7.phpt │ │ ├── bug_4794_8.phpt │ │ ├── call_private.phpt │ │ ├── callback_bug_with_array.phpt │ │ ├── clearAll.phpt │ │ ├── enable_coroutine.phpt │ │ ├── enable_coroutine2.phpt │ │ ├── function_alias.phpt │ │ ├── greater_than_0.phpt │ │ ├── info.phpt │ │ ├── list.phpt │ │ ├── manager.phpt │ │ ├── master.phpt │ │ ├── master_base.phpt │ │ ├── memory.phpt │ │ ├── next_round.phpt │ │ ├── not_exist.phpt │ │ ├── reinit_1.phpt │ │ ├── reinit_2.phpt │ │ ├── stats.phpt │ │ ├── swoole_timer_list_alias.phpt │ │ ├── task_worker.phpt │ │ ├── task_worker_tick_1k.phpt │ │ └── verify.phpt │ ├── swoole_websocket_server/ │ │ ├── bug_1.phpt │ │ ├── close_frame_flag.phpt │ │ ├── close_frame_full.phpt │ │ ├── compression.phpt │ │ ├── disconnect.phpt │ │ ├── disconnect_with_code.phpt │ │ ├── dynamic_property.phpt │ │ ├── empty_message.phpt │ │ ├── exists_and_isEstablished.phpt │ │ ├── fin.phpt │ │ ├── fin2.phpt │ │ ├── get_large_requests.phpt │ │ ├── get_small_requests.phpt │ │ ├── greeter.phpt │ │ ├── header_token.phpt │ │ ├── listener.phpt │ │ ├── malformed_data.phpt │ │ ├── memory.phpt │ │ ├── message_size.phpt │ │ ├── onDisconnct.phpt │ │ ├── pack.phpt │ │ ├── ping.phpt │ │ ├── pingloop.phpt │ │ ├── pingloop_open_ping_pong_frame.phpt │ │ ├── pingpong.phpt │ │ ├── pingpong_open_ping_pong_frame.phpt │ │ ├── query.phpt │ │ ├── recv_decode.phpt │ │ ├── send_close_frame.phpt │ │ ├── send_encode.phpt │ │ ├── send_encode_async.phpt │ │ ├── set_cookie_on_before_handshake_response.phpt │ │ ├── set_cookie_on_handshake.phpt │ │ └── websocket_compress_on_handshake.phpt │ ├── template │ └── test.sql ├── thirdparty/ │ ├── boost/ │ │ └── asm/ │ │ ├── LICENSE │ │ ├── combined.S │ │ ├── jump_arm64_aapcs_elf_gas.S │ │ ├── jump_arm64_aapcs_macho_gas.S │ │ ├── jump_combined_sysv_macho_gas.S │ │ ├── jump_loongarch64_sysv_elf_gas.S │ │ ├── jump_mips64_n64_elf_gas.S │ │ ├── jump_ppc64_sysv_elf_gas.S │ │ ├── jump_ppc64_sysv_macho_gas.S │ │ ├── jump_ppc64_sysv_xcoff_gas.S │ │ ├── jump_riscv64_sysv_elf_gas.S │ │ ├── jump_sparc64_sysv_elf_gas.S │ │ ├── jump_x86_64_sysv_elf_gas.S │ │ ├── jump_x86_64_sysv_macho_gas.S │ │ ├── make_arm64_aapcs_elf_gas.S │ │ ├── make_arm64_aapcs_macho_gas.S │ │ ├── make_combined_sysv_macho_gas.S │ │ ├── make_loongarch64_sysv_elf_gas.S │ │ ├── make_mips64_n64_elf_gas.S │ │ ├── make_ppc64_sysv_elf_gas.S │ │ ├── make_ppc64_sysv_macho_gas.S │ │ ├── make_ppc64_sysv_xcoff_gas.S │ │ ├── make_riscv64_sysv_elf_gas.S │ │ ├── make_sparc64_sysv_elf_gas.S │ │ ├── make_x86_64_sysv_elf_gas.S │ │ └── make_x86_64_sysv_macho_gas.S │ ├── hiredis/ │ │ ├── CHANGELOG.md │ │ ├── COPYING │ │ ├── README.md │ │ ├── alloc.c │ │ ├── alloc.h │ │ ├── fmacros.h │ │ ├── hiredis.c │ │ ├── hiredis.h │ │ ├── net.c │ │ ├── net.h │ │ ├── read.c │ │ ├── read.h │ │ ├── sds.c │ │ ├── sds.h │ │ └── sdsalloc.h │ ├── llhttp/ │ │ ├── LICENSE │ │ ├── LICENSE-MIT │ │ ├── api.c │ │ ├── http.c │ │ ├── llhttp.c │ │ └── llhttp.h │ ├── multipart_parser.c │ ├── multipart_parser.h │ ├── nghttp2/ │ │ ├── COPYING │ │ ├── LICENSE │ │ ├── nghttp2.h │ │ ├── nghttp2_buf.c │ │ ├── nghttp2_buf.h │ │ ├── nghttp2_hd.c │ │ ├── nghttp2_hd.h │ │ ├── nghttp2_hd_huffman.c │ │ ├── nghttp2_hd_huffman.h │ │ ├── nghttp2_hd_huffman_data.c │ │ ├── nghttp2_helper.c │ │ ├── nghttp2_mem.c │ │ ├── nghttp2_mem.h │ │ ├── nghttp2_rcbuf.c │ │ ├── nghttp2_rcbuf.h │ │ └── nghttp2ver.h │ ├── nlohmann/ │ │ ├── LICENSE.MIT │ │ ├── adl_serializer.hpp │ │ ├── detail/ │ │ │ ├── conversions/ │ │ │ │ ├── from_json.hpp │ │ │ │ ├── to_chars.hpp │ │ │ │ └── to_json.hpp │ │ │ ├── exceptions.hpp │ │ │ ├── input/ │ │ │ │ ├── binary_reader.hpp │ │ │ │ ├── input_adapters.hpp │ │ │ │ ├── json_sax.hpp │ │ │ │ ├── lexer.hpp │ │ │ │ ├── parser.hpp │ │ │ │ └── position_t.hpp │ │ │ ├── iterators/ │ │ │ │ ├── internal_iterator.hpp │ │ │ │ ├── iter_impl.hpp │ │ │ │ ├── iteration_proxy.hpp │ │ │ │ ├── iterator_traits.hpp │ │ │ │ ├── json_reverse_iterator.hpp │ │ │ │ └── primitive_iterator.hpp │ │ │ ├── json_pointer.hpp │ │ │ ├── json_ref.hpp │ │ │ ├── macro_scope.hpp │ │ │ ├── macro_unscope.hpp │ │ │ ├── meta/ │ │ │ │ ├── cpp_future.hpp │ │ │ │ ├── detected.hpp │ │ │ │ ├── is_sax.hpp │ │ │ │ ├── type_traits.hpp │ │ │ │ └── void_t.hpp │ │ │ ├── output/ │ │ │ │ ├── binary_writer.hpp │ │ │ │ ├── output_adapters.hpp │ │ │ │ └── serializer.hpp │ │ │ └── value_t.hpp │ │ ├── json.hpp │ │ ├── json_fwd.hpp │ │ └── thirdparty/ │ │ └── hedley/ │ │ ├── hedley.hpp │ │ └── hedley_undef.hpp │ ├── pdo_oci/ │ │ ├── CREDITS │ │ ├── README.md │ │ ├── oci_driver.c │ │ ├── oci_statement.c │ │ ├── pdo_oci.c │ │ └── php_pdo_oci_int.h │ ├── pdo_sqlite/ │ │ ├── php_pdo_sqlite.h │ │ ├── php_pdo_sqlite_int.h │ │ ├── sqlite_driver.c │ │ ├── sqlite_sql_parser.c │ │ └── sqlite_statement.c │ ├── php/ │ │ ├── LICENSE │ │ ├── curl/ │ │ │ ├── curl_arginfo.h │ │ │ ├── curl_private.h │ │ │ ├── interface.cc │ │ │ ├── multi.cc │ │ │ └── php_curl.h │ │ ├── main/ │ │ │ └── SAPI.h │ │ ├── sockets/ │ │ │ ├── conversions.cc │ │ │ ├── conversions.h │ │ │ ├── multicast.cc │ │ │ ├── multicast.h │ │ │ ├── php_sockets_cxx.h │ │ │ ├── sendrecvmsg.cc │ │ │ └── sockaddr_conv.cc │ │ ├── ssh2/ │ │ │ ├── LICENSE │ │ │ ├── php_ssh2.h │ │ │ ├── ssh2.cc │ │ │ ├── ssh2_fopen_wrappers.cc │ │ │ └── ssh2_sftp.cc │ │ ├── standard/ │ │ │ ├── proc_open.cc │ │ │ ├── proc_open.h │ │ │ └── var_decoder.cc │ │ ├── streams/ │ │ │ ├── php_streams_int.h │ │ │ └── plain_wrapper.c │ │ └── zend/ │ │ └── zend_execute.c │ ├── php82/ │ │ ├── pdo_odbc/ │ │ │ ├── odbc_driver.c │ │ │ ├── odbc_stmt.c │ │ │ └── php_pdo_odbc_int.h │ │ └── pdo_pgsql/ │ │ ├── pgsql_driver.c │ │ ├── pgsql_driver_arginfo.h │ │ ├── pgsql_statement.c │ │ └── php_pdo_pgsql_int.h │ ├── php83/ │ │ ├── pdo_odbc/ │ │ │ ├── odbc_driver.c │ │ │ ├── odbc_stmt.c │ │ │ └── php_pdo_odbc_int.h │ │ └── pdo_pgsql/ │ │ ├── pgsql_driver.c │ │ ├── pgsql_driver_arginfo.h │ │ ├── pgsql_statement.c │ │ └── php_pdo_pgsql_int.h │ ├── php84/ │ │ ├── curl/ │ │ │ ├── curl_arginfo.h │ │ │ ├── curl_private.h │ │ │ ├── interface.cc │ │ │ ├── multi.cc │ │ │ └── php_curl.h │ │ ├── ftp/ │ │ │ ├── CREDITS │ │ │ ├── ftp.c │ │ │ ├── ftp.h │ │ │ ├── php_ftp.c │ │ │ └── php_ftp.h │ │ ├── pdo_firebird/ │ │ │ ├── CREDITS │ │ │ ├── firebird_driver.c │ │ │ ├── firebird_statement.c │ │ │ ├── pdo_firebird_utils.cpp │ │ │ ├── pdo_firebird_utils.h │ │ │ └── php_pdo_firebird_int.h │ │ ├── pdo_odbc/ │ │ │ ├── odbc_driver.c │ │ │ ├── odbc_stmt.c │ │ │ └── php_pdo_odbc_int.h │ │ └── pdo_pgsql/ │ │ ├── pgsql_driver.c │ │ ├── pgsql_driver_arginfo.h │ │ ├── pgsql_sql_parser.c │ │ ├── pgsql_statement.c │ │ └── php_pdo_pgsql_int.h │ └── php85/ │ ├── pdo_firebird/ │ │ ├── CREDITS │ │ ├── firebird_driver.c │ │ ├── firebird_statement.c │ │ ├── pdo_firebird_utils.cpp │ │ ├── pdo_firebird_utils.h │ │ └── php_pdo_firebird_int.h │ ├── pdo_odbc/ │ │ ├── odbc_driver.c │ │ ├── odbc_stmt.c │ │ ├── php_pdo_odbc.h │ │ └── php_pdo_odbc_int.h │ └── pdo_pgsql/ │ ├── pdo_pgsql_arginfo.h │ ├── pgsql_driver.c │ ├── pgsql_driver_arginfo.h │ ├── pgsql_sql_parser.c │ ├── pgsql_statement.c │ ├── php_pdo_pgsql.h │ └── php_pdo_pgsql_int.h └── tools/ ├── analysis.php ├── arginfo-check.php ├── bootstrap.php ├── build-library.php ├── code-generator.php ├── composer.json ├── constant-generator.php ├── export.php ├── gen-data.php ├── gen-ext-class.php ├── get-ip-info.php ├── next-version.php ├── option-generator.php ├── pecl-package.php ├── phpt-fixer.php ├── rename.php ├── send-http-data.php ├── show-big-files.php └── templates/ ├── class.c └── version.tpl.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^' Priority: 2 - Regex: '^<.*\.h>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Right ReflowComments: true SortIncludes: false SortUsingDeclarations: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 8 UseTab: Never ================================================ FILE: .editorconfig ================================================ # https://editorconfig.org/ root = true [*] trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf charset = utf-8 indent_style = space tab_width = 4 [{*.{awk,sh},Makefile*}] indent_size = 4 indent_style = table max_line_length = 80 [*.{c,cc,cpp,h,html,inc,php,phpt}] indent_size = 4 indent_style = space max_line_length = 120 [*.md] indent_style = space max_line_length = 120 [COMMIT_EDITMSG] indent_size = 4 indent_style = space max_line_length = 80 ================================================ FILE: .gitattributes ================================================ /.github/ export-ignore /benchmark/ export-ignore /core-tests/ export-ignore *.h linguist-language=cpp ================================================ FILE: .github/ISSUE_TEMPLATE ================================================ Please answer these questions before submitting your issue. 1. What did you do? If possible, provide a simple script for reproducing the error. 2. What did you expect to see? 3. What did you see instead? 4. What version of Swoole are you using (show your `php --ri swoole`)? 5. What is your machine environment used (show your `uname -a` & `php -v` & `gcc -v`) ? ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/alpine.Dockerfile ================================================ ARG PHP_VERSION ARG ALPINE_VERSION FROM phpswoole/php:${PHP_VERSION}-alpine LABEL maintainer="Swoole Team " version="1.0" license="Apache2" ARG PHP_VERSION COPY . /swoole WORKDIR /swoole RUN apk add liburing-dev RUN set -ex \ && phpize \ && ./configure --enable-swoole-curl --enable-iouring \ && make -s -j$(nproc) && make install RUN echo "extension=swoole.so" > "/usr/local/etc/php/conf.d/swoole.ini" RUN php -v RUN php -m RUN php --ri swoole RUN echo -e "\033[42;37m Build Completed :).\033[0m\n" ================================================ FILE: .github/workflows/core.yml ================================================ name: Core Tests on: [ push, pull_request ] env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} jobs: ubuntu: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[core]')" timeout-minutes: 12 services: tinyproxy: image: "vimagick/tinyproxy" ports: - 8888:8888 nginx: image: "nginx" ports: - "80:80" env: NGINX_PORT: "[::]:80" socks5: image: "xkuma/socks5" ports: - 8080:1080 env: PROXY_USER: user PROXY_PASSWORD: password PROXY_SERVER: 0.0.0.0:1080 socks5-no-auth: image: "xkuma/socks5" ports: - 8081:1080 env: PROXY_SERVER: 0.0.0.0:1080 steps: - uses: actions/checkout@v6 - name: install dependencies run: sudo apt update -y && sudo apt install -y googletest libgtest-dev libnghttp2-dev libboost-stacktrace-dev libbrotli-dev redis-server nodejs npm nghttp2-client - name: install liburing run: sudo bash ./scripts/install-liburing.sh - name: configure run: phpize && ./configure --enable-sockets --enable-mysqlnd --enable-iouring - name: build run: | cmake . -D CODE_COVERAGE=ON -D enable_thread=1 -D verbose=1 -D async_io=1 || exit 1 make VERBOSE=1 -j $(nproc) core-tests || exit 1 - name: run tests run: | ./run-core-tests.sh - name: run coverage shell: bash run: sudo apt-get install lcov && sudo lcov --directory . --capture --branch-coverage --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --output-file coverage.info && sudo lcov --remove coverage.info '/usr/*' --output-file coverage.info && sudo lcov --list coverage.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.info fail_ci_if_error: true ================================================ FILE: .github/workflows/coverity.yml ================================================ name: coverity-scan on: push: branches: - coverity_scan env: COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} COVERITY_SCAN_EMAIL: team@swoole.com COVERITY_PROJECT: ${{ github.repository }} COV_TOOLS_DIR: ${{ github.workspace }}/cov-analysis-linux64 COV_BUILD_DIR: ${{ github.workspace }} COV_RESULTS_DIR: cov-int COV_RESULTS_FILE: analysis-results.tgz jobs: coverity-scan: if: "github.repository_owner == 'swoole' && (!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[coverity]'))" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "8.3" - name: Download and install Coverity Build Tool run: | wget -q https://scan.coverity.com/download/linux64 \ --post-data "token=${COVERITY_SCAN_TOKEN}&project=${COVERITY_PROJECT}" \ -O cov-analysis-linux64.tar.gz mkdir ${COV_TOOLS_DIR} tar xzf cov-analysis-linux64.tar.gz --strip 1 -C ${COV_TOOLS_DIR} ls -la ${COV_TOOLS_DIR}/bin - name: Run build steps run: | sudo phpize && ./configure --enable-mysqlnd --enable-sockets --enable-swoole-curl - name: Run Coverity Scan Analysis Tool run: | export PATH=${COV_TOOLS_DIR}/bin:$PATH cd ${COV_BUILD_DIR} cov-build --dir ${COV_RESULTS_DIR} make -j $(nproc) - name: Upload Coverity Scan Analysis results run: | cd ${COV_BUILD_DIR} tar czf ${COV_RESULTS_FILE} ${COV_RESULTS_DIR} curl \ --form project=${COVERITY_PROJECT} \ --form token=${COVERITY_SCAN_TOKEN} \ --form email=${COVERITY_SCAN_EMAIL} \ --form file=@${COV_RESULTS_FILE} \ --form version=${GITHUB_SHA} \ --form description="GitHub Actions" \ https://scan.coverity.com/builds?project=${COVERITY_PROJECT} ================================================ FILE: .github/workflows/ext.yml ================================================ name: Compile Tests on: [ push, pull_request ] env: CPPFLAGS: "-I/opt/homebrew/opt/pcre2/include/" jobs: build-ubuntu-latest: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[ubuntu]')" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: install-deps run: sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev - name: phpize run: phpize - name: build1 run: ./configure && make clean && make -j$(nproc) - name: build2 run: ./configure --enable-sockets && make clean && make -j$(nproc) - name: build5 run: ./configure --enable-sockets --enable-mysqlnd && make clean && make -j$(nproc) - name: build6 run: ./configure --enable-sockets --enable-mysqlnd --enable-debug-log && make clean && make -j$(nproc) - name: build7 run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-debug-log && make clean && make -j$(nproc) - name: build8 run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-debug-log && make clean && make -j$(nproc) - name: build with thread context run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-debug-log --enable-thread-context && make clean && make -j$(nproc) build-macos-latest: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[macos]')" runs-on: macos-latest steps: - name: install dependencies run: brew reinstall php - uses: actions/checkout@v6 - name: phpize run: phpize - name: build1 run: ./configure CPPFLAGS="${CPPFLAGS}" && make clean && make -j$(sysctl -n hw.ncpu) - name: build2 run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets && make clean && make -j$(sysctl -n hw.ncpu) - name: build5 run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd && make clean && make -j$(sysctl -n hw.ncpu) - name: build6 run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-debug-log && make clean && make -j$(sysctl -n hw.ncpu) - name: build7 run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-debug-log --enable-cares && make clean && make -j$(sysctl -n hw.ncpu) build-alpine-latest: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[alpine]')" runs-on: ubuntu-latest strategy: matrix: php-version: [ '8.2', '8.3', '8.4', '8.5' ] max-parallel: 8 fail-fast: false steps: - uses: actions/checkout@v6 - name: build run: | cp .github/workflows/alpine.Dockerfile alpine.Dockerfile docker build -t swoole . -f alpine.Dockerfile --build-arg PHP_VERSION=${{ matrix.php-version }} ================================================ FILE: .github/workflows/framework.yml ================================================ name: Framework Tests on: push: pull_request: jobs: linux: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[framework]')" strategy: fail-fast: false matrix: php-version: [ '8.2', '8.3', '8.4', '8.5' ] framework: [ 'Laravel Octane', 'Hyperf', 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: dom, curl, libxml, mbstring, zip, redis, pdo, pdo_mysql, bcmath tools: phpize, composer:v2 ini-values: extension=swoole coverage: none - name: Build Swoole run: | sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev libpq-dev valgrind phpize ./configure --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-swoole-pgsql make -j$(nproc) sudo make install php -v php -m php --ini php --ri swoole - name: Laravel Octane Tests if: matrix.framework == 'Laravel Octane' && matrix.php-version != '8.5' run: | git clone https://github.com/laravel/octane.git --depth=1 cd octane/ composer update --prefer-dist --no-interaction --no-progress vendor/bin/testbench package:sync-skeleton vendor/bin/phpunit --display-deprecations --fail-on-deprecation - name: Hyperf Tests if: matrix.framework == 'Hyperf' && matrix.php-version != '8.5' env: SW_VERSION: 'master' MYSQL_VERSION: '5.7' PGSQL_VERSION: '14' run: | git clone https://github.com/hyperf/hyperf.git --depth=1 cd hyperf/ composer update -o ./.travis/requirement.install.sh ./.travis/setup.services.sh export TRAVIS_BUILD_DIR=$(pwd) && bash ./.travis/setup.mysql.sh export TRAVIS_BUILD_DIR=$(pwd) && bash ./.travis/setup.pgsql.sh cp .travis/.env.example .env export SWOOLE_BRANCH=${GITHUB_REF##*/} if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' bin/co-phpunit --exclude-group NonCoroutine USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' vendor/bin/phpunit --group NonCoroutine USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' vendor/bin/phpunit src/filesystem --group NonCoroutine else .travis/run.test.sh fi - name: Simps Tests if: matrix.framework == 'Simps' run: | git clone https://github.com/simps/mqtt.git --depth=1 cd mqtt/ composer install -o composer test macos: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[framework]')" runs-on: macos-latest strategy: fail-fast: false matrix: php-version: [ '8.2', '8.3', '8.4', '8.5' ] framework: [ 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} - macOS steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: dom, curl, libxml, mbstring, zip, redis, pdo, pdo_mysql, bcmath tools: phpize, composer:v2 ini-values: extension=swoole coverage: none - name: Build Swoole run: | phpize export CPPFLAGS="${CPPFLAGS} -I/opt/homebrew/opt/pcre2/include/" export CFLAGS="${CFLAGS} -I/opt/homebrew/opt/pcre2/include/" ./configure --enable-mysqlnd --enable-swoole-curl --enable-cares make -j$(sysctl -n hw.ncpu) sudo make install php --ri swoole - name: Simps Tests if: matrix.framework == 'Simps' run: | git clone https://github.com/simps/mqtt.git --depth=1 cd mqtt/ composer install -o composer test ================================================ FILE: .github/workflows/iouring.yml ================================================ name: Linux io_uring Tests on: [push, pull_request] jobs: linux: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[iouring]')" strategy: fail-fast: false matrix: php: [ '8.2', '8.3', '8.4', '8.5' ] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-${{ matrix.os }}-iouring runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php }}" coverage: none - name: Show machine information run: | date env uname -a ulimit -a php -v php --ini ls -al pwd echo "`git log -20 --pretty --oneline`" echo "`git log -10 --stat --pretty --oneline`" - name: Run Swoole test run: | export SWOOLE_CI_TYPE=IOURING export SWOOLE_BRANCH=${GITHUB_REF##*/} export SWOOLE_BUILD_DIR=$(realpath .) export PHP_VERSION=${{ matrix.php }} ${SWOOLE_BUILD_DIR}/scripts/route.sh ================================================ FILE: .github/workflows/thread.yml ================================================ name: Thread Support Tests on: [push, pull_request] jobs: linux: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[thread]')" timeout-minutes: 15 strategy: fail-fast: false matrix: php: ['8.2-zts', '8.3-zts', '8.4-zts', '8.5-zts'] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-thread-${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php }}" coverage: none env: phpts: ts - name: Show machine information run: | date env uname -a ulimit -a php -v php --ini ls -al pwd echo "`git log -20 --pretty --oneline`" echo "`git log -10 --stat --pretty --oneline`" - name: Run tests run: | export SWOOLE_CI_TYPE=THREAD export SWOOLE_BRANCH=${GITHUB_REF##*/} export SWOOLE_BUILD_DIR=$(realpath .) export PHP_VERSION=${{ matrix.php }} ${SWOOLE_BUILD_DIR}/scripts/route.sh ================================================ FILE: .github/workflows/unit.yml ================================================ name: Unit Tests on: [push, pull_request] jobs: linux: if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[unit]')" timeout-minutes: 30 strategy: fail-fast: false matrix: php: ['8.2', '8.3', '8.4', '8.5'] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php }}" coverage: none - name: Show machine information run: | date env uname -a ulimit -a php -v php --ini ls -al pwd echo "`git log -20 --pretty --oneline`" echo "`git log -10 --stat --pretty --oneline`" - name: Run unit tests env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION }} run: | export SWOOLE_CI_TYPE=NORMAL export SWOOLE_BRANCH=${GITHUB_REF##*/} export SWOOLE_BUILD_DIR=$(realpath .) export PHP_VERSION=${{ matrix.php }} ${{runner.workspace}}/swoole-src/scripts/route.sh macos: runs-on: macos-latest if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[macos-unit]')" timeout-minutes: 30 strategy: fail-fast: false matrix: # php-version: [ '8.2', '8.3', '8.4', '8.5' ] php-version: [ '8.3' ] name: ${{ matrix.php-version }} - macOS steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: dom, curl, libxml, mbstring, zip, redis, pdo, pdo_mysql, bcmath, sockets tools: phpize, composer:v2 ini-values: extension=swoole coverage: none - name: Install dependencies run: | brew install redis tinyproxy nginx md5sha1sum brew services start redis brew services start nginx brew services start tinyproxy - name: Build Swoole run: | phpize export PCRE2_INCLUDE_DIR="/opt/homebrew/opt/pcre2/include" export CPPFLAGS="${CPPFLAGS} -I${PCRE2_INCLUDE_DIR}" export CFLAGS="${CFLAGS} -I${PCRE2_INCLUDE_DIR}" ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-zstd make -j$(sysctl -n hw.ncpu) sudo make install php --ri swoole uname -a - name: Run unit tests run: | export SWOOLE_CI_TYPE=NORMAL export SWOOLE_BRANCH=${GITHUB_REF##*/} export SWOOLE_BUILD_DIR=$(realpath .) export PHP_VERSION=${{ matrix.php-version }} export SWOOLE_CI_IN_MACOS=1 cd ${{runner.workspace}}/swoole-src ulimit -n 100000 ./scripts/run-tests.sh ================================================ FILE: .gitignore ================================================ /.cproject /.idea /.project *.o *.lo *~ *.so *.loT *.pid *.dep modules/* /.deps /.libs/ /core /examples/core /Debug /Release /CMakeFiles /cmake_install.cmake /CMakeCache.txt /Makefile /bin /lib /library /install_manifest.txt /config.guess /config.h /config.h.in /config.log /config.nice /config.status /config.sub /configure /Makefile.fragments /Makefile.global /Makefile.objects /install-sh /libtool /ltmain.sh /missing /mkinstalldirs /swoole.la /acinclude.m4 /aclocal.m4 /run-tests.php /autom4te.cache /build /examples/async/data.txt /examples/.idea /examples/recv_file.jpg /modules /examples/async/test.copy /examples/ssl_client /examples/ssl/ca.crt /examples/ssl/client.key /examples/ssl/client.pfx /examples/ssl/client /examples/ssl/corpssl.crt /examples/ssl/corpssl.key /examples/ext /benchmark/.idea/ /benchmark/vendor /benchmark/composer.lock /tools/.idea/ /tmp-php.ini .settings/ tests/.idea /tests/swoole_*/*.log /tests/swoole_*/*.sh /tests/swoole_*/*.diff /tests/swoole_*/*.out /tests/swoole_*/*.exp /tests/swoole_*/*.mem /tests/swoole_*/*.php /tests/swoole_library/curl/skip /core-tests/server/*.log cmake-build-debug/ *.cbp /.vscode /.vs /configure.in /configure.ac # core-tests /core-tests/CMakeCache.txt /core-tests/CMakeFiles/ /core-tests/Makefile /core-tests/bin/ /core-tests/cmake_install.cmake /core-tests/samples/Makefile /core-tests/samples/cmake_install\.cmake /core-tests/samples/CMakeCache\.txt /core-tests/samples/bin/ /core-tests/samples/CMakeFiles/ /core-tests/.project /core-tests/.cproject /core-tests/compile_commands.json /core-tests/samples/.project /core-tests/fuzz/fuzz_results/ /core-tests/fuzz/bin/ /tools/vendor /tools/composer.lock /examples/wrapper/CMakeFiles /examples/wrapper/CMakeCache.txt /examples/wrapper/Makefile /examples/wrapper/server /examples/wrapper/cmake_install.cmake /out /gcov_result # coverage *.gcno *.gcov *.gcda *.info /html /tests/include/lib/vendor/ /tests/include/lib/composer.lock /scripts/data /.cmake/ /Testing/ build.ninja ================================================ FILE: .php-cs-fixer.dist.php ================================================ setRiskyAllowed(true) ->setRules([ '@DoctrineAnnotation' => true, '@PhpCsFixer' => true, '@PSR2' => true, '@Symfony' => true, 'align_multiline_comment' => ['comment_type' => 'all_multiline'], 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => ['operators' => ['=' => 'align', '=>' => 'align', ]], 'blank_line_after_namespace' => true, 'blank_line_before_statement' => ['statements' => ['declare']], 'class_attributes_separation' => true, 'concat_space' => ['spacing' => 'one'], 'constant_case' => ['case' => 'lower'], 'combine_consecutive_unsets' => true, 'declare_strict_types' => true, 'fully_qualified_strict_types' => ['phpdoc_tags' => []], 'general_phpdoc_annotation_remove' => ['annotations' => ['author']], 'header_comment' => ['comment_type' => 'PHPDoc', 'header' => $header, 'location' => 'after_open', 'separate' => 'bottom'], 'increment_style' => ['style' => 'post'], 'lambda_not_used_import' => false, 'linebreak_after_opening_tag' => true, 'list_syntax' => ['syntax' => 'short'], 'lowercase_static_reference' => true, 'multiline_comment_opening_closing' => true, 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true, 'remove_inheritdoc' => false], 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'not_operator_with_space' => false, 'not_operator_with_successor_space' => false, 'php_unit_strict' => false, 'phpdoc_align' => ['align' => 'left'], 'phpdoc_annotation_without_dot' => false, 'phpdoc_no_empty_return' => false, 'phpdoc_types_order' => ['sort_algorithm' => 'none', 'null_adjustment' => 'always_last'], 'phpdoc_separation' => false, 'phpdoc_summary' => false, 'ordered_class_elements' => true, 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], 'ordered_types' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], 'single_line_comment_style' => ['comment_types' => []], 'single_line_comment_spacing' => false, 'single_line_empty_body' => false, 'single_quote' => true, 'standardize_increment' => false, 'standardize_not_equals' => true, 'yoda_style' => ['always_move_variable' => false, 'equal' => false, 'identical' => false], ]) ->setFinder( PhpCsFixer\Finder::create() ->exclude(['html', 'vendor']) ->in(__DIR__) ) ->setUsingCache(false); ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(libswoole) enable_language(ASM) set(SWOOLE_VERSION 6.2.0) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g") file(READ ./config.h SWOOLE_CONFIG_FILE) set(CMAKE_MACOSX_RPATH 1) set(SWOOLE_LINK_LIBRARIES pthread dl) if (APPLE) set(CMAKE_SHARED_LINKER_FLAGS "-undefined dynamic_lookup") include_directories(BEFORE /usr/local/include) link_directories(BEFORE /usr/local/lib) else() list(APPEND SWOOLE_LINK_LIBRARIES rt crypt) endif() find_package(PkgConfig REQUIRED) if (UNIX AND NOT APPLE) find_library(URING_LIBRARIES uring) if (URING_LIBRARIES) message(STATUS "Found iouring") list(APPEND SWOOLE_LINK_LIBRARIES ${URING_LIBRARIES}) else() message(WARNING "liburing not found.") endif() endif() if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) endif () # Code Coverage Configuration add_library(coverage_config INTERFACE) option(CODE_COVERAGE "Enable coverage reporting" OFF) if(CODE_COVERAGE) message(STATUS "Open coverage") # --coverage => -fprofile-arcs -ftest-coverage target_compile_options(coverage_config INTERFACE -O0 -g -fprofile-update=atomic --coverage ) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) target_link_options(coverage_config INTERFACE --coverage) else() target_link_libraries(coverage_config INTERFACE --coverage) endif() endif(CODE_COVERAGE) file(GLOB_RECURSE SRC_LIST FOLLOW_SYMLINKS src/*.c src/*.cc thirdparty/boost/asm/combined.S thirdparty/hiredis/alloc.c thirdparty/hiredis/async.c thirdparty/hiredis/hiredis.c thirdparty/hiredis/net.c thirdparty/hiredis/read.c thirdparty/hiredis/sds.c thirdparty/llhttp/api.c thirdparty/llhttp/http.c thirdparty/llhttp/llhttp.c thirdparty/multipart_parser.c ) file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS include/*.h) file(GLOB_RECURSE HEAD_WAPPER_FILES FOLLOW_SYMLINKS include/wrapper/*.hpp) set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) #message(STATUS "source=${SRC_LIST}") #message(STATUS "header=${HEAD_FILES}") add_definitions(-DHAVE_CONFIG_H) # test #add_definitions(-DSW_USE_THREAD_CONTEXT) include_directories(BEFORE ./include ./include/wrapper ext-src/ thirdparty/ thirdparty/llhttp ./) # find OpenSSL if (DEFINED openssl_dir) include_directories(BEFORE ${openssl_dir}/include) link_directories(${openssl_dir}/lib) else() find_package(OpenSSL) if (${OPENSSL_FOUND}) message(STATUS "Found OpenSSL, ${OPENSSL_LIBRARIES}") include_directories(BEFORE ${OPENSSL_INCLUDE_DIR}) list(APPEND SWOOLE_LINK_LIBRARIES ssl crypto) else() message(STATUS "Not found OpenSSL") endif() endif() if (DEFINED brotli_dir) include_directories(BEFORE ${brotli_dir}/include) link_directories(${brotli_dir}/lib) endif() foreach (LINE ${SWOOLE_CONFIG_FILE}) if ("${LINE}" MATCHES "define SW_USE_CARES 1") message(STATUS "enable c-ares") list(APPEND SWOOLE_LINK_LIBRARIES cares) endif() endforeach() if (DEFINED enable_trace_log) add_definitions(-DSW_LOG_TRACE_OPEN) endif() if (DEFINED enable_asan) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") add_definitions(-DSW_USE_ASAN) endif() if (DEFINED enable_thread) add_definitions(-DSW_THREAD) endif() if (DEFINED async_io) # This macro is exclusively used for kernel unit testing to # verify the asynchronous operation functionality of the file thread pool. add_definitions(-DSW_USE_ASYNC) endif() if (DEFINED verbose) add_definitions(-DSW_VERBOSE) endif() set(php_dir "" CACHE STRING "Set the root directory of PHP") if (DEFINED php_dir) set(PHP_CONFIG "${php_dir}/bin/php-config") else () set(PHP_CONFIG "php-config") endif() execute_process(COMMAND ${PHP_CONFIG} --includes OUTPUT_VARIABLE PHP_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE PHP_CONFIG_RESULT) if (NOT PHP_CONFIG_RESULT EQUAL 0) message(FATAL_ERROR "Failed to execute php-config: ${PHP_CONFIG_RESULT}") endif() execute_process(COMMAND ${PHP_CONFIG} --extension-dir OUTPUT_VARIABLE PHP_EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE PHP_CONFIG_RESULT) if (NOT PHP_CONFIG_RESULT EQUAL 0) message(FATAL_ERROR "Failed to execute php-config: ${PHP_CONFIG_RESULT}") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PHP_INCLUDES}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PHP_INCLUDES}") if (CMAKE_SYSTEM_NAME MATCHES "Linux") execute_process(COMMAND ldconfig -p OUTPUT_VARIABLE LDCONFIG_LIST OUTPUT_STRIP_TRAILING_WHITESPACE) #message(STATUS LDCONFIG_LIST) if (LDCONFIG_LIST MATCHES brotlienc) list(APPEND SWOOLE_LINK_LIBRARIES brotlienc) endif() if (LDCONFIG_LIST MATCHES brotlidec) list(APPEND SWOOLE_LINK_LIBRARIES brotlidec) endif() endif() # lib-swoole link_directories(${LIBRARY_OUTPUT_PATH}) add_library(lib-swoole SHARED ${SRC_LIST}) set_target_properties(lib-swoole PROPERTIES OUTPUT_NAME "swoole" VERSION ${SWOOLE_VERSION}) target_link_libraries(lib-swoole ${SWOOLE_LINK_LIBRARIES}) if (CODE_COVERAGE) target_link_libraries(lib-swoole coverage_config gcov) endif(CODE_COVERAGE) # test_server set(TEST_SRC_LIST examples/cpp/test_server.cc) add_executable(test_server ${TEST_SRC_LIST}) add_dependencies(test_server lib-swoole) target_link_libraries(test_server swoole pthread) # co set(TEST_SRC_LIST examples/cpp/co.cc) add_executable(co ${TEST_SRC_LIST}) add_dependencies(co lib-swoole) target_link_libraries(co swoole) # ext-swoole file(GLOB ext_cxx_files ext-src/*.cc) set(ext_src_list ${ext_cxx_files} thirdparty/php/curl/interface.cc thirdparty/php/curl/multi.cc thirdparty/php84/curl/interface.cc thirdparty/php84/curl/multi.cc thirdparty/php/sockets/multicast.cc thirdparty/php/sockets/sendrecvmsg.cc thirdparty/php/sockets/conversions.cc thirdparty/php/sockets/sockaddr_conv.cc thirdparty/php/standard/proc_open.cc thirdparty/php/standard/var_decoder.cc thirdparty/nghttp2/nghttp2_hd.c thirdparty/nghttp2/nghttp2_rcbuf.c thirdparty/nghttp2/nghttp2_helper.c thirdparty/nghttp2/nghttp2_buf.c thirdparty/nghttp2/nghttp2_mem.c thirdparty/nghttp2/nghttp2_hd_huffman.c thirdparty/nghttp2/nghttp2_hd_huffman_data.c ) add_library(ext-swoole SHARED ${ext_src_list}) set_target_properties(ext-swoole PROPERTIES PREFIX "") set_target_properties(ext-swoole PROPERTIES OUTPUT_NAME "swoole") add_dependencies(ext-swoole lib-swoole) # core-tests pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) if (${NGHTTP2_FOUND}) message(STATUS "Found nghttp2") else() message(STATUS "Not found nghttp2") endif() # find GTest find_package(GTest REQUIRED) if (!${GTEST_FOUND}) message(FATAL_ERROR "Not found GTest") endif() message(STATUS "Found GTest") file(GLOB_RECURSE core_test_files core-tests/src/*.cpp thirdparty/llhttp/*.c) add_executable(core-tests ${core_test_files}) add_dependencies(core-tests lib-swoole) include_directories(BEFORE core-tests/include thirdparty thirdparty/hiredis thirdparty/llhttp/ ${GTEST_INCLUDE_DIRS} ${NGHTTP2_INCLUDE_DIR}) target_link_libraries(core-tests swoole pthread ssl crypto ${GTEST_BOTH_LIBRARIES} ${NGHTTP2_LIBRARIES}) # find libpq if (DEFINED libpq_dir) include_directories(BEFORE ${libpq_dir}/include) link_directories(${libpq_dir}/lib) else() pkg_check_modules(LIBPQ REQUIRED libpq) target_include_directories(ext-swoole PRIVATE ${LIBPQ_INCLUDE_DIRS}) endif() target_link_libraries(ext-swoole swoole pq curl) # install INSTALL(TARGETS ext-swoole LIBRARY DESTINATION ${PHP_EXTENSION_DIR}) INSTALL(TARGETS lib-swoole LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) INSTALL(FILES ${HEAD_FILES} DESTINATION include/swoole) INSTALL(FILES ${HEAD_WAPPER_FILES} DESTINATION include/swoole/wrapper) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/config.h DESTINATION include/swoole) ================================================ FILE: Dockerfile ================================================ # This Dockerfile is designed to create a debug version of the PHP and Swoole environment, # enabling ASAN (`--enable-address-sanitizer`) to facilitate debugging and analysis of runtime crashes. FROM ubuntu:22.04 ARG PHP_VERSION=8.2.28 RUN apt update RUN apt install -y g++ cmake automake wget pkg-config git xz-utils RUN apt install -y libssl-dev libcurl4-openssl-dev libxml2-dev libzip-dev libsqlite3-dev libreadline-dev libonig-dev \ libbz2-dev libffi-dev libxslt-dev unixodbc-dev libpq-dev libbrotli-dev libc-ares-dev RUN mkdir /work WORKDIR /work RUN wget https://www.php.net/distributions/php-${PHP_VERSION}.tar.xz RUN tar -xvf php-${PHP_VERSION}.tar.xz COPY . /work/php-${PHP_VERSION}/ext/swoole RUN cd php-${PHP_VERSION} && ./buildconf --force && \ ./configure --enable-mbstring --with-curl --with-openssl \ --enable-soap --enable-intl --enable-bcmath --enable-sockets \ --with-pear --with-webp --with-jpeg --with-ffi \ --enable-sysvsem --enable-sysvshm --enable-sysvmsg --with-zlib --with-bz2 --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-xsl \ --without-pdo-sqlite \ --enable-debug --enable-address-sanitizer \ --enable-swoole \ --enable-swoole-curl \ --enable-swoole-pgsql \ --enable-swoole-sqlite \ --enable-mysqlnd \ --enable-cares \ --with-swoole-odbc=unixODBC,/usr \ --enable-brotli && \ make clean && make -j $(nproc) && make install RUN php -v RUN php -m RUN php --ri swoole RUN php --ri curl RUN php --ri openssl RUN cd /work/php-${PHP_VERSION} && make clean RUN cd /work && rm php-${PHP_VERSION}.tar.xz && rm -rf php-${PHP_VERSION}/ext/swoole/.git RUN rm -rf /var/lib/apt/lists/* /usr/bin/qemu-*-static ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright Tianfeng.Han [rango@swoole.com] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile.frag ================================================ swoole-build-coverage: CCACHE_DISABLE=1 EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" EXTRA_CXXFLAGS="-fprofile-arcs -ftest-coverage" $(MAKE) swoole-test-coverage: CCACHE_DISABLE=1 EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" EXTRA_CXXFLAGS="-fprofile-arcs -ftest-coverage" $(MAKE) && $(MAKE) install && $(top_srcdir)/tests/start.sh $(top_srcdir)/tests swoole-test-coverage-lcov: swoole-test-coverage lcov -c --directory $(top_srcdir)/.libs --output-file $(top_srcdir)/coverage.info swoole-test-coverage-html: swoole-test-coverage-lcov genhtml $(top_srcdir)/coverage.info --output-directory=$(top_srcdir)/html ================================================ FILE: README.md ================================================

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

[![Compiler Tests](https://github.com/swoole/swoole-src/actions/workflows/ext.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/ext.yml) [![Core Test](https://github.com/swoole/swoole-src/actions/workflows/core.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/core.yml) [![Unit Tests](https://github.com/swoole/swoole-src/actions/workflows/unit.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/unit.yml) [![Thread Support Tests](https://github.com/swoole/swoole-src/actions/workflows/thread.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/thread.yml) [![Linux io_uring Tests](https://github.com/swoole/swoole-src/actions/workflows/iouring.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/iouring.yml) [![Frameworks Tests](https://github.com/swoole/swoole-src/actions/workflows/framework.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/framework.yml) [![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/phpswoole) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.swoole.dev) [![Latest Release](https://img.shields.io/github/release/swoole/swoole-src.svg)](https://github.com/swoole/swoole-src/releases/) [![License](https://badgen.net/github/license/swoole/swoole-src)](https://github.com/swoole/swoole-src/blob/master/LICENSE) [![Coverity Scan Build Status](https://scan.coverity.com/projects/11654/badge.svg)](https://scan.coverity.com/projects/swoole-swoole-src) [![Codecov](https://codecov.io/gh/swoole/swoole-src/branch/master/graph/badge.svg)](https://codecov.io/gh/swoole/swoole-src) ## ⚙️ Quick Start Run Swoole program by [Docker](https://github.com/swoole/docker-swoole) ```bash docker run --rm phpswoole/swoole "php --ri swoole" ``` > For details on how to use it, see: [How to Use This Image](https://github.com/swoole/docker-swoole#how-to-use-this-image). ## Documentation ### HTTP Service ```php $http = new Swoole\Http\Server('127.0.0.1', 9501); $http->set(['hook_flags' => SWOOLE_HOOK_ALL]); $http->on('request', function ($request, $response) { $result = []; Co::join([ go(function () use (&$result) { $result['google'] = file_get_contents("https://www.google.com/"); }), go(function () use (&$result) { $result['taobao'] = file_get_contents("https://www.taobao.com/"); }) ]); $response->end(json_encode($result)); }); $http->start(); ``` ### Concurrency ```php Co\run(function() { Co\go(function() { while(1) { sleep(1); $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); echo fread($fp, 8192), PHP_EOL; } }); Co\go(function() { $fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); while(1) { $conn = stream_socket_accept($fp); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); } }); Co\go(function() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); while(true) { $redis->subscribe(['test'], function ($instance, $channelName, $message) { echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL; }); } }); Co\go(function() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $count = 0; while(true) { sleep(2); $redis->publish('test','hello, world, count='.$count++); } }); }); ``` ## Runtime Hook **Swoole hooks the blocking io function of PHP at the `bottom layer` and `automatically` converts it to a non-blocking function, so that these functions can be called concurrently in coroutines.** ### Supported extension/functions * `ext-curl` (Support `symfony` and `guzzle`) * `ext-redis` * `ext-mysqli` * `ext-pdo_mysql` * `ext-pdo_pgsql` * `ext-pdo_sqlite` * `ext-pdo_oracle` * `ext-pdo_odbc` * `stream functions` (e.g. `stream_socket_client`/`stream_socket_server`), Supports `TCP`/`UDP`/`UDG`/`Unix`/`SSL/TLS`/`FileSystem API`/`Pipe` * `ext-sockets` * `ext-soap` * `sleep`/`usleep`/`time_sleep_until` * `proc_open` * `gethostbyname`/`shell_exec`/`exec` * `fread`/`fopen`/`fsockopen`/`fwrite`/`flock` ## 🛠 Develop & Discussion + __IDE Helper & API__: + __Twitter__: + __Discord__: + __中文社区__: ## 💎 Awesome Swoole Project [Awesome Swoole](https://github.com/swoole/awesome-swoole) maintains a curated list of awesome things related to Swoole, including * Swoole-based frameworks and libraries. * Packages to integrate Swoole with popular PHP frameworks, including Laravel, Symfony, Slim, and Yii. * Books, videos, and other learning materials about Swoole. * Debugging, profiling, and testing tools for developing Swoole-based applications. * Coroutine-friendly packages and libraries. * Other Swoole related projects and resources. ## ✨ Event-based The network layer in Swoole is event-based and takes full advantage of the underlying epoll/kqueue implementation, making it really easy to serve millions of requests. Swoole 4.x uses a brand new engine kernel and now it has a full-time developer team, so we are entering an unprecedented period in PHP history which offers a unique possibility for rapid evolution in performance. ## ⚡ Coroutine Swoole 4.x or later supports the built-in coroutine with high availability, and you can use fully synchronized code to implement asynchronous performance. PHP code without any additional keywords, the underlying automatic coroutine-scheduling. Developers can understand coroutines as ultra-lightweight threads, and you can easily create thousands of coroutines in a single process. ### MySQL Concurrency 10K requests to read data from MySQL takes only 0.2s! ```php $s = microtime(true); Co\run(function() { for ($c = 100; $c--;) { go(function () { $mysql = new Swoole\Coroutine\MySQL; $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test' ]); $statement = $mysql->prepare('SELECT * FROM `user`'); for ($n = 100; $n--;) { $result = $statement->execute(); assert(count($result) > 0); } }); } }); echo 'use ' . (microtime(true) - $s) . ' s'; ``` ### Mixed server You can create multiple services on the single event loop: TCP, HTTP, Websocket and HTTP2, and easily handle thousands of requests. ```php function tcp_pack(string $data): string { return pack('N', strlen($data)) . $data; } function tcp_unpack(string $data): string { return substr($data, 4, unpack('N', substr($data, 0, 4))[1]); } $tcp_options = [ 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4 ]; ``` ```php $server = new Swoole\WebSocket\Server('127.0.0.1', 9501, SWOOLE_BASE); $server->set(['open_http2_protocol' => true]); // http && http2 $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('Hello ' . $request->rawcontent()); }); // websocket $server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $server->push($frame->fd, 'Hello ' . $frame->data); }); // tcp $tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP); $tcp_server->set($tcp_options); $tcp_server->on('receive', function (Swoole\Server $server, int $fd, int $reactor_id, string $data) { $server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); }); $server->start(); ``` ### Coroutine clients Whether you DNS query or send requests or receive responses, all of these are scheduled by coroutine automatically. ```php go(function () { // http $http_client = new Swoole\Coroutine\Http\Client('127.0.0.1', 9501); assert($http_client->post('/', 'Swoole Http')); var_dump($http_client->body); // websocket $http_client->upgrade('/'); $http_client->push('Swoole Websocket'); var_dump($http_client->recv()->data); }); go(function () { // http2 $http2_client = new Swoole\Coroutine\Http2\Client('localhost', 9501); $http2_client->connect(); $http2_request = new Swoole\Http2\Request; $http2_request->method = 'POST'; $http2_request->data = 'Swoole Http2'; $http2_client->send($http2_request); $http2_response = $http2_client->recv(); var_dump($http2_response->data); }); go(function () use ($tcp_options) { // tcp $tcp_client = new Swoole\Coroutine\Client(SWOOLE_TCP); $tcp_client->set($tcp_options); $tcp_client->connect('127.0.0.1', 9502); $tcp_client->send(tcp_pack('Swoole Tcp')); var_dump(tcp_unpack($tcp_client->recv())); }); ``` ### Channel Channel is the only way for exchanging data between coroutines, the development combination of the `Coroutine + Channel` is the famous CSP programming model. In Swoole development, Channel is usually used for implementing connection pool or scheduling coroutine concurrent. #### The simplest example of a connection pool In the following example, we have a thousand concurrently requests to redis. Normally, this has exceeded the maximum number of Redis connections setting and will throw a connection exception, but the connection pool based on Channel can perfectly schedule requests. We don't have to worry about connection overload. ```php class RedisPool { /**@var \Swoole\Coroutine\Channel */ protected $pool; /** * RedisPool constructor. * @param int $size max connections */ public function __construct(int $size = 100) { $this->pool = new \Swoole\Coroutine\Channel($size); for ($i = 0; $i < $size; $i++) { $redis = new \Swoole\Coroutine\Redis(); $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { throw new \RuntimeException("failed to connect redis server."); } else { $this->put($redis); } } } public function get(): \Swoole\Coroutine\Redis { return $this->pool->pop(); } public function put(\Swoole\Coroutine\Redis $redis) { $this->pool->push($redis); } public function close(): void { $this->pool->close(); $this->pool = null; } } go(function () { $pool = new RedisPool(); // max concurrency num is more than max connections // but it's no problem, channel will help you with scheduling for ($c = 0; $c < 1000; $c++) { go(function () use ($pool, $c) { for ($n = 0; $n < 100; $n++) { $redis = $pool->get(); assert($redis->set("awesome-{$c}-{$n}", 'swoole')); assert($redis->get("awesome-{$c}-{$n}") === 'swoole'); assert($redis->delete("awesome-{$c}-{$n}")); $pool->put($redis); } }); } }); ``` #### Producer and consumers Some Swoole's clients implement the defer mode for concurrency, but you can still implement it flexible with a combination of coroutines and channels. ```php go(function () { // User: I need you to bring me some information back. // Channel: OK! I will be responsible for scheduling. $channel = new Swoole\Coroutine\Channel; go(function () use ($channel) { // Coroutine A: Ok! I will show you the github addr info $addr_info = Co::getaddrinfo('github.com'); $channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); }); go(function () use ($channel) { // Coroutine B: Ok! I will show you what your code look like $mirror = Co::readFile(__FILE__); $channel->push(['B', $mirror]); }); go(function () use ($channel) { // Coroutine C: Ok! I will show you the date $channel->push(['C', date(DATE_W3C)]); }); for ($i = 3; $i--;) { list($id, $data) = $channel->pop(); echo "From {$id}:\n {$data}\n"; } // User: Amazing, I got every information at earliest time! }); ``` ### Timer ```php $id = Swoole\Timer::tick(100, function () { echo "⚙️ Do something...\n"; }); Swoole\Timer::after(500, function () use ($id) { Swoole\Timer::clear($id); echo "⏰ Done\n"; }); Swoole\Timer::after(1000, function () use ($id) { if (!Swoole\Timer::exists($id)) { echo "✅ All right!\n"; } }); ``` #### The way of coroutine ```php go(function () { $i = 0; while (true) { Co::sleep(0.1); echo "📝 Do something...\n"; if (++$i === 5) { echo "🛎 Done\n"; break; } } echo "🎉 All right!\n"; }); ``` ## 🔥 Amazing runtime hooks **As of Swoole v4.1.0, we added the ability to transform synchronous PHP network libraries into co-routine libraries using a single line of code.** Simply call the `Swoole\Runtime::enableCoroutine()` method at the top of your script. In the sample below we connect to php-redis and concurrently read 10k requests in 0.1s: ```php Swoole\Runtime::enableCoroutine(); $s = microtime(true); Co\run(function() { for ($c = 100; $c--;) { go(function () { ($redis = new Redis)->connect('127.0.0.1', 6379); for ($n = 100; $n--;) { assert($redis->get('awesome') === 'swoole'); } }); } }); echo 'use ' . (microtime(true) - $s) . ' s'; ``` By calling this method, the Swoole kernel replaces ZendVM stream function pointers. If you use `php_stream` based extensions, all socket operations can be dynamically converted to be asynchronous IO scheduled by coroutine at runtime! ### How many things you can do in 1s? Sleep 10K times, read, write, check and delete files 10K times, use PDO and MySQLi to communicate with the database 10K times, create a TCP server and multiple clients to communicate with each other 10K times, create a UDP server and multiple clients to communicate with each other 10K times... Everything works well in one process! Just see what the Swoole brings, just imagine... ```php Swoole\Runtime::enableCoroutine(); $s = microtime(true); Co\run(function() { // i just want to sleep... for ($c = 100; $c--;) { go(function () { for ($n = 100; $n--;) { usleep(1000); } }); } // 10K file read and write for ($c = 100; $c--;) { go(function () use ($c) { $tmp_filename = "/tmp/test-{$c}.php"; for ($n = 100; $n--;) { $self = file_get_contents(__FILE__); file_put_contents($tmp_filename, $self); assert(file_get_contents($tmp_filename) === $self); } unlink($tmp_filename); }); } // 10K pdo and mysqli read for ($c = 50; $c--;) { go(function () { $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root'); $statement = $pdo->prepare('SELECT * FROM `user`'); for ($n = 100; $n--;) { $statement->execute(); assert(count($statement->fetchAll()) > 0); } }); } for ($c = 50; $c--;) { go(function () { $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test'); $statement = $mysqli->prepare('SELECT `id` FROM `user`'); for ($n = 100; $n--;) { $statement->bind_result($id); $statement->execute(); $statement->fetch(); assert($id > 0); } }); } // php_stream tcp server & client with 12.8K requests in single process function tcp_pack(string $data): string { return pack('n', strlen($data)) . $data; } function tcp_length(string $head): int { return unpack('n', $head)[1]; } go(function () { $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]); $socket = stream_socket_server( 'tcp://0.0.0.0:9502', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx ); if (!$socket) { echo "$errstr ($errno)\n"; } else { $i = 0; while ($conn = stream_socket_accept($socket, 1)) { stream_set_timeout($conn, 5); for ($n = 100; $n--;) { $data = fread($conn, tcp_length(fread($conn, 2))); assert($data === "Hello Swoole Server #{$n}!"); fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!")); } if (++$i === 128) { fclose($socket); break; } } } }); for ($c = 128; $c--;) { go(function () { $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1); if (!$fp) { echo "$errstr ($errno)\n"; } else { stream_set_timeout($fp, 5); for ($n = 100; $n--;) { fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!")); $data = fread($fp, tcp_length(fread($fp, 2))); assert($data === "Hello Swoole Client #{$n}!"); } fclose($fp); } }); } // udp server & client with 12.8K requests in single process go(function () { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); $socket->bind('127.0.0.1', 9503); $client_map = []; for ($c = 128; $c--;) { for ($n = 0; $n < 100; $n++) { $recv = $socket->recvfrom($peer); $client_uid = "{$peer['address']}:{$peer['port']}"; $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1; assert($recv === "Client: Hello #{$id}!"); $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!"); } } $socket->close(); }); for ($c = 128; $c--;) { go(function () { $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1); if (!$fp) { echo "$errstr ($errno)\n"; } else { for ($n = 0; $n < 100; $n++) { fwrite($fp, "Client: Hello #{$n}!"); $recv = fread($fp, 1024); list($address, $port) = explode(':', (stream_socket_get_name($fp, true))); assert($address === '127.0.0.1' && (int)$port === 9503); assert($recv === "Server: Hello #{$n}!"); } fclose($fp); } }); } }); echo 'use ' . (microtime(true) - $s) . ' s'; ``` ## ⌛️ Installation > As with any open source project, Swoole always provides the most reliable stability and the most powerful features in **the latest released version**. Please ensure as much as possible that you are using the latest version. ### Compiling requirements + Linux, OS X or Cygwin, WSL + PHP 8.1.0 or later (The higher the version, the better the performance.) + GCC 4.8 or later ### 1. Install via PECL (beginners) ```shell pecl install swoole ``` ### 2. Install from source (recommended) Please download the source packages from [Releases](https://github.com/swoole/swoole-src/releases) or clone a specific version. Don't use `master` branch as it may be in development. To clone the source code from git specify a tag: ```shell git clone --branch v6.0.0 --single-branch https://github.com/swoole/swoole-src.git && \ cd swoole-src ``` Compile and install at the source folder: ```shell phpize && \ ./configure && \ make && make install ``` #### Enable extension in PHP After compiling and installing to the system successfully, you have to add a new line `extension=swoole.so` to `php.ini` to enable Swoole extension. #### Extra compiler configurations > for example: `./configure --enable-openssl --enable-sockets` + `--enable-openssl` or `--with-openssl-dir=DIR` + `--enable-sockets` + `--enable-mysqlnd` (need mysqlnd, it just for supporting `$mysql->escape` method) + `--enable-swoole-curl` ### Upgrade > ⚠️ If you upgrade from source, don't forget to `make clean` before you upgrade your swoole 1. `pecl upgrade swoole` 2. `cd swoole-src && git pull && make clean && make && sudo make install` 3. if you change your PHP version, please re-run `phpize clean && phpize` then try to compile ### Major change since version 4.3.0 Async clients and API are moved to a separate PHP extension `swoole_async` since version 4.3.0, install `swoole_async`: ```shell git clone https://github.com/swoole/ext-async.git cd ext-async phpize ./configure make -j 4 sudo make install ``` Enable it by adding a new line `extension=swoole_async.so` to `php.ini`. ## 🍭 Benchmark + On the open source [Techempower Web Framework benchmarks](https://www.techempower.com/benchmarks/#section=data-r17) Swoole used MySQL database benchmark to rank first, and all performance tests ranked in the first echelon. + You can just run [Benchmark Script](https://github.com/swoole/benchmark/blob/master/benchmark.php) to quickly test the maximum QPS of Swoole-HTTP-Server on your machine. ## 🔰️ Security issues Security issues should be reported privately, via email, to the Swoole develop team [team@swoole.com](mailto:team@swoole.com). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. ## 🖊️ Contribution Your contribution to Swoole development is very welcome! You may contribute in the following ways: * [Report issues and feedback](https://github.com/swoole/swoole-src/issues) * Submit fixes, features via Pull Request * Write/polish documentation ## ❤️ Contributors This project exists thanks to all the people who contribute. [[Contributors](https://github.com/swoole/swoole-src/graphs/contributors)]. ## 🎙️ Official Evangelist [Demin](https://deminy.in) has been playing with PHP since 2000, focusing on building high-performance, secure web services. He is an occasional conference speaker on PHP and Swoole, and has been working for companies in the states like eBay, Visa and Glu Mobile for years. You may find Demin on [Twitter](https://twitter.com/deminy) or [GitHub](https://github.com/deminy). ## 📃 License Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html ================================================ FILE: codecov.yml ================================================ ignore: - "src/core/error.cc" - "thirdparty/hiredis/*" - "thirdparty/llhttp/*" coverage: range: 60..80 round: down precision: 2 ================================================ FILE: composer.json ================================================ { "name": "swoole/swoole", "type": "php-ext", "license": "Apache-2.0", "description": "Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP.", "require": { "php": ">=8.2 <8.6", "ext-pdo": "*" }, "suggest": { "ext-pdo_mysql": "PDO_MYSQL is required for Swoole to work with MySQL", "ext-pdo_pgsql": "PDO_PGSQL is required for Swoole to work with PostgreSQL", "ext-curl": "CURL is required for Swoole to work with HTTP", "ext-sockets": "Sockets is required for Swoole to work with Socket", "ext-openssl": "OpenSSL is required for Swoole to work with HTTPS", "ext-posix": "*" }, "php-ext": { "extension-name": "swoole", "configure-options": [ { "name": "enable-sockets", "description": "Enable sockets support" }, { "name": "with-openssl-dir", "description": "Specify openssl installation directory (requires OpenSSL 1.1.0 or later)?", "needs-value": true }, { "name": "enable-mysqlnd", "description": "Enable mysqlnd support" }, { "name": "enable-swoole-curl", "description": "Enable curl support" }, { "name": "enable-cares", "description": "Enable cares support" }, { "name": "enable-brotli", "description": "Enable brotli support" }, { "name": "with-brotli-dir", "description": "Specify brotli installation directory?", "needs-value": true }, { "name": "enable-swoole-pgsql", "description": "Enable PostgreSQL database support" }, { "name": "with-swoole-odbc", "description": "Enable ODBC database support", "needs-value": true }, { "name": "with-swoole-oracle", "description": "Enable Oracle database support", "needs-value": true }, { "name": "enable-swoole-sqlite", "description": "Enable Sqlite database support" }, { "name": "with-swoole-firebird", "description": "Enable Firebird database support", "needs-value": true }, { "name": "enable-swoole-thread", "description": "Enable swoole thread support (need php zts support)" }, { "name": "enable-iouring", "description": "Enable iouring for file async support" }, { "name": "with-liburing-dir", "description": "Specify liburing installation directory (requires liburing 2.8 or later)", "needs-value": true }, { "name": "enable-uring-socket", "description": "Enable iouring for http coroutine server support" }, { "name": "with-swoole-ssh2", "description": "Enable async ssh2 client support", "needs-value": true }, { "name": "enable-swoole-ftp", "description": "Enable async ssh2 client support" }, { "name": "enable-zstd", "description": "Enable zstd support (requires zstd 1.4.0 or later)" } ] } } ================================================ FILE: config.m4 ================================================ dnl $Id$ dnl config.m4 for extension swoole dnl +----------------------------------------------------------------------+ dnl | Swoole | dnl +----------------------------------------------------------------------+ dnl | This source file is subject to version 2.0 of the Apache license, | dnl | that is bundled with this package in the file LICENSE, and is | dnl | available through the world-wide-web at the following url: | dnl | http://www.apache.org/licenses/LICENSE-2.0.html | dnl | If you did not receive a copy of the Apache2.0 license and are unable| dnl | to obtain it through the world-wide-web, please send a note to | dnl | license@swoole.com so we can mail you a copy immediately. | dnl +----------------------------------------------------------------------+ dnl | Author: Tianfeng Han | dnl | Author: Twosee | dnl +----------------------------------------------------------------------+ PHP_ARG_ENABLE([debug-log], [enable debug log], [AS_HELP_STRING([--enable-debug-log], [Enable swoole debug log])], [no], [no]) PHP_ARG_ENABLE([trace-log], [enable trace log], [AS_HELP_STRING([--enable-trace-log], [Enable swoole trace log])], [no], [no]) PHP_ARG_ENABLE([sockets], [enable sockets support], [AS_HELP_STRING([--enable-sockets], [Do you have sockets extension?])], [no], [no]) PHP_ARG_ENABLE([swoole], [swoole support], [AS_HELP_STRING([--enable-swoole], [Enable swoole support])], [enable_swoole="yes"]) PHP_ARG_ENABLE([mysqlnd], [enable mysqlnd support], [AS_HELP_STRING([--enable-mysqlnd], [Enable mysqlnd])], [no], [no]) PHP_ARG_ENABLE([cares], [enable c-ares support], [AS_HELP_STRING([--enable-cares], [Enable cares])], [no], [no]) PHP_ARG_ENABLE([iouring], [enable io-uring support], [AS_HELP_STRING([--enable-iouring], [Enable io-uring])], [no], [no]) PHP_ARG_WITH([liburing_dir], [dir of liburing], [AS_HELP_STRING([[--with-liburing-dir[=DIR]]], [Include liburing support (requires liburing >= 2.13)])], [no], [no]) PHP_ARG_ENABLE([uring_socket], [enable uring_socket support], [AS_HELP_STRING([--enable-uring-socket], [Enable iouring socket support])], [no], [no]) PHP_ARG_WITH([openssl_dir], [dir of openssl], [AS_HELP_STRING([[--with-openssl-dir[=DIR]]], [Include OpenSSL support (requires OpenSSL >= 1.1.0)])], [no], [no]) PHP_ARG_ENABLE([brotli], [enable brotli support], [AS_HELP_STRING([[--enable-brotli]], [Use brotli])], [auto], [no]) PHP_ARG_WITH([brotli_dir], [dir of brotli], [AS_HELP_STRING([[--with-brotli-dir[=DIR]]], [Include Brotli support])], [no], [no]) PHP_ARG_ENABLE([zstd], [enable zstd support], [AS_HELP_STRING([[--enable-zstd]], [Use zstd])], [no], [no]) PHP_ARG_WITH([nghttp2_dir], [dir of nghttp2], [AS_HELP_STRING([[--with-nghttp2-dir[=DIR]]], [Include nghttp2 support])], [no], [no]) PHP_ARG_ENABLE([asan], [enable asan], [AS_HELP_STRING([--enable-asan], [Enable asan])], [no], [no]) PHP_ARG_ENABLE([swoole-coverage], [whether to enable swoole coverage support], [AS_HELP_STRING([--enable-swoole-coverage], [Enable swoole coverage support])], [no], [no]) PHP_ARG_ENABLE([swoole-dev], [whether to enable Swoole developer build flags], [AS_HELP_STRING([--enable-swoole-dev], [Enable developer flags])], [no], [no]) PHP_ARG_ENABLE([swoole-curl], [whether to enable Swoole CURL build flags], [AS_HELP_STRING([--enable-swoole-curl], [Enable cURL support])], [no], [no]) PHP_ARG_ENABLE([swoole-pgsql], [whether to enable postgresql build flags], [AS_HELP_STRING([--enable-swoole-pgsql], [Enable postgresql support])], [no], [no]) PHP_ARG_WITH([swoole-firebird], [whether to enable firebird build flags], [AS_HELP_STRING([[--with-swoole-firebird[=DIR]]], [PDO: Async Firebird support. DIR is the Firebird base install directory [/opt/firebird]])], [no], [no]) PHP_ARG_WITH([swoole-ssh2], [whether to enable ssh2 support], [AS_HELP_STRING([[--with-swoole-ssh2[=DIR]]], [Enable Async ssh2 support. DIR is the libssh2 base install directory [/usr]])], [no], [no]) PHP_ARG_ENABLE([swoole-ftp], [whether to enable Async FTP support], [AS_HELP_STRING([--enable-swoole-ftp], [Enable Async FTP support])], [no], [no]) PHP_ARG_ENABLE([thread-context], [whether to enable thread context], [AS_HELP_STRING([--enable-thread-context], [Use thread context])], [no], [no]) PHP_ARG_ENABLE([swoole-thread], [whether to enable swoole thread support], [AS_HELP_STRING([--enable-swoole-thread], [Enable swoole thread support])], [no], [no]) PHP_ARG_ENABLE([swoole-stdext], [whether to enable swoole stdext support], [AS_HELP_STRING([--enable-swoole-stdext], [Enable swoole stdext support("[Experimental] This module is only used for swoole-cli. If you are unsure which feature you need, keep it disabled")])], [no], [no]) PHP_ARG_ENABLE([swoole-coro-time], [whether to enable coroutine execution time ], [AS_HELP_STRING([--enable-swoole-coro-time], [Calculating coroutine execution time])], [no], [no]) define([PDO_ODBC_HELP_TEXT],[[ The include and lib dirs are looked for under 'dir'. The 'flavour' can be one of: ibm-db2, iODBC, unixODBC, generic. If ',dir' part is omitted, default for the flavour you have selected will be used. e.g.: --with-swoole-odbc=unixODBC will check for unixODBC under /usr/local. You may attempt to use an otherwise unsupported driver using the 'generic' flavour. The syntax for generic ODBC support is: --with-swoole-odbc=generic,dir,libname,ldflags,cflags. When built as 'shared' the extension filename is always pdo_odbc.so]]) PHP_ARG_WITH([swoole-odbc], ["for ODBC v3 support for PDO"], [AS_HELP_STRING([--with-swoole-odbc=flavour,dir], ["PDO: Support for 'flavour' ODBC driver."]PDO_ODBC_HELP_TEXT)], [no], [no]) AC_DEFUN([PDO_ODBC_CHECK_HEADER],[ AC_MSG_CHECKING([for $1 in $PDO_ODBC_INCDIR]) if test -f "$PDO_ODBC_INCDIR/$1"; then php_pdo_have_header=yes AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_$1]), [1], [Define to 1 if you have the <$1> header file.]) AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi ]) AC_DEFUN([SWOOLE_HAVE_PHP_EXT], [ extname=$1 haveext=$[PHP_]translit($1,a-z_-,A-Z__) AC_MSG_CHECKING([for ext/$extname support]) if test -x "$PHP_EXECUTABLE"; then grepext=`$PHP_EXECUTABLE -m | $EGREP ^$extname\$` if test "$grepext" = "$extname"; then [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=1 AC_MSG_RESULT([yes]) $2 else [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)= AC_MSG_RESULT([no]) $3 fi elif test "$haveext" != "no" && test "x$haveext" != "x"; then [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=1 AC_MSG_RESULT([yes]) $2 else [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)= AC_MSG_RESULT([no]) $3 fi ]) AC_DEFUN([AC_SWOOLE_CPU_AFFINITY], [ AC_MSG_CHECKING([for cpu affinity]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef __FreeBSD__ #include #include typedef cpuset_t cpu_set_t; #else #define _GNU_SOURCE 1 #include #endif ]], [[ cpu_set_t cpu_set; CPU_ZERO(&cpu_set); ]])],[ AC_DEFINE([HAVE_CPU_AFFINITY], 1, [cpu affinity?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_REUSEPORT], [ AC_MSG_CHECKING([for socket REUSEPORT]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ int val = 1; setsockopt(0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)); ]])],[ AC_DEFINE([HAVE_REUSEPORT], 1, [have SO_REUSEPORT?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_FUTEX], [ AC_MSG_CHECKING([for futex]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ int futex_addr; syscall(SYS_futex, &futex_addr, FUTEX_WAIT, NULL, NULL, 0); ]])],[ AC_DEFINE([HAVE_FUTEX], 1, [have FUTEX?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_UCONTEXT], [ AC_MSG_CHECKING([for ucontext]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _XOPEN_SOURCE #include #include #include ]], [[ ucontext_t context; getcontext(&context); ]])],[ AC_DEFINE([HAVE_UCONTEXT], 1, [have ucontext?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_VALGRIND], [ AC_MSG_CHECKING([for valgrind]) AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ ]])],[ AC_DEFINE([HAVE_VALGRIND], 1, [have valgrind?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) AC_LANG_POP([C++]) ]) AC_DEFUN([AC_SWOOLE_HAVE_BOOST_STACKTRACE], [ AC_MSG_CHECKING([for valgrind]) AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ ]])],[ AC_DEFINE([HAVE_BOOST_STACKTRACE], 1, [have boost-stacktrace?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) AC_LANG_POP([C++]) ]) AC_DEFUN([AC_SWOOLE_HAVE_IOURING_FUTEX], [ AC_MSG_CHECKING([for io_uring futex]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _GNU_SOURCE #include ]], [[ int op = IORING_OP_FUTEX_WAIT; ]])],[ AC_DEFINE([HAVE_IOURING_FUTEX], 1, [have io_uring futex?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_IOURING_STATX], [ AC_MSG_CHECKING([for io_uring statx]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _GNU_SOURCE #include #include #include ]], [[ struct statx _statxbuf; memset(&_statxbuf, 0, sizeof(_statxbuf)); int op = IORING_OP_STATX; ]])],[ AC_DEFINE([HAVE_IOURING_STATX], 1, [have io_uring statx?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_HAVE_IOURING_FTRUNCATE], [ AC_MSG_CHECKING([for io_uring ftruncate]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #define _GNU_SOURCE #include ]], [[ int op = IORING_OP_FTRUNCATE; ]])],[ AC_DEFINE([HAVE_IOURING_FTRUNCATE], 1, [have io_uring ftruncate?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_DEFUN([AC_SWOOLE_CHECK_SOCKETS], [ AC_CHECK_FUNCS([hstrerror socketpair if_nametoindex if_indextoname]) AC_CHECK_HEADERS([netdb.h netinet/tcp.h sys/un.h sys/sockio.h]) AC_DEFINE([HAVE_SOCKETS], 1, [ ]) dnl Check for AI_V4MAPPED flag AC_CACHE_CHECK([if getaddrinfo supports AI_V4MAPPED],[ac_cv_gai_ai_v4mapped], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[int flag = AI_V4MAPPED;]])], [ac_cv_gai_ai_v4mapped=yes], [ac_cv_gai_ai_v4mapped=no]) ]) if test "$ac_cv_gai_ai_v4mapped" = yes; then AC_DEFINE(HAVE_AI_V4MAPPED,1,[Whether you have AI_V4MAPPED]) fi dnl Check for AI_ALL flag AC_CACHE_CHECK([if getaddrinfo supports AI_ALL],[ac_cv_gai_ai_all], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[int flag = AI_ALL;]])], [ac_cv_gai_ai_all=yes], [ac_cv_gai_ai_all=no]) ]) if test "$ac_cv_gai_ai_all" = yes; then AC_DEFINE(HAVE_AI_ALL,1,[Whether you have AI_ALL]) fi dnl Check for AI_IDN flag AC_CACHE_CHECK([if getaddrinfo supports AI_IDN],[ac_cv_gai_ai_idn], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[int flag = AI_IDN;]])], [ac_cv_gai_ai_idn=yes], [ac_cv_gai_ai_idn=no]) ]) if test "$ac_cv_gai_ai_idn" = yes; then AC_DEFINE(HAVE_AI_IDN,1,[Whether you have AI_IDN]) fi ]) AC_DEFUN([AC_SWOOLE_HAVE_MSGQUEUE], [ AC_MSG_CHECKING([for sysv message queue]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ msgget(0x9501, IPC_CREAT); ]])],[ AC_DEFINE([HAVE_MSGQUEUE], 1, [have sysv message queue?]) AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) ]) ]) AC_MSG_CHECKING([if compiling with clang]) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([], [[ #ifndef __clang__ not clang #endif ]])], [CLANG=yes], [CLANG=no] ) AC_MSG_RESULT([$CLANG]) dnl AC_PROG_CC_C99 is obsolete with autoconf >= 2.70 yet necessary for <= 2.69. m4_version_prereq([2.70],,[AC_PROG_CC_C99]) AC_CANONICAL_HOST if test "$PHP_SWOOLE" != "no"; then AC_CHECK_LIB(c, accept4, AC_DEFINE(HAVE_ACCEPT4, 1, [have accept4])) AC_CHECK_LIB(c, signalfd, AC_DEFINE(HAVE_SIGNALFD, 1, [have signalfd])) AC_CHECK_LIB(c, eventfd, AC_DEFINE(HAVE_EVENTFD, 1, [have eventfd])) AC_CHECK_LIB(c, epoll_create, AC_DEFINE(HAVE_EPOLL, 1, [have epoll])) AC_CHECK_LIB(c, sendfile, AC_DEFINE(HAVE_SENDFILE, 1, [have sendfile])) AC_CHECK_LIB(c, kqueue, AC_DEFINE(HAVE_KQUEUE, 1, [have kqueue])) AC_CHECK_LIB(c, backtrace, AC_DEFINE(HAVE_EXECINFO, 1, [have execinfo])) AC_CHECK_LIB(c, daemon, AC_DEFINE(HAVE_DAEMON, 1, [have daemon])) AC_CHECK_LIB(c, mkostemp, AC_DEFINE(HAVE_MKOSTEMP, 1, [have mkostemp])) AC_CHECK_LIB(c, inotify_init, AC_DEFINE(HAVE_INOTIFY, 1, [have inotify])) AC_CHECK_LIB(c, malloc_trim, AC_DEFINE(HAVE_MALLOC_TRIM, 1, [have malloc_trim])) AC_CHECK_LIB(c, inotify_init1, AC_DEFINE(HAVE_INOTIFY_INIT1, 1, [have inotify_init1])) AC_CHECK_LIB(c, gethostbyname2_r, AC_DEFINE(HAVE_GETHOSTBYNAME2_R, 1, [have gethostbyname2_r])) AC_CHECK_LIB(c, ptrace, AC_DEFINE(HAVE_PTRACE, 1, [have ptrace])) AC_CHECK_LIB(c, getrandom, AC_DEFINE(HAVE_GETRANDOM, 1, [have getrandom])) AC_CHECK_LIB(c, arc4random, AC_DEFINE(HAVE_ARC4RANDOM, 1, [have arc4random])) AC_CHECK_LIB(c, CCRandomGenerateBytes, AC_DEFINE(HAVE_CCRANDOMGENERATEBYTES, 1, [have_ccrandomgeneratebytes])) AC_CHECK_LIB(pthread, pthread_rwlock_init, AC_DEFINE(HAVE_RWLOCK, 1, [have pthread_rwlock_init])) AC_CHECK_LIB(pthread, pthread_spin_lock, AC_DEFINE(HAVE_SPINLOCK, 1, [have pthread_spin_lock])) AC_CHECK_LIB(pthread, pthread_mutex_timedlock, AC_DEFINE(HAVE_MUTEX_TIMEDLOCK, 1, [have pthread_mutex_timedlock])) AC_CHECK_LIB(pthread, pthread_rwlock_timedrdlock, AC_DEFINE(HAVE_RWLOCK_TIMEDRDLOCK, 1, [have pthread_rwlock_timedrdlock])) AC_CHECK_LIB(pthread, pthread_rwlock_timedwrlock, AC_DEFINE(HAVE_RWLOCK_TIMEDWRLOCK, 1, [have pthread_rwlock_timedwrlock])) AC_CHECK_LIB(pthread, pthread_barrier_init, AC_DEFINE(HAVE_PTHREAD_BARRIER, 1, [have pthread_barrier_init])) AC_CHECK_LIB(pthread, pthread_mutexattr_setpshared, AC_DEFINE(HAVE_PTHREAD_MUTEXATTR_SETPSHARED, 1, [have pthread_mutexattr_setpshared])) AC_CHECK_LIB(pthread, pthread_mutexattr_setrobust, AC_DEFINE(HAVE_PTHREAD_MUTEXATTR_SETROBUST, 1, [have pthread_mutexattr_setrobust])) AC_CHECK_LIB(pthread, pthread_mutex_consistent, AC_DEFINE(HAVE_PTHREAD_MUTEX_CONSISTENT, 1, [have pthread_mutex_consistent])) if test "$PHP_SWOOLE_DEV" = "yes"; then AX_CHECK_COMPILE_FLAG(-Wbool-conversion, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wbool-conversion") AX_CHECK_COMPILE_FLAG(-Wignored-qualifiers, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wignored-qualifiers") AX_CHECK_COMPILE_FLAG(-Wduplicate-enum, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wduplicate-enum") AX_CHECK_COMPILE_FLAG(-Wempty-body, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wempty-body") AX_CHECK_COMPILE_FLAG(-Wenum-compare, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wenum-compare") AX_CHECK_COMPILE_FLAG(-Wextra, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wextra") AX_CHECK_COMPILE_FLAG(-Wformat-security, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wformat-security") AX_CHECK_COMPILE_FLAG(-Wheader-guard, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wheader-guard") AX_CHECK_COMPILE_FLAG(-Wincompatible-pointer-types-discards-qualifiers, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wincompatible-pointer-types-discards-qualifiers") AX_CHECK_COMPILE_FLAG(-Winit-self, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Winit-self") AX_CHECK_COMPILE_FLAG(-Wlogical-not-parentheses, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wlogical-not-parentheses") AX_CHECK_COMPILE_FLAG(-Wlogical-op-parentheses, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wlogical-op-parentheses") AX_CHECK_COMPILE_FLAG(-Wloop-analysis, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wloop-analysis") AX_CHECK_COMPILE_FLAG(-Wuninitialized, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wuninitialized") AX_CHECK_COMPILE_FLAG(-Wno-date-time, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-date-time") AX_CHECK_COMPILE_FLAG(-Wno-missing-field-initializers, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-missing-field-initializers") AX_CHECK_COMPILE_FLAG(-Wno-sign-compare, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-sign-compare") AX_CHECK_COMPILE_FLAG(-Wno-unused-const-variable, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-unused-const-variable") AX_CHECK_COMPILE_FLAG(-Wno-unused-parameter, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-unused-parameter") AX_CHECK_COMPILE_FLAG(-Wno-variadic-macros, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-variadic-macros") AX_CHECK_COMPILE_FLAG(-Wparentheses, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wparentheses") AX_CHECK_COMPILE_FLAG(-Wpointer-bool-conversion, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wpointer-bool-conversion") AX_CHECK_COMPILE_FLAG(-Wsizeof-array-argument, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wsizeof-array-argument") AX_CHECK_COMPILE_FLAG(-Wwrite-strings, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wwrite-strings") AX_CHECK_COMPILE_FLAG(-fdiagnostics-show-option, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -fdiagnostics-show-option") AX_CHECK_COMPILE_FLAG(-fno-omit-frame-pointer, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -fno-omit-frame-pointer") AX_CHECK_COMPILE_FLAG(-fno-optimize-sibling-calls, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -fno-optimize-sibling-calls") AX_CHECK_COMPILE_FLAG(-fsanitize-address, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -fsanitize-address") AX_CHECK_COMPILE_FLAG(-fstack-protector, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -fstack-protector") EXTRA_CFLAGS="$_MAINTAINER_CFLAGS" CFLAGS="-g -O0 -Wall $CFLAGS" CXXFLAGS="-g -O0 -Wall $CXXFLAGS" fi if test "$PHP_SWOOLE_CURL" = "yes"; then PKG_CHECK_MODULES([CURL], [libcurl >= 7.56.0]) PHP_EVAL_LIBLINE($CURL_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($CURL_CFLAGS) AC_DEFINE(SW_USE_CURL, 1, [do we enable cURL native client]) fi if test "$PHP_SWOOLE_CORO_TIME" = "yes"; then AC_DEFINE(SW_CORO_TIME, 1, [do we enable to calculate coroutine execution time]) fi dnl pgsql begin if test "$PHP_SWOOLE_PGSQL" != "no"; then dnl TODO macros below can be reused to find curl things dnl prepare pkg-config if test -z "$PKG_CONFIG"; then AC_PATH_PROG(PKG_CONFIG, pkg-config, no) fi AC_MSG_CHECKING(for libpq) if test "x${LIBPQ_LIBS+set}" = "xset" || test "x${LIBPQ_CFLAGS+set}" = "xset"; then AC_MSG_RESULT([using LIBPQ_CFLAGS and LIBPQ_LIBS]) elif test -x "$PKG_CONFIG" ; then dnl find pkg using pkg-config cli tool libpq_pkg_config_path="$PHP_SWOOLE_PGSQL/lib/pkgconfig" if test "xyes" = "x$PHP_SWOOLE_PGSQL" ; then libpq_pkg_config_path=/lib/pkgconfig fi if test "x" != "x$PKG_CONFIG_PATH"; then libpq_pkg_config_path="$libpq_pkg_config_path:$PKG_CONFIG_PATH" fi libpq_version_full=`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --modversion libpq` AC_MSG_RESULT(${libpq_version_full}) LIBPQ_CFLAGS="`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --cflags libpq`" LIBPQ_LIBS="`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --libs libpq`" fi _libpq_saved_cflags="$CFLAGS" CFLAGS="$CFLAGS $LIBPQ_CFLAGS" AC_CHECK_HEADER(libpq-fe.h, [], [ dnl this is too long, wht so chaos? cat >&2 <&2 < #include ]], [[ SQLLEN ind = 0; char buf[1]; SQLBindCol((SQLHSTMT)0, (SQLUSMALLINT)1, (SQLSMALLINT)SQL_C_CHAR, (SQLPOINTER)buf, (SQLLEN)sizeof(buf), &ind); return 0; ]])], [ dnl And now check for an ODBC 3.0 function to assert that they are *good* dnl libraries. AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include #include ]], [[ SQLHANDLE out = SQL_NULL_HANDLE; SQLAllocHandle((SQLSMALLINT)SQL_HANDLE_ENV, (SQLHANDLE)SQL_NULL_HANDLE, &out); return 0; ]])], [], [AC_MSG_ERROR([ Your ODBC library does not appear to be ODBC 3 compatible. You should consider using iODBC or unixODBC instead, and loading your libraries as a driver in that environment; it will emulate the functions required for PDO support. ])] ) ], [AC_MSG_ERROR([Your ODBC library does not exist or there was an error. Check config.log for more information])] ) LIBS="$SAVE_LIBS" AC_DEFINE(SW_USE_ODBC, 1, [do we enable swoole-odbc coro support]) fi dnl odbc end dnl SWOOLE_ORACLE start if test -z "$SED"; then SWOOLE_PDO_OCI_SED="sed"; else SWOOLE_PDO_OCI_SED="$SED"; fi SWOOLE_PDO_OCI_TAIL1=`echo a | tail -n1 2>/dev/null` if test "$SWOOLE_PDO_OCI_TAIL1" = "a"; then SWOOLE_PDO_OCI_TAIL1="tail -n1" else SWOOLE_PDO_OCI_TAIL1="tail -1" fi AC_DEFUN([AC_PDO_OCI_VERSION],[ AC_MSG_CHECKING([Oracle version]) PDO_OCI_LCS_BASE=$PDO_OCI_LIB_DIR/libclntsh.$SHLIB_SUFFIX_NAME dnl Oracle 10g, 11g, 12c etc PDO_OCI_LCS=`ls $PDO_OCI_LCS_BASE.*.1 2> /dev/null | $SWOOLE_PDO_OCI_TAIL1` if test -f "$PDO_OCI_LCS"; then dnl Oracle 10g, 11g 12c etc. The x.2 version libraries are named x.1 for dnl drop in compatibility PDO_OCI_VERSION=`echo $PDO_OCI_LCS | $SWOOLE_PDO_OCI_SED -e 's/.*\.\(.*\)\.1$/\1.1/'` elif test -f $PDO_OCI_LCS_BASE.9.0; then dnl There is no case for Oracle 9.2. Oracle 9.2 libraries have a 9.0 suffix dnl for drop-in compatibility with Oracle 9.0 PDO_OCI_VERSION=9.0 else AC_MSG_ERROR(Oracle libclntsh.$SHLIB_SUFFIX_NAME client library not found or its version is lower than 9) fi AC_MSG_RESULT($PDO_OCI_VERSION) ]) AC_DEFUN([AC_PDO_OCI_CHECK_LIB_DIR],[ AC_CHECK_SIZEOF([long]) AC_MSG_CHECKING([if we're at 64-bit platform]) AS_IF([test "$ac_cv_sizeof_long" -eq 4],[ AC_MSG_RESULT([no]) TMP_PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib32" ],[ AC_MSG_RESULT([yes]) TMP_PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib" ]) AC_MSG_CHECKING([OCI8 libraries dir]) if test -d "$PDO_OCI_DIR/lib" && test ! -d "$PDO_OCI_DIR/lib32"; then PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib" elif test ! -d "$PDO_OCI_DIR/lib" && test -d "$PDO_OCI_DIR/lib32"; then PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib32" elif test -d "$PDO_OCI_DIR/lib" && test -d "$PDO_OCI_DIR/lib32"; then PDO_OCI_LIB_DIR=$TMP_PDO_OCI_LIB_DIR else AC_MSG_ERROR([Oracle required OCI8 libraries not found]) fi AC_MSG_RESULT($PDO_OCI_LIB_DIR) ]) PHP_ARG_WITH([swoole-oracle], [whether to enable oracle build flags], [AS_HELP_STRING([[--with-swoole-oracle[=DIR]]], ["PDO: Oracle OCI support. DIR defaults to ${ORACLE_HOME}. Use --with-swoole-oracle=instantclient,/path/to/instant/client/lib for an Oracle Instant Client installation."])], [no], [no]) if test "$PHP_SWOOLE_ORACLE" != "no"; then if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) fi AC_MSG_CHECKING([Oracle Install-Dir]) if test "$PHP_SWOOLE_ORACLE" = "yes" || test -z "$PHP_SWOOLE_ORACLE"; then PDO_OCI_DIR=$ORACLE_HOME else PDO_OCI_DIR=$PHP_SWOOLE_ORACLE fi AC_MSG_RESULT($PHP_SWOOLE_ORACLE) AC_MSG_CHECKING([if that is sane]) if test -z "$PDO_OCI_DIR"; then AC_MSG_ERROR([You need to tell me where to find your Oracle Instant Client SDK, or set ORACLE_HOME.]) else AC_MSG_RESULT([yes]) fi if test "instantclient" = "`echo $PDO_OCI_DIR | cut -d, -f1`" ; then AC_CHECK_SIZEOF([long]) AC_MSG_CHECKING([if we're at 64-bit platform]) AS_IF([test "$ac_cv_sizeof_long" -eq 4],[ AC_MSG_RESULT([no]) PDO_OCI_CLIENT_DIR="client" ],[ AC_MSG_RESULT([yes]) PDO_OCI_CLIENT_DIR="client64" ]) PDO_OCI_LIB_DIR="`echo $PDO_OCI_DIR | cut -d, -f2`" AC_PDO_OCI_VERSION($PDO_OCI_LIB_DIR) AC_MSG_CHECKING([for oci.h]) dnl Header directory for Instant Client SDK RPM install OCISDKRPMINC=`echo "$PDO_OCI_LIB_DIR" | $SWOOLE_PDO_OCI_SED -e 's!^\(.*\)/lib/oracle/\(.*\)/\('${PDO_OCI_CLIENT_DIR}'\)/lib[/]*$!\1/include/oracle/\2/\3!'` dnl Header directory for manual installation OCISDKMANINC=`echo "$PDO_OCI_LIB_DIR" | $SWOOLE_PDO_OCI_SED -e 's!^\(.*\)/lib[/]*$!\1/include!'` dnl Header directory for Instant Client SDK zip file install OCISDKZIPINC=$PDO_OCI_LIB_DIR/sdk/include if test -f "$OCISDKRPMINC/oci.h" ; then PHP_ADD_INCLUDE($OCISDKRPMINC) AC_MSG_RESULT($OCISDKRPMINC) elif test -f "$OCISDKMANINC/oci.h" ; then PHP_ADD_INCLUDE($OCISDKMANINC) AC_MSG_RESULT($OCISDKMANINC) elif test -f "$OCISDKZIPINC/oci.h" ; then PHP_ADD_INCLUDE($OCISDKZIPINC) AC_MSG_RESULT($OCISDKZIPINC) else AC_MSG_ERROR([I'm too dumb to figure out where the include dir is in your Instant Client install]) fi else AC_PDO_OCI_CHECK_LIB_DIR($PDO_OCI_DIR) if test -d "$PDO_OCI_DIR/rdbms/public"; then PHP_ADD_INCLUDE($PDO_OCI_DIR/rdbms/public) PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/rdbms/public" fi if test -d "$PDO_OCI_DIR/rdbms/demo"; then PHP_ADD_INCLUDE($PDO_OCI_DIR/rdbms/demo) PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/rdbms/demo" fi if test -d "$PDO_OCI_DIR/network/public"; then PHP_ADD_INCLUDE($PDO_OCI_DIR/network/public) PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/network/public" fi if test -d "$PDO_OCI_DIR/plsql/public"; then PHP_ADD_INCLUDE($PDO_OCI_DIR/plsql/public) PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/plsql/public" fi if test -d "$PDO_OCI_DIR/include"; then PHP_ADD_INCLUDE($PDO_OCI_DIR/include) PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/include" fi if test -f "$PDO_OCI_LIB_DIR/sysliblist"; then PHP_EVAL_LIBLINE(`cat $PDO_OCI_LIB_DIR/sysliblist`, SWOOLE_SHARED_LIBADD) elif test -f "$PDO_OCI_DIR/rdbms/lib/sysliblist"; then PHP_EVAL_LIBLINE(`cat $PDO_OCI_DIR/rdbms/lib/sysliblist`, SWOOLE_SHARED_LIBADD) fi AC_PDO_OCI_VERSION($PDO_OCI_LIB_DIR) fi case $PDO_OCI_VERSION in 7.3|8.0|8.1) AC_MSG_ERROR([Oracle client libraries < 9 are not supported]) ;; esac PHP_ADD_LIBRARY(clntsh, 1, SWOOLE_SHARED_LIBADD) PHP_ADD_LIBPATH($PDO_OCI_LIB_DIR, SWOOLE_SHARED_LIBADD) PHP_CHECK_LIBRARY(clntsh, OCIEnvCreate, [ AC_DEFINE(HAVE_OCIENVCREATE,1,[ ]) ], [], [ -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD ]) PHP_CHECK_LIBRARY(clntsh, OCIEnvNlsCreate, [ AC_DEFINE(HAVE_OCIENVNLSCREATE,1,[ ]) ], [], [ -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD ]) dnl Scrollable cursors? PHP_CHECK_LIBRARY(clntsh, OCIStmtFetch2, [ AC_DEFINE(HAVE_OCISTMTFETCH2,1,[ ]) ], [], [ -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD ]) dnl Can handle bytes vs. characters? PHP_CHECK_LIBRARY(clntsh, OCILobRead2, [ AC_DEFINE(HAVE_OCILOBREAD2,1,[ ]) ], [], [ -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD ]) EXTRA_CFLAGS="$EXTRA_CFLAGS -I$pdo_cv_inc_path $PDO_OCI_INCLUDE" PHP_CHECK_PDO_INCLUDES AC_DEFINE_UNQUOTED(SWOOLE_PDO_OCI_CLIENT_VERSION, "$PDO_OCI_VERSION", [ ]) AC_DEFINE(SW_USE_ORACLE, 1, [do we enable oracle coro support]) fi dnl SWOOLE_ORACLE stop dnl sqlite start PHP_ARG_ENABLE([swoole-sqlite], ["for sqlite 3 support for PDO"], [AS_HELP_STRING([--enable-swoole-sqlite], [PDO: sqlite 3 support.])], [no], [no]) if test "$PHP_SWOOLE_SQLITE" != "no"; then if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) fi PHP_CHECK_PDO_INCLUDES PKG_CHECK_MODULES([SQLITE], [sqlite3 >= 3.7.7]) PHP_EVAL_INCLINE($SQLITE_CFLAGS) PHP_EVAL_LIBLINE($SQLITE_LIBS, SWOOLE_SHARED_LIBADD) AC_DEFINE(HAVE_SW_PDO_SQLITELIB, 1, [Define to 1 if you have the pdo_sqlite extension enabled.]) PHP_CHECK_LIBRARY(sqlite3, sqlite3_close_v2, [ AC_DEFINE(HAVE_SW_SQLITE3_CLOSE_V2, 1, [have sqlite3_close_v2]) ], [], [$SWOOLE_SHARED_LIBADD]) PHP_CHECK_LIBRARY(sqlite3, sqlite3_column_table_name, [ AC_DEFINE(HAVE_SW_SQLITE3_COLUMN_TABLE_NAME, 1, [have sqlite3_column_table_name]) ], [], [$SWOOLE_SHARED_LIBADD]) AC_DEFINE(SW_USE_SQLITE, 1, [do we enable sqlite coro support]) fi dnl sqlite stop dnl firebird start if test "$PHP_SWOOLE_FIREBIRD" != "no"; then if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) fi AC_PATH_PROG([FB_CONFIG], [fb_config], [no]) if test -x "$FB_CONFIG" && test "$PHP_PDO_FIREBIRD" = "yes"; then AC_MSG_CHECKING([for libfbconfig]) FB_CFLAGS=$($FB_CONFIG --cflags) FB_LIBDIR=$($FB_CONFIG --libs) FB_VERSION=$($FB_CONFIG --version) AC_MSG_RESULT([version $FB_VERSION]) AS_VERSION_COMPARE([$FB_VERSION], [3.0], [AC_MSG_ERROR([Firebird required version is at least 3.0])]) PHP_EVAL_LIBLINE([$FB_LIBDIR], [SWOOLE_SHARED_LIBADD]) PHP_EVAL_INCLINE([$FB_CFLAGS]) else AS_VAR_IF([PHP_PDO_FIREBIRD], [yes], [ FIREBIRD_INCDIR= FIREBIRD_LIBDIR= FIREBIRD_LIBDIR_FLAG= ], [ FIREBIRD_INCDIR=$PHP_PDO_FIREBIRD/include FIREBIRD_LIBDIR=$PHP_PDO_FIREBIRD/$PHP_LIBDIR FIREBIRD_LIBDIR_FLAG=-L$FIREBIRD_LIBDIR ]) PHP_CHECK_LIBRARY([fbclient], [fb_get_master_interface], [], [AC_MSG_FAILURE([libfbclient not found.])], [$FIREBIRD_LIBDIR_FLAG]) PHP_ADD_LIBRARY_WITH_PATH([fbclient], [$FIREBIRD_LIBDIR], [SWOOLE_SHARED_LIBADD]) PHP_ADD_INCLUDE([$FIREBIRD_INCDIR]) fi PHP_CHECK_PDO_INCLUDES AC_DEFINE(SW_USE_FIREBIRD, 1, [do we enable firebird coro support]) fi dnl firebird stop dnl ssh2 start if test "$PHP_SWOOLE_SSH2" != "no"; then SEARCH_PATH="/usr/local /usr" SEARCH_FOR="/include/libssh2.h" if test -r $PHP_SWOOLE_SSH2/$SEARCH_FOR; then # path given as parameter SSH2_DIR=$PHP_SWOOLE_SSH2 else AC_MSG_CHECKING([for ssh2 files in default path]) for i in $SEARCH_PATH ; do if test -r $i/$SEARCH_FOR; then SSH2_DIR=$i AC_MSG_RESULT(found in $i) fi done fi if test -z "$SSH2_DIR"; then AC_MSG_RESULT([not found]) AC_MSG_ERROR([The required libssh2 library was not found. You can obtain that package from http://sourceforge.net/projects/libssh2/]) fi PHP_ADD_INCLUDE($SSH2_DIR/include) PHP_CHECK_LIBRARY(ssh2, libssh2_session_hostkey, [ PHP_ADD_LIBRARY_WITH_PATH(ssh2, $SSH2_DIR/lib, SWOOLE_SHARED_LIBADD) AC_DEFINE(SW_HAVE_SSH2LIB, 1, [Have libssh2]) ],[ AC_MSG_ERROR([libssh2 version >= 1.2 not found]) ],[ -L$SSH2_DIR/lib -lm ]) PHP_CHECK_LIBRARY(ssh2, libssh2_agent_init, [ AC_DEFINE(PHP_SSH2_AGENT_AUTH, 1, [Have libssh2 with ssh-agent support]) ],[ AC_MSG_WARN([libssh2 <= 1.2.3, ssh-agent subsystem support not enabled]) ],[ -L$SSH2_DIR/lib -lm ]) PHP_CHECK_LIBRARY(ssh2, libssh2_session_set_timeout, [ AC_DEFINE(PHP_SSH2_SESSION_TIMEOUT, 1, [Have libssh2 with session timeout support]) ],[ AC_MSG_WARN([libssh2 < 1.2.9, session timeout support not enabled]) ],[ -L$SSH2_DIR/lib -lm ]) fi dnl ssh2 stop AC_CHECK_LIB(z, gzgets, [ AC_DEFINE(SW_HAVE_ZLIB, 1, [have zlib]) PHP_ADD_LIBRARY(z, 1, SWOOLE_SHARED_LIBADD) ]) if test "$PHP_BROTLI_DIR" != "no"; then AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) PHP_ADD_INCLUDE("${PHP_BROTLI_DIR}/include") PHP_ADD_LIBRARY_WITH_PATH(brotlienc, "${PHP_BROTLI_DIR}/${PHP_LIBDIR}") PHP_ADD_LIBRARY_WITH_PATH(brotlidec, "${PHP_BROTLI_DIR}/${PHP_LIBDIR}") elif test "$PHP_BROTLI" = "yes"; then PKG_CHECK_MODULES([BROTLIENC], [libbrotlienc]) PKG_CHECK_MODULES([BROTLIDEC], [libbrotlidec]) AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) PHP_EVAL_LIBLINE($BROTLIENC_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($BROTLIENC_CFLAGS) PHP_EVAL_LIBLINE($BROTLIDEC_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($BROTLIDEC_CFLAGS) elif test "$PHP_BROTLI" = "auto"; then PKG_CHECK_MODULES([BROTLIENC], [libbrotlienc], [found_brotlienc=yes], [found_brotlienc=no]) if test "$found_brotlienc" = "yes"; then AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) PHP_EVAL_LIBLINE($BROTLIENC_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($BROTLIENC_CFLAGS) fi PKG_CHECK_MODULES([BROTLIDEC], [libbrotlidec], [found_brotlidec=yes], [found_brotlidec=no]) if test "$found_brotlidec" = "yes"; then AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) PHP_EVAL_LIBLINE($BROTLIDEC_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($BROTLIDEC_CFLAGS) fi fi if test "$PHP_ZSTD" = "yes"; then PKG_CHECK_MODULES([ZSTD], [libzstd >= 1.4.0]) AC_DEFINE(SW_HAVE_ZSTD, 1, [have zstd]) PHP_EVAL_LIBLINE($ZSTD_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($ZSTD_CFLAGS) fi PHP_ADD_LIBRARY(pthread) PHP_SUBST(SWOOLE_SHARED_LIBADD) AC_ARG_ENABLE(debug, [ --enable-debug Compile with debug symbols], [PHP_DEBUG=$enableval], [PHP_DEBUG=0] ) if test "$PHP_DEBUG_LOG" != "no"; then AC_DEFINE(SW_DEBUG, 1, [do we enable swoole debug]) PHP_DEBUG=1 fi if test "$PHP_ASAN" != "no"; then PHP_DEBUG=1 CFLAGS="$CFLAGS -fsanitize=address -fno-omit-frame-pointer" CXXFLAGS="$CXXFLAGS -fsanitize=address -fno-omit-frame-pointer" fi if test "$PHP_TRACE_LOG" != "no"; then AC_DEFINE(SW_LOG_TRACE_OPEN, 1, [enable trace log]) fi if test "$PHP_SWOOLE_THREAD" != "no"; then AC_DEFINE(SW_THREAD, 1, [enable swoole thread support]) fi if test "$PHP_SWOOLE_STDEXT" != "no"; then AC_DEFINE(SW_STDEXT, 1, [enable swoole stdext support]) fi if test "$PHP_SOCKETS" = "yes"; then AC_MSG_CHECKING([for php_sockets.h]) AS_IF([test -f $abs_srcdir/ext/sockets/php_sockets.h], [AC_MSG_RESULT([ok, found in $abs_srcdir])], [test -f $phpincludedir/ext/sockets/php_sockets.h], [AC_MSG_RESULT([ok, found in $phpincludedir])], [AC_MSG_ERROR([cannot find php_sockets.h. Please check if sockets extension is installed.]) ]) AC_DEFINE(SW_SOCKETS, 1, [enable sockets support]) dnl Some systems build and package PHP socket extension separately dnl and php_config.h does not have HAVE_SOCKETS defined. AC_DEFINE(HAVE_SOCKETS, 1, [whether sockets extension is enabled]) PHP_ADD_EXTENSION_DEP(swoole, sockets, true) fi if test "$PHP_THREAD" = "yes"; then AC_DEFINE(SW_USE_THREAD, 1, [enable thread support]) fi if test "$PHP_CARES" = "yes"; then PKG_CHECK_MODULES([CARES], [libcares]) PHP_EVAL_LIBLINE($CARES_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($CARES_CFLAGS) AC_DEFINE(SW_USE_CARES, 1, [do we enable c-ares support]) AC_DEFINE(HAVE_CARES, 1, [have c-ares]) fi AC_SWOOLE_CPU_AFFINITY AC_SWOOLE_HAVE_REUSEPORT AC_SWOOLE_HAVE_FUTEX AC_SWOOLE_HAVE_UCONTEXT AC_SWOOLE_HAVE_VALGRIND AC_SWOOLE_CHECK_SOCKETS AC_SWOOLE_HAVE_MSGQUEUE AC_SWOOLE_HAVE_BOOST_STACKTRACE AS_CASE([$host_os], [darwin*], [SW_OS="MAC"], [cygwin*], [SW_OS="CYGWIN"], [mingw*], [SW_OS="MINGW"], [linux*], [SW_OS="LINUX"], [*bsd*], [SW_OS="BSD"], [] ) CFLAGS="-Wall -pthread $CFLAGS" LDFLAGS="$LDFLAGS -lpthread" HAVE_CONFIG_IOURING_X="no" if (test "$PHP_IOURING" = "yes" || test "$PHP_LIBURING_DIR" != "no") && test "$SW_OS" = "LINUX"; then if test "$PHP_IOURING" != "no"; then PKG_CHECK_MODULES([URING], [liburing >= 2.8]) PHP_EVAL_LIBLINE($URING_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($URING_CFLAGS) elif test "$PHP_LIBURING_DIR" != "no"; then PHP_ADD_INCLUDE("${PHP_LIBURING_DIR}/include") PHP_ADD_LIBRARY_WITH_PATH(uring, "${PHP_LIBURING_DIR}/${PHP_LIBDIR}") PHP_ADD_LIBRARY(uring, 1, SWOOLE_SHARED_LIBADD) fi AC_SWOOLE_HAVE_IOURING_STATX KERNEL_MAJOR=`uname -r | awk -F '.' '{print $1}'` KERNEL_MINOR=`uname -r | awk -F '.' '{print $2}'` if (test $KERNEL_MAJOR -eq 6 && test $KERNEL_MINOR -ge 9); then dnl IORING_OP_FTRUNCATE is available since 6.9 AC_SWOOLE_HAVE_IOURING_FTRUNCATE fi if (test $KERNEL_MAJOR -eq 6 && test $KERNEL_MINOR -ge 7); then dnl IORING_OP_FUTEX_WAKE/IORING_OP_FUTEX_WAIT is available since 6.7 AC_SWOOLE_HAVE_IOURING_FUTEX fi HAVE_CONFIG_IOURING_X="yes" AC_DEFINE(SW_USE_IOURING, 1, [have io_uring]) fi if test "$PHP_URING_SOCKET" != "no"; then if test "$HAVE_CONFIG_IOURING_X" != "no"; then AC_DEFINE(SW_USE_URING_SOCKET, 1, [enable io_uring socket support]) else AC_MSG_ERROR([--enable-uring-socket requires io_uring. Please specify either --enable-iouring or --with-liburing-dir.]) fi fi dnl Check should we link to librt if test "$SW_OS" = "LINUX"; then GLIBC_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}') AC_MSG_NOTICE([glibc version: $GLIBC_VERSION]) GLIBC_MAJOR_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}' | cut -d '.' -f 1) GLIBC_MINOR_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}' | cut -d '.' -f 2) if test $GLIBC_MAJOR_VERSION -lt 2 || (test $GLIBC_MAJOR_VERSION -eq 2 && test $GLIBC_MINOR_VERSION -lt 17); then OS_SHOULD_HAVE_LIBRT=1 else AC_MSG_NOTICE([link with -lrt (only for glibc version before 2.17)]) OS_SHOULD_HAVE_LIBRT=0 fi elif test "$SW_OS" = "MAC"; then OS_SHOULD_HAVE_LIBRT=0 else AS_CASE([$host_os], [openbsd*], [OS_SHOULD_HAVE_LIBRT=0] [OS_SHOULD_HAVE_LIBRT=1] ) fi if test "x$OS_SHOULD_HAVE_LIBRT" = "x1"; then AC_MSG_NOTICE([Librt is required on $host_os.]) dnl Check for the existence of librt AC_CHECK_LIB([rt], [clock_gettime], [], [ AC_MSG_ERROR([We have to link to librt on your os, but librt not found.]) ]) PHP_ADD_LIBRARY(rt, 1, SWOOLE_SHARED_LIBADD) else AC_MSG_NOTICE([$host_os doesn't have librt -- don't link to librt.]) fi if test "$SW_OS" = "LINUX"; then LDFLAGS="$LDFLAGS -z now" fi if test "$PHP_OPENSSL_DIR" != "no"; then PHP_ADD_INCLUDE("${PHP_OPENSSL_DIR}/include") PHP_ADD_LIBRARY_WITH_PATH(ssl, "${PHP_OPENSSL_DIR}/${PHP_LIBDIR}") PHP_ADD_LIBRARY_WITH_PATH(crypto, "${PHP_OPENSSL_DIR}/${PHP_LIBDIR}") else PKG_CHECK_MODULES([SSL], [libssl]) PHP_EVAL_LIBLINE($SSL_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($SSL_CFLAGS) PKG_CHECK_MODULES([CRYPTO], [libcrypto]) PHP_EVAL_LIBLINE($CRYPTO_LIBS, SWOOLE_SHARED_LIBADD) PHP_EVAL_INCLINE($CRYPTO_CFLAGS) fi if test "$PHP_NGHTTP2_DIR" != "no"; then PHP_ADD_INCLUDE("${PHP_NGHTTP2_DIR}/include") PHP_ADD_LIBRARY_WITH_PATH(nghttp2, "${PHP_NGHTTP2_DIR}/${PHP_LIBDIR}") PHP_ADD_LIBRARY(nghttp2, 1, SWOOLE_SHARED_LIBADD) fi PHP_ADD_LIBRARY(pthread, 1, SWOOLE_SHARED_LIBADD) if test "$PHP_MYSQLND" = "yes"; then PHP_ADD_EXTENSION_DEP(mysqli, mysqlnd) AC_DEFINE(SW_USE_MYSQLND, 1, [use mysqlnd]) fi AC_MSG_CHECKING([for sources]) if test -f "$abs_srcdir/ext-src/php_swoole.cc"; then swoole_source_dir=$abs_srcdir elif test -f "ext-src/php_swoole.cc"; then swoole_source_dir=$(pwd) else swoole_source_dir="ext/swoole" fi AC_MSG_RESULT([$swoole_source_dir]) ext_src_files=$(cd $swoole_source_dir && find ext-src/ -name *.cc) lib_src_files=$(cd $swoole_source_dir && find src/ -name *.cc) swoole_source_file="${ext_src_files} ${lib_src_files}" swoole_source_file="$swoole_source_file \ thirdparty/php/sockets/multicast.cc \ thirdparty/php/sockets/sendrecvmsg.cc \ thirdparty/php/sockets/conversions.cc \ thirdparty/php/sockets/sockaddr_conv.cc \ thirdparty/php/standard/var_decoder.cc \ thirdparty/php/standard/proc_open.cc" swoole_source_file="$swoole_source_file \ thirdparty/llhttp/api.c \ thirdparty/llhttp/http.c \ thirdparty/llhttp/llhttp.c \ thirdparty/multipart_parser.c" if test "$PHP_NGHTTP2_DIR" = "no"; then swoole_source_file="$swoole_source_file \ thirdparty/nghttp2/nghttp2_hd.c \ thirdparty/nghttp2/nghttp2_rcbuf.c \ thirdparty/nghttp2/nghttp2_helper.c \ thirdparty/nghttp2/nghttp2_buf.c \ thirdparty/nghttp2/nghttp2_mem.c \ thirdparty/nghttp2/nghttp2_hd_huffman.c \ thirdparty/nghttp2/nghttp2_hd_huffman_data.c" fi dnl During static compilation, there is no php-config variable, dnl but the php-version variable is always present and is not affected by the shell environment variables. dnl During dynamic compilation, the php-config variable is always available, whereas the php-version variable is absent. if test -z "$PHP_CONFIG"; then if test -z "$PHP_VERSION"; then AC_MSG_ERROR([the PHP_VERSION variable must be defined]) else SW_PHP_VERSION=$PHP_VERSION fi else SW_PHP_VERSION=`$PHP_CONFIG --version` fi SW_PHP_VERSION_ID=`echo "${SW_PHP_VERSION}" | $AWK 'BEGIN { FS = "."; } { printf "%d", ([$]1 * 10 + [$]2); }'` SW_PHP_THIRDPARTY_DIR="thirdparty/php${SW_PHP_VERSION_ID}" AC_MSG_NOTICE([php version: $SW_PHP_VERSION, version_id: $SW_PHP_VERSION_ID, thirdparty_dir: $SW_PHP_THIRDPARTY_DIR]) if test "$PHP_SWOOLE_CURL" != "no"; then if test "$SW_PHP_VERSION_ID" -ge "84"; then swoole_source_file="$swoole_source_file \ thirdparty/php84/curl/interface.cc \ thirdparty/php84/curl/multi.cc" else swoole_source_file="$swoole_source_file \ thirdparty/php/curl/interface.cc \ thirdparty/php/curl/multi.cc" fi fi if test "$PHP_SWOOLE_PGSQL" != "no"; then swoole_source_file="$swoole_source_file \ ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_driver.c \ ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_statement.c" if test "$SW_PHP_VERSION_ID" -ge "84"; then swoole_source_file="$swoole_source_file \ ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_sql_parser.c" fi fi if test "$PHP_SWOOLE_ORACLE" != "no"; then swoole_source_file="$swoole_source_file \ thirdparty/pdo_oci/oci_driver.c \ thirdparty/pdo_oci/oci_statement.c" fi if test "$PHP_SWOOLE_ODBC" != "no"; then swoole_source_file="$swoole_source_file \ ${SW_PHP_THIRDPARTY_DIR}/pdo_odbc/odbc_driver.c \ ${SW_PHP_THIRDPARTY_DIR}/pdo_odbc/odbc_stmt.c" fi if test "$PHP_SWOOLE_SQLITE" != "no"; then swoole_source_file="$swoole_source_file \ thirdparty/pdo_sqlite/sqlite_driver.c \ thirdparty/pdo_sqlite/sqlite_statement.c" if test "$SW_PHP_VERSION_ID" -ge "84"; then swoole_source_file="$swoole_source_file \ thirdparty/pdo_sqlite/sqlite_sql_parser.c" fi fi if test "$PHP_SWOOLE_FIREBIRD" != "no"; then if test "$SW_PHP_VERSION_ID" -ge "85"; then swoole_source_file="$swoole_source_file \ thirdparty/php85/pdo_firebird/firebird_driver.c \ thirdparty/php85/pdo_firebird/firebird_statement.c \ thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp" else swoole_source_file="$swoole_source_file \ thirdparty/php84/pdo_firebird/firebird_driver.c \ thirdparty/php84/pdo_firebird/firebird_statement.c \ thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp" fi fi if test "$PHP_SWOOLE_SSH2" != "no"; then swoole_source_file="$swoole_source_file \ thirdparty/php/ssh2/ssh2.cc \ thirdparty/php/ssh2/ssh2_fopen_wrappers.cc \ thirdparty/php/ssh2/ssh2_sftp.cc" fi if test "$PHP_SWOOLE_FTP" != "no"; then AC_DEFINE(SW_HAVE_FTP_SSL, 1, [have swoole-ftp with SSL]) swoole_source_file="$swoole_source_file \ thirdparty/php84/ftp/ftp.c \ thirdparty/php84/ftp/php_ftp.c" AC_DEFINE(SW_HAVE_FTP, 1, [have swoole-ftp]) fi SW_ASM_DIR="thirdparty/boost/asm/" SW_USE_ASM_CONTEXT="yes" AS_CASE([$host_cpu], [x86_64*], [SW_CPU="x86_64"], [amd64*], [SW_CPU="x86_64"], [x86*], [SW_CPU="x86"], [i?86*], [SW_CPU="x86"], [arm64*], [SW_CPU="arm64"], [aarch64*], [SW_CPU="arm64"], [arm*], [SW_CPU="arm32"], [mips64*], [SW_CPU="mips64"], [mips*], [SW_CPU="mips32"], [riscv64*], [SW_CPU="riscv64"], [loongarch64*], [SW_CPU="loongarch64"], [ SW_USE_ASM_CONTEXT="no" ] ) if test "$SW_OS" = "MAC"; then SW_CONTEXT_ASM_FILE="combined_sysv_macho_gas.S" elif test "$SW_CPU" = "x86_64"; then if test "$SW_OS" = "LINUX" || test "$SW_OS" = "BSD"; then SW_CONTEXT_ASM_FILE="x86_64_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "x86"; then if test "$SW_OS" = "LINUX" || test "$SW_OS" = "BSD"; then SW_CONTEXT_ASM_FILE="i386_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "arm32"; then if test "$SW_OS" = "LINUX" || test "$SW_OS" = "BSD"; then SW_CONTEXT_ASM_FILE="arm_aapcs_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "arm64"; then if test "$SW_OS" = "LINUX" || test "$SW_OS" = "BSD"; then SW_CONTEXT_ASM_FILE="arm64_aapcs_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "ppc32"; then if test "$SW_OS" = "LINUX"; then SW_CONTEXT_ASM_FILE="ppc32_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "ppc64"; then if test "$SW_OS" = "LINUX" || test "$SW_OS" = "BSD"; then SW_CONTEXT_ASM_FILE="ppc64_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "mips64"; then if test "$SW_OS" = "LINUX"; then SW_CONTEXT_ASM_FILE="mips64_n64_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "mips32"; then if test "$SW_OS" = "LINUX"; then SW_CONTEXT_ASM_FILE="mips32_o32_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "riscv64"; then if test "$SW_OS" = "LINUX"; then SW_CONTEXT_ASM_FILE="riscv64_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi elif test "$SW_CPU" = "loongarch64"; then if test "$SW_OS" = "LINUX"; then SW_CONTEXT_ASM_FILE="loongarch64_sysv_elf_gas.S" else SW_USE_ASM_CONTEXT="no" fi else SW_USE_ASM_CONTEXT="no" fi if test "$PHP_THREAD_CONTEXT" != "no"; then AC_DEFINE(SW_USE_THREAD_CONTEXT, 1, [do we enable thread context]) SW_USE_ASM_CONTEXT="no" fi if test "$SW_USE_ASM_CONTEXT" = "yes"; then swoole_source_file="$swoole_source_file \ ${SW_ASM_DIR}make_${SW_CONTEXT_ASM_FILE} \ ${SW_ASM_DIR}jump_${SW_CONTEXT_ASM_FILE} " AC_DEFINE(SW_USE_ASM_CONTEXT, 1, [use boost asm context]) fi EXTRA_CFLAGS="$EXTRA_CFLAGS -DENABLE_PHP_SWOOLE" PHP_NEW_EXTENSION(swoole, $swoole_source_file, $ext_shared,, "$EXTRA_CFLAGS", cxx) PHP_ADD_INCLUDE([$ext_srcdir]) PHP_ADD_INCLUDE([$ext_srcdir/include]) PHP_ADD_INCLUDE([$ext_srcdir/ext-src]) PHP_ADD_INCLUDE([$ext_srcdir/thirdparty]) AC_MSG_CHECKING([swoole coverage]) if test "$PHP_SWOOLE_COVERAGE" != "no"; then AC_MSG_RESULT([enabled]) PHP_ADD_MAKEFILE_FRAGMENT else AC_MSG_RESULT([disabled]) fi PHP_INSTALL_HEADERS([ext/swoole], [ext-src/*.h config.h php_swoole.h \ include/*.h \ stubs/*.h \ thirdparty/*.h \ thirdparty/llhttp/*.h \ thirdparty/nghttp2/*.h]) PHP_REQUIRE_CXX() CXXFLAGS="$CXXFLAGS -Wall -Wno-date-time -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations" if test "$SW_OS" = "CYGWIN" || test "$SW_OS" = "MINGW"; then CXXFLAGS="$CXXFLAGS -std=gnu++14" else CXXFLAGS="$CXXFLAGS -std=c++14" fi if test "$SW_CPU" = "arm"; then PHP_ADD_LIBRARY(atomic, 1, SWOOLE_SHARED_LIBADD) fi PHP_ADD_BUILD_DIR($ext_builddir/ext-src) PHP_ADD_BUILD_DIR($ext_builddir/src/core) PHP_ADD_BUILD_DIR($ext_builddir/src/memory) PHP_ADD_BUILD_DIR($ext_builddir/src/reactor) PHP_ADD_BUILD_DIR($ext_builddir/src/lock) PHP_ADD_BUILD_DIR($ext_builddir/src/os) PHP_ADD_BUILD_DIR($ext_builddir/src/network) PHP_ADD_BUILD_DIR($ext_builddir/src/server) PHP_ADD_BUILD_DIR($ext_builddir/src/protocol) PHP_ADD_BUILD_DIR($ext_builddir/src/coroutine) PHP_ADD_BUILD_DIR($ext_builddir/src/wrapper) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/boost) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/boost/asm) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/sockets) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/standard) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/curl) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/ssh2) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/pdo_oci) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/pdo_sqlite) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/curl) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_firebird) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/pdo_firebird) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/llhttp) if test "$PHP_NGHTTP2_DIR" = "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/nghttp2) fi if test "$PHP_SWOOLE_PGSQL" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql) fi if test "$PHP_SWOOLE_ODBC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/${SW_PHP_THIRDPARTY_DIR}/pdo_odbc) fi if test "$PHP_SWOOLE_FTP" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/ftp) fi fi ================================================ FILE: core-tests/.gitignore ================================================ .idea/ .cmake/ Testing/ build.ninja .ninja_deps .ninja_log ================================================ FILE: core-tests/code-stats.sh ================================================ #!/bin/sh -e __CURRENT__=$(pwd) __DIR__=$(cd "$(dirname "$0")";pwd) # enter the dir cd "${__DIR__}" cloc . --exclude-dir=js,Debug,CMakeFiles,build,CMakeFiles,deps ================================================ FILE: core-tests/docker-compose.yml ================================================ version: '3.4' services: httpbin: container_name: "httpbin" image: "kennethreitz/httpbin" tinyproxy: container_name: "tinyproxy" image: "vimagick/tinyproxy" ports: - 8888:8888 socks5: container_name: "socks5" image: "xkuma/socks5" ports: - "8080:1080" environment: - PROXY_USER=user - PROXY_PASSWORD=password - PROXY_SERVER=0.0.0.0:1080 socks5-no-auth: image: "xkuma/socks5" ports: - 8081:1080 environment: PROXY_SERVER: 0.0.0.0:1080 ================================================ FILE: core-tests/fuzz/project.json ================================================ { "project": { "name": "fuzz", "type": "bin" }, "build": { "ldflags": "", "cxxflags": "", "cflags": "", "c_std": "", "cxx_std": "" }, "install": { "target": "" } } ================================================ FILE: core-tests/fuzz/src/main.cpp ================================================ #include "phpx_embed.h" #include #include #include #include using namespace php; using namespace std; int main(int argc, char * argv[]) { VM vm(argc, argv); cout << "hello world" << endl; char buf[8192]; ssize_t n; int fd = 0; if (argc > 0) { fd = open(argv[1], O_RDONLY); } n = read(fd, buf, 8192); if (n < 0) { fprintf(stderr, "failed to read data\n"); return 1; } auto req_var = exec("Swoole\\Http\\Request::create"); var_dump(req_var); if (!req_var.isObject()) { fprintf(stderr, "cannot create object of Swoole\\Http\\Request\n"); return 2; } Variant data(buf, n); auto req = Object(req_var); auto retval = req.exec("parse", data); printf("retval=%ld", retval.toInt()); var_dump(req); return 0; } ================================================ FILE: core-tests/include/httplib_client.h ================================================ /** * httplib.h * * Copyright (c) 2020 Yuji Hirose. All rights reserved. * MIT License * GitHub: https://github.com/yhirose/cpp-httplib */ #pragma once #include "swoole_websocket.h" /* * Configuration */ #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT #define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 #endif #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND #define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 #endif #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND #define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND #define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND #define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND #define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND #define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND #define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND #ifdef _WIN32 #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 #else #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 #endif #endif #ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 #endif #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) #endif #ifndef CPPHTTPLIB_TCP_NODELAY #define CPPHTTPLIB_TCP_NODELAY true #endif #ifndef CPPHTTPLIB_RECV_BUFSIZ #define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) #endif #ifndef CPPHTTPLIB_THREAD_POOL_COUNT #define CPPHTTPLIB_THREAD_POOL_COUNT \ ((std::max)(8u, std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() - 1 : 0)) #endif // Prefer gnu::deprecated, otherwise gcc complains if we use // [[deprecated]] together with pedantic. #ifndef CPPHTTPLIB_DEPRECATED #if defined(__has_cpp_attribute) #if __has_cpp_attribute(gnu::deprecated) #define CPPHTTPLIB_DEPRECATED [[gnu::deprecated]] #else #if __has_cpp_attribute(deprecated) #define CPPHTTPLIB_DEPRECATED [[deprecated]] #else #define CPPHTTPLIB_DEPRECATED #endif #endif #else #define CPPHTTPLIB_DEPRECATED #endif #endif /* * Headers */ #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif //_CRT_SECURE_NO_WARNINGS #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif //_CRT_NONSTDC_NO_DEPRECATE #if defined(_MSC_VER) #ifdef _WIN64 using ssize_t = __int64; #else using ssize_t = int; #endif #if _MSC_VER < 1900 #define snprintf _snprintf_s #endif #endif // _MSC_VER #ifndef S_ISREG #define S_ISREG(m) (((m) &S_IFREG) == S_IFREG) #endif // S_ISREG #ifndef S_ISDIR #define S_ISDIR(m) (((m) &S_IFDIR) == S_IFDIR) #endif // S_ISDIR #ifndef NOMINMAX #define NOMINMAX #endif // NOMINMAX #include #include #include #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") #endif #ifndef strcasecmp #define strcasecmp _stricmp #endif // strcasecmp using socket_t = SOCKET; #ifdef CPPHTTPLIB_USE_POLL #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) #endif #else // not _WIN32 #include #include #include #include #include #include #ifdef CPPHTTPLIB_USE_POLL #include #endif #include #include #include #include #include using socket_t = int; #define INVALID_SOCKET (-1) #endif //_WIN32 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CPPHTTPLIB_OPENSSL_SUPPORT #include #include #include #include #include #include #include // #if OPENSSL_VERSION_NUMBER < 0x1010100fL // #error Sorry, OpenSSL versions prior to 1.1.1 are not supported // #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L #include inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { return M_ASN1_STRING_data(asn1); } #endif #endif #ifdef CPPHTTPLIB_ZLIB_SUPPORT #include #endif /* * Declaration */ namespace httplib { const std::string USER_AGENT = "cpp-httplib/0.7"; namespace detail { struct ci { bool operator()(const std::string &s1, const std::string &s2) const { return std::lexicographical_compare( s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); }); } }; } // namespace detail using Headers = std::multimap; using Params = std::multimap; using Match = std::smatch; using Progress = std::function; struct Response; struct WebSocketFrame; using ResponseHandler = std::function; struct MultipartFormData { std::string name; std::string content; std::string filename; std::string content_type; }; using MultipartFormDataItems = std::vector; using MultipartFormDataMap = std::multimap; class DataSink { public: DataSink() : os(&sb_), sb_(*this) {} DataSink(const DataSink &) = delete; DataSink &operator=(const DataSink &) = delete; DataSink(DataSink &&) = delete; DataSink &operator=(DataSink &&) = delete; std::function write; std::function done; std::function is_writable; std::ostream os; private: class data_sink_streambuf : public std::streambuf { public: data_sink_streambuf(DataSink &sink) : sink_(sink) {} protected: std::streamsize xsputn(const char *s, std::streamsize n) { sink_.write(s, static_cast(n)); return n; } private: DataSink &sink_; }; data_sink_streambuf sb_; }; using ContentProvider = std::function; using ChunkedContentProvider = std::function; using ContentReceiver = std::function; using MultipartContentHeader = std::function; using Range = std::pair; using Ranges = std::vector; struct Request { std::string method; std::string path; Headers headers; std::string body; std::string remote_addr; int remote_port = -1; // for server std::string version; std::string target; Params params; MultipartFormDataMap files; Ranges ranges; Match matches; // for client size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT; ResponseHandler response_handler; ContentReceiver content_receiver; size_t content_length = 0; ContentProvider content_provider; Progress progress; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT const SSL *ssl; #endif bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); void set_header(const char *key, const std::string &val); bool has_param(const char *key) const; std::string get_param_value(const char *key, size_t id = 0) const; size_t get_param_value_count(const char *key) const; bool is_multipart_form_data() const; bool has_file(const char *key) const; MultipartFormData get_file_value(const char *key) const; // private members... size_t authorization_count_ = 0; }; struct Response { std::string version; int status = -1; Headers headers; std::string body; bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); void set_header(const char *key, const std::string &val); void set_redirect(const char *url, int status = 302); void set_content(const char *s, size_t n, const char *content_type); void set_content(std::string s, const char *content_type); void set_content_provider( size_t length, ContentProvider provider, std::function resource_releaser = [] {}); void set_chunked_content_provider( ChunkedContentProvider provider, std::function resource_releaser = [] {}); Response() = default; Response(const Response &) = default; Response &operator=(const Response &) = default; Response(Response &&) = default; Response &operator=(Response &&) = default; ~Response() { if (content_provider_resource_releaser_) { content_provider_resource_releaser_(); } } // private members... size_t content_length_ = 0; ContentProvider content_provider_; std::function content_provider_resource_releaser_; }; struct WebSocketFrame : public swoole::websocket::Frame { WebSocketFrame() = default; ~WebSocketFrame() { if (payload) { sw_free(payload - header_length); } } }; class Stream { public: virtual ~Stream() = default; virtual bool is_readable() const = 0; virtual bool is_writable() const = 0; virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0; virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; template ssize_t write_format(const char *fmt, const Args &...args); ssize_t write(const char *ptr); ssize_t write(const std::string &s); }; using Logger = std::function; using SocketOptions = std::function; inline void default_socket_options(socket_t sock) { int yes = 1; #ifdef _WIN32 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)); setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast(&yes), sizeof(yes)); #else #ifdef SO_REUSEPORT setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), sizeof(yes)); #else setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)); #endif #endif } class Client { public: explicit Client(const std::string &host); explicit Client(const std::string &host, int port); explicit Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); virtual ~Client(); virtual bool is_valid() const; std::shared_ptr Get(const char *path); std::shared_ptr Get(const char *path, const Headers &headers); std::shared_ptr Get(const char *path, Progress progress); std::shared_ptr Get(const char *path, const Headers &headers, Progress progress); std::shared_ptr Get(const char *path, ContentReceiver content_receiver); std::shared_ptr Get(const char *path, const Headers &headers, ContentReceiver content_receiver); std::shared_ptr Get(const char *path, ContentReceiver content_receiver, Progress progress); std::shared_ptr Get(const char *path, const Headers &headers, ContentReceiver content_receiver, Progress progress); std::shared_ptr Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver); std::shared_ptr Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); bool Upgrade(const char *path, Headers &headers); std::shared_ptr Head(const char *path); std::shared_ptr Head(const char *path, const Headers &headers); std::shared_ptr Post(const char *path); std::shared_ptr Post(const char *path, const std::string &body, const char *content_type); std::shared_ptr Post(const char *path, const Headers &headers, const std::string &body, const char *content_type); std::shared_ptr Post(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Post(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Post(const char *path, const Params ¶ms); std::shared_ptr Post(const char *path, const Headers &headers, const Params ¶ms); std::shared_ptr Post(const char *path, const MultipartFormDataItems &items); std::shared_ptr Post(const char *path, const Headers &headers, const MultipartFormDataItems &items); std::shared_ptr Put(const char *path); std::shared_ptr Put(const char *path, const std::string &body, const char *content_type); std::shared_ptr Put(const char *path, const Headers &headers, const std::string &body, const char *content_type); std::shared_ptr Put(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Put(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Put(const char *path, const Params ¶ms); std::shared_ptr Put(const char *path, const Headers &headers, const Params ¶ms); // websocket inline bool Push(const std::string &data, int opcode = swoole::websocket::OPCODE_TEXT) { return Push(data.c_str(), data.length(), swoole::websocket::OPCODE_TEXT); } bool Push(const char *data, size_t length, int opcode = swoole::websocket::OPCODE_TEXT); std::shared_ptr Recv(); std::shared_ptr Patch(const char *path, const std::string &body, const char *content_type); std::shared_ptr Patch(const char *path, const Headers &headers, const std::string &body, const char *content_type); std::shared_ptr Patch(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Patch(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type); std::shared_ptr Delete(const char *path); std::shared_ptr Delete(const char *path, const std::string &body, const char *content_type); std::shared_ptr Delete(const char *path, const Headers &headers); std::shared_ptr Delete(const char *path, const Headers &headers, const std::string &body, const char *content_type); std::shared_ptr Options(const char *path); std::shared_ptr Options(const char *path, const Headers &headers); bool send(const Request &req, Response &res); size_t is_socket_open() const; void stop(); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); CPPHTTPLIB_DEPRECATED void set_timeout_sec(time_t timeout_sec); void set_connection_timeout(time_t sec, time_t usec = 0); void set_read_timeout(time_t sec, time_t usec = 0); void set_write_timeout(time_t sec, time_t usec = 0); void set_basic_auth(const char *username, const char *password); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void set_digest_auth(const char *username, const char *password); #endif void set_keep_alive(bool on); void set_follow_location(bool on); void set_compress(bool on); void set_decompress(bool on); void set_interface(const char *intf); void set_websocket_mask(bool on); void set_proxy(const char *host, int port); void set_proxy_basic_auth(const char *username, const char *password); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void set_proxy_digest_auth(const char *username, const char *password); #endif void set_logger(Logger logger); protected: struct Socket { socket_t sock = INVALID_SOCKET; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT SSL *ssl = nullptr; #endif bool is_open() const { return sock != INVALID_SOCKET; } }; virtual bool create_and_connect_socket(Socket &socket); virtual void close_socket(Socket &socket, bool process_socket_ret); bool process_request(Stream &strm, const Request &req, Response &res, bool close_connection); // Socket endoint information const std::string host_; const int port_; const std::string host_and_port_; // Current open socket Socket socket_; mutable std::mutex socket_mutex_; std::recursive_mutex request_mutex_; // Settings std::string client_cert_path_; std::string client_key_path_; time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; std::string basic_auth_username_; std::string basic_auth_password_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT std::string digest_auth_username_; std::string digest_auth_password_; #endif bool keep_alive_ = false; bool follow_location_ = false; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = nullptr; bool compress_ = false; bool decompress_ = true; std::string interface_; std::string proxy_host_; int proxy_port_ = 80; bool websocket_mask_ = false; std::string proxy_basic_auth_username_; std::string proxy_basic_auth_password_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT std::string proxy_digest_auth_username_; std::string proxy_digest_auth_password_; #endif Logger logger_; void copy_settings(const Client &rhs) { client_cert_path_ = rhs.client_cert_path_; client_key_path_ = rhs.client_key_path_; connection_timeout_sec_ = rhs.connection_timeout_sec_; read_timeout_sec_ = rhs.read_timeout_sec_; read_timeout_usec_ = rhs.read_timeout_usec_; write_timeout_sec_ = rhs.write_timeout_sec_; write_timeout_usec_ = rhs.write_timeout_usec_; basic_auth_username_ = rhs.basic_auth_username_; basic_auth_password_ = rhs.basic_auth_password_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT digest_auth_username_ = rhs.digest_auth_username_; digest_auth_password_ = rhs.digest_auth_password_; #endif keep_alive_ = rhs.keep_alive_; follow_location_ = rhs.follow_location_; tcp_nodelay_ = rhs.tcp_nodelay_; socket_options_ = rhs.socket_options_; compress_ = rhs.compress_; decompress_ = rhs.decompress_; interface_ = rhs.interface_; proxy_host_ = rhs.proxy_host_; proxy_port_ = rhs.proxy_port_; proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; #endif websocket_mask_ = rhs.websocket_mask_; logger_ = rhs.logger_; } private: socket_t create_client_socket() const; bool read_response_line(Stream &strm, Response &res); bool write_request(Stream &strm, const Request &req, bool close_connection); bool redirect(const Request &req, Response &res); bool handle_request(Stream &strm, const Request &req, Response &res, bool close_connection); std::shared_ptr send_with_content_provider(const char *method, const char *path, const Headers &headers, const std::string &body, size_t content_length, ContentProvider content_provider, const char *content_type); virtual bool process_socket(Socket &socket, std::function callback); virtual bool is_ssl() const; }; inline void Get(std::vector &requests, const char *path, const Headers &headers) { Request req; req.method = "GET"; req.path = path; req.headers = headers; requests.emplace_back(std::move(req)); } inline void Get(std::vector &requests, const char *path) { Get(requests, path, Headers()); } inline void Post(std::vector &requests, const char *path, const Headers &headers, const std::string &body, const char *content_type) { Request req; req.method = "POST"; req.path = path; req.headers = headers; if (content_type) { req.headers.emplace("Content-Type", content_type); } req.body = body; requests.emplace_back(std::move(req)); } inline void Post(std::vector &requests, const char *path, const std::string &body, const char *content_type) { Post(requests, path, Headers(), body, content_type); } inline void Post(std::vector &requests, const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { Request req; req.method = "POST"; req.headers = Headers(); req.path = path; req.content_length = content_length; req.content_provider = content_provider; if (content_type) { req.headers.emplace("Content-Type", content_type); } requests.emplace_back(std::move(req)); } class Client2 { public: explicit Client2(const char *scheme_host_port) : Client2(scheme_host_port, std::string(), std::string()) {} explicit Client2(const char *scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path) { const static std::regex re(R"(^(https?)://([^:/?#]+)(?::(\d+))?)"); std::cmatch m; if (std::regex_match(scheme_host_port, m, re)) { auto scheme = m[1].str(); auto host = m[2].str(); auto port_str = m[3].str(); auto port = !port_str.empty() ? std::stoi(port_str) : (scheme == "https" ? 443 : 80); if (scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT is_ssl_ = true; cli_ = std::make_shared(host.c_str(), port, client_cert_path, client_key_path); #endif } else { cli_ = std::make_shared(host.c_str(), port, client_cert_path, client_key_path); } } } ~Client2() {} bool is_valid() const { return cli_ != nullptr; } std::shared_ptr Get(const char *path) { return cli_->Get(path); } std::shared_ptr Get(const char *path, const Headers &headers) { return cli_->Get(path, headers); } std::shared_ptr Get(const char *path, Progress progress) { return cli_->Get(path, progress); } std::shared_ptr Get(const char *path, const Headers &headers, Progress progress) { return cli_->Get(path, headers, progress); } std::shared_ptr Get(const char *path, ContentReceiver content_receiver) { return cli_->Get(path, content_receiver); } std::shared_ptr Get(const char *path, const Headers &headers, ContentReceiver content_receiver) { return cli_->Get(path, headers, content_receiver); } std::shared_ptr Get(const char *path, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, content_receiver, progress); } std::shared_ptr Get(const char *path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, headers, content_receiver, progress); } std::shared_ptr Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { return cli_->Get(path, headers, response_handler, content_receiver); } std::shared_ptr Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, headers, response_handler, content_receiver, progress); } std::shared_ptr Head(const char *path) { return cli_->Head(path); } std::shared_ptr Head(const char *path, const Headers &headers) { return cli_->Head(path, headers); } std::shared_ptr Post(const char *path) { return cli_->Post(path); } std::shared_ptr Post(const char *path, const std::string &body, const char *content_type) { return cli_->Post(path, body, content_type); } std::shared_ptr Post(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return cli_->Post(path, headers, body, content_type); } std::shared_ptr Post(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Post(path, content_length, content_provider, content_type); } std::shared_ptr Post(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Post(path, headers, content_length, content_provider, content_type); } std::shared_ptr Post(const char *path, const Params ¶ms) { return cli_->Post(path, params); } std::shared_ptr Post(const char *path, const Headers &headers, const Params ¶ms) { return cli_->Post(path, headers, params); } std::shared_ptr Post(const char *path, const MultipartFormDataItems &items) { return cli_->Post(path, items); } std::shared_ptr Post(const char *path, const Headers &headers, const MultipartFormDataItems &items) { return cli_->Post(path, headers, items); } std::shared_ptr Put(const char *path) { return cli_->Put(path); } std::shared_ptr Put(const char *path, const std::string &body, const char *content_type) { return cli_->Put(path, body, content_type); } std::shared_ptr Put(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return cli_->Put(path, headers, body, content_type); } std::shared_ptr Put(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Put(path, content_length, content_provider, content_type); } std::shared_ptr Put(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Put(path, headers, content_length, content_provider, content_type); } std::shared_ptr Put(const char *path, const Params ¶ms) { return cli_->Put(path, params); } std::shared_ptr Put(const char *path, const Headers &headers, const Params ¶ms) { return cli_->Put(path, headers, params); } std::shared_ptr Patch(const char *path, const std::string &body, const char *content_type) { return cli_->Patch(path, body, content_type); } std::shared_ptr Patch(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return cli_->Patch(path, headers, body, content_type); } std::shared_ptr Patch(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Patch(path, content_length, content_provider, content_type); } std::shared_ptr Patch(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return cli_->Patch(path, headers, content_length, content_provider, content_type); } std::shared_ptr Delete(const char *path) { return cli_->Delete(path); } std::shared_ptr Delete(const char *path, const std::string &body, const char *content_type) { return cli_->Delete(path, body, content_type); } std::shared_ptr Delete(const char *path, const Headers &headers) { return cli_->Delete(path, headers); } std::shared_ptr Delete(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return cli_->Delete(path, headers, body, content_type); } std::shared_ptr Options(const char *path) { return cli_->Options(path); } std::shared_ptr Options(const char *path, const Headers &headers) { return cli_->Options(path, headers); } bool send(const Request &req, Response &res) { return cli_->send(req, res); } bool is_socket_open() { return cli_->is_socket_open(); } void stop() { cli_->stop(); } void set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } void set_socket_options(SocketOptions socket_options) { cli_->set_socket_options(socket_options); } Client2 &set_connection_timeout(time_t sec, time_t usec) { cli_->set_connection_timeout(sec, usec); return *this; } Client2 &set_read_timeout(time_t sec, time_t usec) { cli_->set_read_timeout(sec, usec); return *this; } Client2 &set_basic_auth(const char *username, const char *password) { cli_->set_basic_auth(username, password); return *this; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT Client2 &set_digest_auth(const char *username, const char *password) { cli_->set_digest_auth(username, password); return *this; } #endif Client2 &set_keep_alive(bool on) { cli_->set_keep_alive(on); return *this; } Client2 &set_follow_location(bool on) { cli_->set_follow_location(on); return *this; } Client2 &set_compress(bool on) { cli_->set_compress(on); return *this; } Client2 &set_decompress(bool on) { cli_->set_decompress(on); return *this; } Client2 &set_interface(const char *intf) { cli_->set_interface(intf); return *this; } Client2 &set_proxy(const char *host, int port) { cli_->set_proxy(host, port); return *this; } Client2 &set_proxy_basic_auth(const char *username, const char *password) { cli_->set_proxy_basic_auth(username, password); return *this; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT Client2 &set_proxy_digest_auth(const char *username, const char *password) { cli_->set_proxy_digest_auth(username, password); return *this; } #endif Client2 &set_logger(Logger logger) { cli_->set_logger(logger); return *this; } // SSL #ifdef CPPHTTPLIB_OPENSSL_SUPPORT Client2 &set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr) { if (is_ssl_) { static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); } return *this; } Client2 &set_ca_cert_store(X509_STORE *ca_cert_store) { if (is_ssl_) { static_cast(*cli_).set_ca_cert_store(ca_cert_store); } return *this; } Client2 &enable_server_certificate_verification(bool enabled) { if (is_ssl_) { static_cast(*cli_).enable_server_certificate_verification(enabled); } return *this; } long get_openssl_verify_result() const { if (is_ssl_) { return static_cast(*cli_).get_openssl_verify_result(); } return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? } SSL_CTX *ssl_context() const { if (is_ssl_) { return static_cast(*cli_).ssl_context(); } return nullptr; } #endif private: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool is_ssl_ = false; #endif std::shared_ptr cli_; }; // ---------------------------------------------------------------------------- /* * Implementation */ namespace detail { inline bool is_hex(char c, int &v) { if (0x20 <= c && isdigit(c)) { v = c - '0'; return true; } else if ('A' <= c && c <= 'F') { v = c - 'A' + 10; return true; } else if ('a' <= c && c <= 'f') { v = c - 'a' + 10; return true; } return false; } inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val) { if (i >= s.size()) { return false; } val = 0; for (; cnt; i++, cnt--) { if (!s[i]) { return false; } int v = 0; if (is_hex(s[i], v)) { val = val * 16 + v; } else { return false; } } return true; } inline std::string from_i_to_hex(size_t n) { const char *charset = "0123456789abcdef"; std::string ret; do { ret = charset[n & 15] + ret; n >>= 4; } while (n > 0); return ret; } inline size_t to_utf8(int code, char *buff) { if (code < 0x0080) { buff[0] = (code & 0x7F); return 1; } else if (code < 0x0800) { buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); buff[1] = static_cast(0x80 | (code & 0x3F)); return 2; } else if (code < 0xD800) { buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); buff[2] = static_cast(0x80 | (code & 0x3F)); return 3; } else if (code < 0xE000) { // D800 - DFFF is invalid... return 0; } else if (code < 0x10000) { buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); buff[2] = static_cast(0x80 | (code & 0x3F)); return 3; } else if (code < 0x110000) { buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); buff[3] = static_cast(0x80 | (code & 0x3F)); return 4; } // NOTREACHED return 0; } // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c inline std::string base64_encode(const std::string &in) { static const auto lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string out; out.reserve(in.size()); int val = 0; int valb = -6; for (auto c : in) { val = (val << 8) + static_cast(c); valb += 8; while (valb >= 0) { out.push_back(lookup[(val >> valb) & 0x3F]); valb -= 6; } } if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } while (out.size() % 4) { out.push_back('='); } return out; } inline bool is_file(const std::string &path) { struct stat st; return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); } inline bool is_dir(const std::string &path) { struct stat st; return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); } inline bool is_valid_path(const std::string &path) { size_t level = 0; size_t i = 0; // Skip slash while (i < path.size() && path[i] == '/') { i++; } while (i < path.size()) { // Read component auto beg = i; while (i < path.size() && path[i] != '/') { i++; } auto len = i - beg; assert(len > 0); if (!path.compare(beg, len, ".")) { ; } else if (!path.compare(beg, len, "..")) { if (level == 0) { return false; } level--; } else { level++; } // Skip slash while (i < path.size() && path[i] == '/') { i++; } } return true; } inline void read_file(const std::string &path, std::string &out) { std::ifstream fs(path, std::ios_base::binary); fs.seekg(0, std::ios_base::end); auto size = fs.tellg(); fs.seekg(0); out.resize(static_cast(size)); fs.read(&out[0], static_cast(size)); } inline std::string file_extension(const std::string &path) { std::smatch m; static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); if (std::regex_search(path, m, re)) { return m[1].str(); } return std::string(); } template void split(const char *b, const char *e, char d, Fn fn) { int i = 0; int beg = 0; while (e ? (b + i != e) : (b[i] != '\0')) { if (b[i] == d) { fn(&b[beg], &b[i]); beg = i + 1; } i++; } if (i) { fn(&b[beg], &b[i]); } } // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` // to store data. The call can set memory on stack for performance. class stream_line_reader { public: stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size) : strm_(strm), fixed_buffer_(fixed_buffer), fixed_buffer_size_(fixed_buffer_size) {} const char *ptr() const { if (glowable_buffer_.empty()) { return fixed_buffer_; } else { return glowable_buffer_.data(); } } size_t size() const { if (glowable_buffer_.empty()) { return fixed_buffer_used_size_; } else { return glowable_buffer_.size(); } } bool end_with_crlf() const { auto end = ptr() + size(); return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; } bool getline() { fixed_buffer_used_size_ = 0; glowable_buffer_.clear(); for (size_t i = 0;; i++) { char byte; auto n = strm_.read(&byte, 1); if (n < 0) { return false; } else if (n == 0) { if (i == 0) { return false; } else { break; } } append(byte); if (byte == '\n') { break; } } return true; } private: void append(char c) { if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { fixed_buffer_[fixed_buffer_used_size_++] = c; fixed_buffer_[fixed_buffer_used_size_] = '\0'; } else { if (glowable_buffer_.empty()) { assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } glowable_buffer_ += c; } } Stream &strm_; char *fixed_buffer_; const size_t fixed_buffer_size_; size_t fixed_buffer_used_size_ = 0; std::string glowable_buffer_; }; inline int close_socket(socket_t sock) { #ifdef _WIN32 return closesocket(sock); #else return close(sock); #endif } template inline ssize_t handle_EINTR(T fn) { ssize_t res = false; while (true) { res = fn(); if (res < 0 && errno == EINTR) { continue; } break; } return res; } inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; pfd_read.events = POLLIN; auto timeout = static_cast(sec * 1000 + usec); return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); return handle_EINTR([&]() { return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); }); #endif } inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; pfd_read.events = POLLOUT; auto timeout = static_cast(sec * 1000 + usec / 1000); return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else fd_set fds; FD_ZERO(&fds); FD_SET(sock, &fds); timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); return handle_EINTR([&]() { return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); }); #endif } inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; pfd_read.events = POLLIN | POLLOUT; auto timeout = static_cast(sec * 1000 + usec); auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); return res >= 0 && !error; } return false; #else fd_set fdsr; FD_ZERO(&fdsr); FD_SET(sock, &fdsr); auto fdsw = fdsr; auto fdse = fdsr; timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); auto ret = handle_EINTR([&]() { return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); }); if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); return getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) >= 0 && !error; } return false; #endif } class SocketStream : public Stream { public: SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec); ~SocketStream() override; bool is_readable() const override; bool is_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; private: socket_t sock_; time_t read_timeout_sec_; time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLSocketStream : public Stream { public: SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec); ~SSLSocketStream() override; bool is_readable() const override; bool is_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; private: socket_t sock_; SSL *ssl_; time_t read_timeout_sec_; time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; }; #endif class BufferStream : public Stream { public: BufferStream() = default; ~BufferStream() override = default; bool is_readable() const override; bool is_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; const std::string &get_buffer() const; private: std::string buffer; size_t position = 0; }; inline bool keep_alive(socket_t sock) { using namespace std::chrono; auto start = steady_clock::now(); while (true) { auto val = select_read(sock, 0, 10000); if (val < 0) { return false; } else if (val == 0) { auto current = steady_clock::now(); auto duration = duration_cast(current - start); auto timeout = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND * 100 + CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; if (duration.count() > timeout) { return false; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } else { return true; } } } template inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); return callback(strm); } inline int shutdown_socket(socket_t sock) { #ifdef _WIN32 return shutdown(sock, SD_BOTH); #else return shutdown(sock, SHUT_RDWR); #endif } template socket_t create_socket(const char *host, int port, int socket_flags, bool tcp_nodelay, SocketOptions socket_options, BindOrConnect bind_or_connect) { // Get address info struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = socket_flags; hints.ai_protocol = 0; auto service = std::to_string(port); if (getaddrinfo(host, service.c_str(), &hints, &result)) { return INVALID_SOCKET; } for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket #ifdef _WIN32 auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); /** * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 * and above the socket creation fails on older Windows Systems. * * Let's try to create a socket the old way in this case. * * Reference: * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa * * WSA_FLAG_NO_HANDLE_INHERIT: * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with * SP1, and later * */ if (sock == INVALID_SOCKET) { sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); } #else auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); #endif if (sock == INVALID_SOCKET) { continue; } #ifndef _WIN32 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } #endif if (tcp_nodelay) { int yes = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), sizeof(yes)); } if (socket_options) { socket_options(sock); } if (rp->ai_family == AF_INET6) { int no = 0; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), sizeof(no)); } // bind or connect if (bind_or_connect(sock, *rp)) { freeaddrinfo(result); return sock; } close_socket(sock); } freeaddrinfo(result); return INVALID_SOCKET; } inline void set_nonblocking(socket_t sock, bool nonblocking) { #ifdef _WIN32 auto flags = nonblocking ? 1UL : 0UL; ioctlsocket(sock, FIONBIO, &flags); #else auto flags = fcntl(sock, F_GETFL, 0); fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); #endif } inline bool is_connection_error() { #ifdef _WIN32 return WSAGetLastError() != WSAEWOULDBLOCK; #else return errno != EINPROGRESS; #endif } inline bool bind_ip_address(socket_t sock, const char *host) { struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; if (getaddrinfo(host, "0", &hints, &result)) { return false; } auto ret = false; for (auto rp = result; rp; rp = rp->ai_next) { const auto &ai = *rp; if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { ret = true; break; } } freeaddrinfo(result); return ret; } #ifndef _WIN32 inline std::string if2ip(const std::string &ifn) { struct ifaddrs *ifap; getifaddrs(&ifap); for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr && ifn == ifa->ifa_name) { if (ifa->ifa_addr->sa_family == AF_INET) { auto sa = reinterpret_cast(ifa->ifa_addr); char buf[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { freeifaddrs(ifap); return std::string(buf, INET_ADDRSTRLEN); } } } } freeifaddrs(ifap); return std::string(); } #endif inline socket_t create_client_socket(const char *host, int port, bool tcp_nodelay, SocketOptions socket_options, time_t timeout_sec, time_t timeout_usec, const std::string &intf) { return create_socket(host, port, 0, tcp_nodelay, socket_options, [&](socket_t sock, struct addrinfo &ai) -> bool { if (!intf.empty()) { #ifndef _WIN32 auto ip = if2ip(intf); if (ip.empty()) { ip = intf; } if (!bind_ip_address(sock, ip.c_str())) { return false; } #endif } set_nonblocking(sock, true); auto ret = ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); if (ret < 0) { if (is_connection_error() || !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { close_socket(sock); return false; } } set_nonblocking(sock, false); return true; }); } inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_len, std::string &ip, int &port) { if (addr.ss_family == AF_INET) { port = ntohs(reinterpret_cast(&addr)->sin_port); } else if (addr.ss_family == AF_INET6) { port = ntohs(reinterpret_cast(&addr)->sin6_port); } std::array ipstr{}; if (!getnameinfo(reinterpret_cast(&addr), addr_len, ipstr.data(), static_cast(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) { ip = ipstr.data(); } } inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { get_remote_ip_and_port(addr, addr_len, ip, port); } } inline const char *find_content_type(const std::string &path, const std::map &user_data) { auto ext = file_extension(path); auto it = user_data.find(ext); if (it != user_data.end()) { return it->second.c_str(); } if (ext == "txt") { return "text/plain"; } else if (ext == "html" || ext == "htm") { return "text/html"; } else if (ext == "css") { return "text/css"; } else if (ext == "jpeg" || ext == "jpg") { return "image/jpg"; } else if (ext == "png") { return "image/png"; } else if (ext == "gif") { return "image/gif"; } else if (ext == "svg") { return "image/svg+xml"; } else if (ext == "ico") { return "image/x-icon"; } else if (ext == "json") { return "application/json"; } else if (ext == "pdf") { return "application/pdf"; } else if (ext == "js") { return "application/javascript"; } else if (ext == "wasm") { return "application/wasm"; } else if (ext == "xml") { return "application/xml"; } else if (ext == "xhtml") { return "application/xhtml+xml"; } return nullptr; } inline const char *status_message(int status) { switch (status) { case 100: return "Continue"; case 101: return "Switching Protocol"; case 102: return "Processing"; case 103: return "Early Hints"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 207: return "Multi-Status"; case 208: return "Already Reported"; case 226: return "IM Used"; case 300: return "Multiple Choice"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 306: return "unused"; case 307: return "Temporary Redirect"; case 308: return "Permanent Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Payload Too Large"; case 414: return "URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Range Not Satisfiable"; case 417: return "Expectation Failed"; case 418: return "I'm a teapot"; case 421: return "Misdirected Request"; case 422: return "Unprocessable Entity"; case 423: return "Locked"; case 424: return "Failed Dependency"; case 425: return "Too Early"; case 426: return "Upgrade Required"; case 428: return "Precondition Required"; case 429: return "Too Many Requests"; case 431: return "Request Header Fields Too Large"; case 451: return "Unavailable For Legal Reasons"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; case 506: return "Variant Also Negotiates"; case 507: return "Insufficient Storage"; case 508: return "Loop Detected"; case 510: return "Not Extended"; case 511: return "Network Authentication Required"; default: case 500: return "Internal Server Error"; } } #ifdef CPPHTTPLIB_ZLIB_SUPPORT inline bool can_compress(const std::string &content_type) { return !content_type.find("text/") || content_type == "image/svg+xml" || content_type == "application/javascript" || content_type == "application/json" || content_type == "application/xml" || content_type == "application/xhtml+xml"; } inline bool compress(std::string &content) { z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); if (ret != Z_OK) { return false; } strm.avail_in = static_cast(content.size()); strm.next_in = const_cast(reinterpret_cast(content.data())); std::string compressed; std::array buff{}; do { strm.avail_out = buff.size(); strm.next_out = reinterpret_cast(buff.data()); ret = deflate(&strm, Z_FINISH); assert(ret != Z_STREAM_ERROR); compressed.append(buff.data(), buff.size() - strm.avail_out); } while (strm.avail_out == 0); assert(ret == Z_STREAM_END); assert(strm.avail_in == 0); content.swap(compressed); deflateEnd(&strm); return true; } class decompressor { public: decompressor() { std::memset(&strm, 0, sizeof(strm)); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; // 15 is the value of wbits, which should be at the maximum possible value // to ensure that any gzip stream can be decoded. The offset of 32 specifies // that the stream type should be automatically detected either gzip or // deflate. is_valid_ = inflateInit2(&strm, 32 + 15) == Z_OK; } ~decompressor() { inflateEnd(&strm); } bool is_valid() const { return is_valid_; } template bool decompress(const char *data, size_t data_length, T callback) { int ret = Z_OK; strm.avail_in = static_cast(data_length); strm.next_in = const_cast(reinterpret_cast(data)); std::array buff{}; do { strm.avail_out = buff.size(); strm.next_out = reinterpret_cast(buff.data()); ret = inflate(&strm, Z_NO_FLUSH); assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&strm); return false; } if (!callback(buff.data(), buff.size() - strm.avail_out)) { return false; } } while (strm.avail_out == 0); return ret == Z_OK || ret == Z_STREAM_END; } private: bool is_valid_; z_stream strm; }; #endif inline bool has_header(const Headers &headers, const char *key) { return headers.find(key) != headers.end(); } inline const char *get_header_value(const Headers &headers, const char *key, size_t id = 0, const char *def = nullptr) { auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); if (it != rng.second) { return it->second.c_str(); } return def; } inline uint64_t get_header_value_uint64(const Headers &headers, const char *key, uint64_t def = 0) { auto it = headers.find(key); if (it != headers.end()) { return std::strtoull(it->second.data(), nullptr, 10); } return def; } inline void parse_header(const char *beg, const char *end, Headers &headers) { auto p = beg; while (p < end && *p != ':') { p++; } if (p < end) { auto key_end = p; p++; // skip ':' while (p < end && (*p == ' ' || *p == '\t')) { p++; } if (p < end) { auto val_begin = p; while (p < end) { p++; } headers.emplace(std::string(beg, key_end), std::string(val_begin, end)); } } } inline bool read_headers(Stream &strm, Headers &headers) { const auto bufsiz = 2048; char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); for (;;) { if (!line_reader.getline()) { return false; } // Check if the line ends with CRLF. if (line_reader.end_with_crlf()) { // Blank line indicates end of headers. if (line_reader.size() == 2) { break; } } else { continue; // Skip invalid line. } // Skip trailing spaces and tabs. auto end = line_reader.ptr() + line_reader.size() - 2; while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) { end--; } parse_header(line_reader.ptr(), end, headers); } return true; } inline bool read_content_with_length(Stream &strm, uint64_t len, Progress progress, ContentReceiver out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; while (r < len) { auto read_len = static_cast(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } if (!out(buf, static_cast(n))) { return false; } r += static_cast(n); if (progress) { if (!progress(r, len)) { return false; } } } return true; } inline void skip_content_with_length(Stream &strm, uint64_t len) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; while (r < len) { auto read_len = static_cast(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return; } r += static_cast(n); } } inline bool read_content_without_length(Stream &strm, ContentReceiver out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); if (n < 0) { return false; } else if (n == 0) { return true; } if (!out(buf, static_cast(n))) { return false; } } return true; } inline bool read_content_chunked(Stream &strm, ContentReceiver out) { const auto bufsiz = 16; char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); if (!line_reader.getline()) { return false; } unsigned long chunk_len; while (true) { char *end_ptr; chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); if (end_ptr == line_reader.ptr()) { return false; } if (chunk_len == ULONG_MAX) { return false; } if (chunk_len == 0) { break; } if (!read_content_with_length(strm, chunk_len, nullptr, out)) { return false; } if (!line_reader.getline()) { return false; } if (strcmp(line_reader.ptr(), "\r\n")) { break; } if (!line_reader.getline()) { return false; } } if (chunk_len == 0) { // Reader terminator after chunks if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) return false; } return true; } inline bool is_chunked_transfer_encoding(const Headers &headers) { return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked"); } template bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, Progress progress, ContentReceiver receiver, bool decompress) { ContentReceiver out = [&](const char *buf, size_t n) { return receiver(buf, n); }; #ifdef CPPHTTPLIB_ZLIB_SUPPORT decompressor decompressor; #endif if (decompress) { #ifdef CPPHTTPLIB_ZLIB_SUPPORT std::string content_encoding = x.get_header_value("Content-Encoding"); if (content_encoding.find("gzip") != std::string::npos || content_encoding.find("deflate") != std::string::npos) { if (!decompressor.is_valid()) { status = 500; return false; } out = [&](const char *buf, size_t n) { return decompressor.decompress(buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); }); }; } #else if (x.get_header_value("Content-Encoding") == "gzip") { status = 415; return false; } #endif } auto ret = true; auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { ret = read_content_chunked(strm, out); } else if (!has_header(x.headers, "Content-Length")) { ret = read_content_without_length(strm, out); } else { auto len = get_header_value_uint64(x.headers, "Content-Length", 0); if (len > payload_max_length) { exceed_payload_max_length = true; skip_content_with_length(strm, len); ret = false; } else if (len > 0) { ret = read_content_with_length(strm, len, progress, out); } } if (!ret) { status = exceed_payload_max_length ? 413 : 400; } return ret; } template inline ssize_t write_headers(Stream &strm, const T &info, const Headers &headers) { ssize_t write_len = 0; for (const auto &x : info.headers) { if (x.first == "EXCEPTION_WHAT") { continue; } auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); if (len < 0) { return len; } write_len += len; } for (const auto &x : headers) { auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); if (len < 0) { return len; } write_len += len; } auto len = strm.write("\r\n"); if (len < 0) { return len; } write_len += len; return write_len; } inline bool write_data(Stream &strm, const char *d, size_t l) { size_t offset = 0; while (offset < l) { auto length = strm.write(d + offset, l - offset); if (length < 0) { return false; } offset += static_cast(length); } return true; } template inline ssize_t write_content( Stream &strm, ContentProvider content_provider, size_t offset, size_t length, T is_shutting_down) { size_t begin_offset = offset; size_t end_offset = offset + length; auto ok = true; DataSink data_sink; data_sink.write = [&](const char *d, size_t l) { if (ok) { offset += l; if (!write_data(strm, d, l)) { ok = false; } } }; data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; while (ok && offset < end_offset && !is_shutting_down()) { if (!content_provider(offset, end_offset - offset, data_sink)) { return -1; } if (!ok) { return -1; } } return static_cast(offset - begin_offset); } template inline ssize_t write_content_chunked(Stream &strm, ContentProvider content_provider, T is_shutting_down) { size_t offset = 0; auto data_available = true; ssize_t total_written_length = 0; auto ok = true; DataSink data_sink; data_sink.write = [&](const char *d, size_t l) { if (ok) { data_available = l > 0; offset += l; // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n"; if (write_data(strm, chunk.data(), chunk.size())) { total_written_length += chunk.size(); } else { ok = false; } } }; data_sink.done = [&](void) { data_available = false; if (ok) { static const std::string done_marker("0\r\n\r\n"); if (write_data(strm, done_marker.data(), done_marker.size())) { total_written_length += done_marker.size(); } else { ok = false; } } }; data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; while (data_available && !is_shutting_down()) { if (!content_provider(offset, 0, data_sink)) { return -1; } if (!ok) { return -1; } } return total_written_length; } template inline bool redirect(T &cli, const Request &req, Response &res, const std::string &path) { Request new_req = req; new_req.path = path; new_req.redirect_count -= 1; if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { new_req.method = "GET"; new_req.body.clear(); new_req.headers.clear(); } Response new_res; auto ret = cli.send(new_req, new_res); if (ret) { res = new_res; } return ret; } inline std::string encode_url(const std::string &s) { std::string result; for (size_t i = 0; s[i]; i++) { switch (s[i]) { case ' ': result += "%20"; break; case '+': result += "%2B"; break; case '\r': result += "%0D"; break; case '\n': result += "%0A"; break; case '\'': result += "%27"; break; case ',': result += "%2C"; break; // case ':': result += "%3A"; break; // ok? probably... case ';': result += "%3B"; break; default: auto c = static_cast(s[i]); if (c >= 0x80) { result += '%'; char hex[4]; auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); assert(len == 2); result.append(hex, static_cast(len)); } else { result += s[i]; } break; } } return result; } inline std::string decode_url(const std::string &s, bool convert_plus_to_space) { std::string result; for (size_t i = 0; i < s.size(); i++) { if (s[i] == '%' && i + 1 < s.size()) { if (s[i + 1] == 'u') { int val = 0; if (from_hex_to_i(s, i + 2, 4, val)) { // 4 digits Unicode codes char buff[4]; size_t len = to_utf8(val, buff); if (len > 0) { result.append(buff, len); } i += 5; // 'u0000' } else { result += s[i]; } } else { int val = 0; if (from_hex_to_i(s, i + 1, 2, val)) { // 2 digits hex codes result += static_cast(val); i += 2; // '00' } else { result += s[i]; } } } else if (convert_plus_to_space && s[i] == '+') { result += ' '; } else { result += s[i]; } } return result; } inline std::string params_to_query_str(const Params ¶ms) { std::string query; for (auto it = params.begin(); it != params.end(); ++it) { if (it != params.begin()) { query += "&"; } query += it->first; query += "="; query += detail::encode_url(it->second); } return query; } inline void parse_query_text(const std::string &s, Params ¶ms) { split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) { std::string key; std::string val; split(b, e, '=', [&](const char *b2, const char *e2) { if (key.empty()) { key.assign(b2, e2); } else { val.assign(b2, e2); } }); params.emplace(decode_url(key, true), decode_url(val, true)); }); } inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { auto pos = content_type.find("boundary="); if (pos == std::string::npos) { return false; } boundary = content_type.substr(pos + 9); if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') { boundary = boundary.substr(1, boundary.size() - 2); } return !boundary.empty(); } inline bool parse_range_header(const std::string &s, Ranges &ranges) { static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); std::smatch m; if (std::regex_match(s, m, re_first_range)) { auto pos = static_cast(m.position(1)); auto len = static_cast(m.length(1)); bool all_valid_ranges = true; split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { if (!all_valid_ranges) return; static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); std::cmatch cm; if (std::regex_match(b, e, cm, re_another_range)) { ssize_t first = -1; if (!cm.str(1).empty()) { first = static_cast(std::stoll(cm.str(1))); } ssize_t last = -1; if (!cm.str(2).empty()) { last = static_cast(std::stoll(cm.str(2))); } if (first != -1 && last != -1 && first > last) { all_valid_ranges = false; return; } ranges.emplace_back(std::make_pair(first, last)); } }); return all_valid_ranges; } return false; } class MultipartFormDataParser { public: MultipartFormDataParser() = default; void set_boundary(std::string &&boundary) { boundary_ = boundary; } bool is_valid() const { return is_valid_; } template bool parse(const char *buf, size_t n, T content_callback, U header_callback) { static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)", std::regex_constants::icase); static const std::regex re_content_disposition( "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" "\"(.*?)\")?\\s*$", std::regex_constants::icase); static const std::string dash_ = "--"; static const std::string crlf_ = "\r\n"; buf_.append(buf, n); // TODO: performance improvement while (!buf_.empty()) { switch (state_) { case 0: { // Initial boundary auto pattern = dash_ + boundary_ + crlf_; if (pattern.size() > buf_.size()) { return true; } auto pos = buf_.find(pattern); if (pos != 0) { return false; } buf_.erase(0, pattern.size()); off_ += pattern.size(); state_ = 1; break; } case 1: { // New entry clear_file_info(); state_ = 2; break; } case 2: { // Headers auto pos = buf_.find(crlf_); while (pos != std::string::npos) { // Empty line if (pos == 0) { if (!header_callback(file_)) { is_valid_ = false; return false; } buf_.erase(0, crlf_.size()); off_ += crlf_.size(); state_ = 3; break; } auto header = buf_.substr(0, pos); { std::smatch m; if (std::regex_match(header, m, re_content_type)) { file_.content_type = m[1]; } else if (std::regex_match(header, m, re_content_disposition)) { file_.name = m[1]; file_.filename = m[2]; } } buf_.erase(0, pos + crlf_.size()); off_ += pos + crlf_.size(); pos = buf_.find(crlf_); } if (state_ != 3) { return true; } break; } case 3: { // Body { auto pattern = crlf_ + dash_; if (pattern.size() > buf_.size()) { return true; } auto pos = buf_.find(pattern); if (pos == std::string::npos) { pos = buf_.size(); while (pos > 0) { auto c = buf_[pos - 1]; if (c != '\r' && c != '\n' && c != '-') { break; } pos--; } } if (!content_callback(buf_.data(), pos)) { is_valid_ = false; return false; } off_ += pos; buf_.erase(0, pos); } { auto pattern = crlf_ + dash_ + boundary_; if (pattern.size() > buf_.size()) { return true; } auto pos = buf_.find(pattern); if (pos != std::string::npos) { if (!content_callback(buf_.data(), pos)) { is_valid_ = false; return false; } off_ += pos + pattern.size(); buf_.erase(0, pos + pattern.size()); state_ = 4; } else { if (!content_callback(buf_.data(), pattern.size())) { is_valid_ = false; return false; } off_ += pattern.size(); buf_.erase(0, pattern.size()); } } break; } case 4: { // Boundary if (crlf_.size() > buf_.size()) { return true; } if (buf_.find(crlf_) == 0) { buf_.erase(0, crlf_.size()); off_ += crlf_.size(); state_ = 1; } else { auto pattern = dash_ + crlf_; if (pattern.size() > buf_.size()) { return true; } if (buf_.find(pattern) == 0) { buf_.erase(0, pattern.size()); off_ += pattern.size(); is_valid_ = true; state_ = 5; } else { return true; } } break; } case 5: { // Done is_valid_ = false; return false; } } } return true; } private: void clear_file_info() { file_.name.clear(); file_.filename.clear(); file_.content_type.clear(); } std::string boundary_; std::string buf_; size_t state_ = 0; bool is_valid_ = false; size_t off_ = 0; MultipartFormData file_; }; inline std::string to_lower(const char *beg, const char *end) { std::string out; auto it = beg; while (it != end) { out += static_cast(::tolower(*it)); it++; } return out; } inline std::string make_multipart_data_boundary() { static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; std::random_device seed_gen; std::mt19937 engine(seed_gen()); std::string result = "--cpp-httplib-multipart-data-"; for (auto i = 0; i < 16; i++) { result += data[engine() % (sizeof(data) - 1)]; } return result; } inline std::pair get_range_offset_and_length(const Request &req, size_t content_length, size_t index) { auto r = req.ranges[index]; if (r.first == -1 && r.second == -1) { return std::make_pair(0, content_length); } auto slen = static_cast(content_length); if (r.first == -1) { r.first = slen - r.second; r.second = slen - 1; } if (r.second == -1) { r.second = slen - 1; } return std::make_pair(r.first, r.second - r.first + 1); } inline std::string make_content_range_header_field(size_t offset, size_t length, size_t content_length) { std::string field = "bytes "; field += std::to_string(offset); field += "-"; field += std::to_string(offset + length - 1); field += "/"; field += std::to_string(content_length); return field; } template bool process_multipart_ranges_data(const Request &req, Response &res, const std::string &boundary, const std::string &content_type, SToken stoken, CToken ctoken, Content content) { for (size_t i = 0; i < req.ranges.size(); i++) { ctoken("--"); stoken(boundary); ctoken("\r\n"); if (!content_type.empty()) { ctoken("Content-Type: "); stoken(content_type); ctoken("\r\n"); } auto offsets = get_range_offset_and_length(req, res.body.size(), i); auto offset = offsets.first; auto length = offsets.second; ctoken("Content-Range: "); stoken(make_content_range_header_field(offset, length, res.body.size())); ctoken("\r\n"); ctoken("\r\n"); if (!content(offset, length)) { return false; } ctoken("\r\n"); } ctoken("--"); stoken(boundary); ctoken("--\r\n"); return true; } inline std::string make_multipart_ranges_data(const Request &req, Response &res, const std::string &boundary, const std::string &content_type) { std::string data; process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data += token; }, [&](const char *token) { data += token; }, [&](size_t offset, size_t length) { data += res.body.substr(offset, length); return true; }); return data; } inline size_t get_multipart_ranges_data_length(const Request &req, Response &res, const std::string &boundary, const std::string &content_type) { size_t data_length = 0; process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { data_length += token.size(); }, [&](const char *token) { data_length += strlen(token); }, [&](size_t /*offset*/, size_t length) { data_length += length; return true; }); return data_length; } template inline bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type, T is_shutting_down) { return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); }, [&](const char *token) { strm.write(token); }, [&](size_t offset, size_t length) { return write_content(strm, res.content_provider_, offset, length, is_shutting_down) >= 0; }); } inline std::pair get_range_offset_and_length(const Request &req, const Response &res, size_t index) { auto r = req.ranges[index]; if (r.second == -1) { r.second = static_cast(res.content_length_) - 1; } return std::make_pair(r.first, r.second - r.first + 1); } inline bool expect_content(const Request &req) { if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "PRI" || (req.method == "DELETE" && req.has_header("Content-Length"))) { return true; } // TODO: check if Content-Length is set return false; } inline bool has_crlf(const char *s) { auto p = s; while (*p) { if (*p == '\r' || *p == '\n') { return true; } p++; } return false; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT template inline std::string message_digest(const std::string &s, Init init, Update update, Final final, size_t digest_length) { using namespace std; std::vector md(digest_length, 0); CTX ctx; init(&ctx); update(&ctx, s.data(), s.size()); final(md.data(), &ctx); stringstream ss; for (auto c : md) { ss << setfill('0') << setw(2) << hex << (unsigned int) c; } return ss.str(); } inline std::string MD5(const std::string &s) { return message_digest(s, MD5_Init, MD5_Update, MD5_Final, MD5_DIGEST_LENGTH); } inline std::string SHA_256(const std::string &s) { return message_digest(s, SHA256_Init, SHA256_Update, SHA256_Final, SHA256_DIGEST_LENGTH); } inline std::string SHA_512(const std::string &s) { return message_digest(s, SHA512_Init, SHA512_Update, SHA512_Final, SHA512_DIGEST_LENGTH); } #endif #ifdef _WIN32 class WSInit { public: WSInit() { WSADATA wsaData; WSAStartup(0x0002, &wsaData); } ~WSInit() { WSACleanup(); } }; static WSInit wsinit_; #endif } // namespace detail // Header utilities inline std::pair make_range_header(Ranges ranges) { std::string field = "bytes="; auto i = 0; for (auto r : ranges) { if (i != 0) { field += ", "; } if (r.first != -1) { field += std::to_string(r.first); } field += '-'; if (r.second != -1) { field += std::to_string(r.second); } i++; } return std::make_pair("Range", field); } inline std::pair make_basic_authentication_header(const std::string &username, const std::string &password, bool is_proxy = false) { auto field = "Basic " + detail::base64_encode(username + ":" + password); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline std::pair make_digest_authentication_header( const Request &req, const std::map &auth, size_t cnonce_count, const std::string &cnonce, const std::string &username, const std::string &password, bool is_proxy = false) { using namespace std; string nc; { stringstream ss; ss << setfill('0') << setw(8) << hex << cnonce_count; nc = ss.str(); } auto qop = auth.at("qop"); if (qop.find("auth-int") != std::string::npos) { qop = "auth-int"; } else { qop = "auth"; } std::string algo = "MD5"; if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } string response; { auto H = algo == "SHA-256" ? detail::SHA_256 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; auto A1 = username + ":" + auth.at("realm") + ":" + password; auto A2 = req.method + ":" + req.path; if (qop == "auth-int") { A2 += ":" + H(req.body); } response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2)); } auto field = "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path + "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + "\", response=\"" + response + "\""; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); } #endif inline bool parse_www_authenticate(const Response &res, std::map &auth, bool is_proxy) { auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; if (res.has_header(auth_key)) { static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); auto s = res.get_header_value(auth_key); auto pos = s.find(' '); if (pos != std::string::npos) { auto type = s.substr(0, pos); if (type == "Basic") { return false; } else if (type == "Digest") { s = s.substr(pos + 1); auto beg = std::sregex_iterator(s.begin(), s.end(), re); for (auto i = beg; i != std::sregex_iterator(); ++i) { auto m = *i; auto key = s.substr(static_cast(m.position(1)), static_cast(m.length(1))); auto val = m.length(2) > 0 ? s.substr(static_cast(m.position(2)), static_cast(m.length(2))) : s.substr(static_cast(m.position(3)), static_cast(m.length(3))); auth[key] = val; } return true; } } } return false; } // https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 inline std::string random_string(size_t length) { auto randchar = []() -> char { const char charset[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; const size_t max_index = (sizeof(charset) - 1); return charset[static_cast(rand()) % max_index]; }; std::string str(length, 0); std::generate_n(str.begin(), length, randchar); return str; } // Request implementation inline bool Request::has_header(const char *key) const { return detail::has_header(headers, key); } inline std::string Request::get_header_value(const char *key, size_t id) const { return detail::get_header_value(headers, key, id, ""); } inline size_t Request::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } inline void Request::set_header(const char *key, const char *val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } inline void Request::set_header(const char *key, const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { headers.emplace(key, val); } } inline bool Request::has_param(const char *key) const { return params.find(key) != params.end(); } inline std::string Request::get_param_value(const char *key, size_t id) const { auto rng = params.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); if (it != rng.second) { return it->second; } return std::string(); } inline size_t Request::get_param_value_count(const char *key) const { auto r = params.equal_range(key); return static_cast(std::distance(r.first, r.second)); } inline bool Request::is_multipart_form_data() const { const auto &content_type = get_header_value("Content-Type"); return !content_type.find("multipart/form-data"); } inline bool Request::has_file(const char *key) const { return files.find(key) != files.end(); } inline MultipartFormData Request::get_file_value(const char *key) const { auto it = files.find(key); if (it != files.end()) { return it->second; } return MultipartFormData(); } // Response implementation inline bool Response::has_header(const char *key) const { return headers.find(key) != headers.end(); } inline std::string Response::get_header_value(const char *key, size_t id) const { return detail::get_header_value(headers, key, id, ""); } inline size_t Response::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); return static_cast(std::distance(r.first, r.second)); } inline void Response::set_header(const char *key, const char *val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } inline void Response::set_header(const char *key, const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { headers.emplace(key, val); } } inline void Response::set_redirect(const char *url, int stat) { if (!detail::has_crlf(url)) { set_header("Location", url); if (300 <= stat && stat < 400) { this->status = stat; } else { this->status = 302; } } } inline void Response::set_content(const char *s, size_t n, const char *content_type) { body.assign(s, n); set_header("Content-Type", content_type); } inline void Response::set_content(std::string s, const char *content_type) { body = std::move(s); set_header("Content-Type", content_type); } inline void Response::set_content_provider(size_t in_length, ContentProvider provider, std::function resource_releaser) { assert(in_length > 0); content_length_ = in_length; content_provider_ = [provider](size_t offset, size_t length, DataSink &sink) { return provider(offset, length, sink); }; content_provider_resource_releaser_ = resource_releaser; } inline void Response::set_chunked_content_provider(ChunkedContentProvider provider, std::function resource_releaser) { content_length_ = 0; content_provider_ = [provider](size_t offset, size_t, DataSink &sink) { return provider(offset, sink); }; content_provider_resource_releaser_ = resource_releaser; } // Rstream implementation inline ssize_t Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } inline ssize_t Stream::write(const std::string &s) { return write(s.data(), s.size()); } template inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { std::array buf{}; #if defined(_MSC_VER) && _MSC_VER < 1900 auto sn = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...); #else auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); #endif if (sn <= 0) { return sn; } auto n = static_cast(sn); if (n >= buf.size() - 1) { std::vector glowable_buf(buf.size()); while (n >= glowable_buf.size() - 1) { glowable_buf.resize(glowable_buf.size() * 2); #if defined(_MSC_VER) && _MSC_VER < 1900 n = static_cast( _snprintf_s(&glowable_buf[0], glowable_buf.size(), glowable_buf.size() - 1, fmt, args...)); #else n = static_cast(snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); #endif } return write(&glowable_buf[0], n); } else { return write(buf.data(), n); } } namespace detail { // Socket stream implementation inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec) : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec) {} inline SocketStream::~SocketStream() {} inline bool SocketStream::is_readable() const { return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; } inline bool SocketStream::is_writable() const { return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; } inline ssize_t SocketStream::read(char *ptr, size_t size) { if (!is_readable()) { return -1; } #ifdef _WIN32 if (size > static_cast((std::numeric_limits::max)())) { return -1; } return recv(sock_, ptr, static_cast(size), 0); #else return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); }); #endif } inline ssize_t SocketStream::write(const char *ptr, size_t size) { if (!is_writable()) { return -1; } #ifdef _WIN32 if (size > static_cast((std::numeric_limits::max)())) { return -1; } return send(sock_, ptr, static_cast(size), 0); #else return handle_EINTR([&]() { return send(sock_, ptr, size, 0); }); #endif } inline void SocketStream::get_remote_ip_and_port(std::string &ip, int &port) const { return detail::get_remote_ip_and_port(sock_, ip, port); } // Buffer stream implementation inline bool BufferStream::is_readable() const { return true; } inline bool BufferStream::is_writable() const { return true; } inline ssize_t BufferStream::read(char *ptr, size_t size) { #if defined(_MSC_VER) && _MSC_VER < 1900 auto len_read = buffer._Copy_s(ptr, size, size, position); #else auto len_read = buffer.copy(ptr, size, position); #endif position += static_cast(len_read); return static_cast(len_read); } inline ssize_t BufferStream::write(const char *ptr, size_t size) { buffer.append(ptr, size); return static_cast(size); } inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} inline const std::string &BufferStream::get_buffer() const { return buffer; } } // namespace detail /* * SSL Implementation */ #ifdef CPPHTTPLIB_OPENSSL_SUPPORT namespace detail { template inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup) { SSL *ssl = nullptr; { std::lock_guard guard(ctx_mutex); ssl = SSL_new(ctx); } if (ssl) { auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); SSL_set_bio(ssl, bio, bio); if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { SSL_shutdown(ssl); { std::lock_guard guard(ctx_mutex); SSL_free(ssl); } return nullptr; } } return ssl; } inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, bool process_socket_ret) { if (process_socket_ret) { SSL_shutdown(ssl); // shutdown only if not already closed by remote } std::lock_guard guard(ctx_mutex); SSL_free(ssl); } template inline bool process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback) { SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); return callback(strm); } #if OPENSSL_VERSION_NUMBER < 0x10100000L static std::shared_ptr> openSSL_locks_; class SSLThreadLocks { public: SSLThreadLocks() { openSSL_locks_ = std::make_shared>(CRYPTO_num_locks()); CRYPTO_set_locking_callback(locking_callback); } ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); } private: static void locking_callback(int mode, int type, const char * /*file*/, int /*line*/) { auto &lk = (*openSSL_locks_)[static_cast(type)]; if (mode & CRYPTO_LOCK) { lk.lock(); } else { lk.unlock(); } } }; #endif class SSLInit { public: SSLInit() { #if OPENSSL_VERSION_NUMBER < 0x1010001fL SSL_load_error_strings(); SSL_library_init(); #else OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); #endif } ~SSLInit() { #if OPENSSL_VERSION_NUMBER < 0x1010001fL ERR_free_strings(); #endif } private: #if OPENSSL_VERSION_NUMBER < 0x10100000L SSLThreadLocks thread_init_; #endif }; // SSL socket stream implementation inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec) : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec) {} inline SSLSocketStream::~SSLSocketStream() {} inline bool SSLSocketStream::is_readable() const { return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; } inline bool SSLSocketStream::is_writable() const { return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; } inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { if (SSL_pending(ssl_) > 0 || is_readable()) { return SSL_read(ssl_, ptr, static_cast(size)); } return -1; } inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } return -1; } inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, int &port) const { detail::get_remote_ip_and_port(sock_, ip, port); } static SSLInit sslinit_; } // namespace detail class SSLClient : public Client { public: explicit SSLClient(const std::string &host); explicit SSLClient(const std::string &host, int port); explicit SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); explicit SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key); ~SSLClient() override; bool is_valid() const override; void set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr); void set_ca_cert_store(X509_STORE *ca_cert_store); void enable_server_certificate_verification(bool enabled); long get_openssl_verify_result() const; SSL_CTX *ssl_context() const; private: bool create_and_connect_socket(Socket &socket) override; void close_socket(Socket &socket, bool process_socket_ret) override; bool process_socket(Socket &socket, std::function callback) override; bool is_ssl() const override; bool connect_with_proxy(Socket &sock, Response &res, bool &success); bool initialize_ssl(Socket &socket); bool verify_host(X509 *server_cert) const; bool verify_host_with_subject_alt_name(X509 *server_cert) const; bool verify_host_with_common_name(X509 *server_cert) const; bool check_host_name(const char *pattern, size_t pattern_len) const; SSL_CTX *ctx_; std::mutex ctx_mutex_; std::vector host_components_; std::string ca_cert_file_path_; std::string ca_cert_dir_path_; X509_STORE *ca_cert_store_ = nullptr; bool server_certificate_verification_ = false; long verify_result_ = 0; friend class Client; }; // SSL HTTP client implementation inline SSLClient::SSLClient(const std::string &host) : SSLClient(host, 443, std::string(), std::string()) {} inline SSLClient::SSLClient(const std::string &host, int port) : SSLClient(host, port, std::string(), std::string()) {} inline SSLClient::SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : Client(host, port, client_cert_path, client_key_path) { ctx_ = SSL_CTX_new(SSLv23_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); if (!client_cert_path.empty() && !client_key_path.empty()) { if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } } } inline SSLClient::SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key) : Client(host, port) { ctx_ = SSL_CTX_new(SSLv23_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); if (client_cert != nullptr && client_key != nullptr) { if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } } } inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } } inline bool SSLClient::is_valid() const { return ctx_; } inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path) { if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; } if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } } inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { if (ca_cert_store) { ca_cert_store_ = ca_cert_store; } } inline void SSLClient::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; } inline long SSLClient::get_openssl_verify_result() const { return verify_result_; } inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } inline bool SSLClient::create_and_connect_socket(Socket &socket) { return is_valid() && Client::create_and_connect_socket(socket); } inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &success) { success = true; Response res2; if (!detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { Request req2; req2.method = "CONNECT"; req2.path = host_and_port_; return process_request(strm, req2, res2, false); })) { close_socket(socket, true); success = false; return false; } if (res2.status == 407) { if (!proxy_digest_auth_username_.empty() && !proxy_digest_auth_password_.empty()) { std::map auth; if (parse_www_authenticate(res2, auth, true)) { Response res3; if (!detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { Request req3; req3.method = "CONNECT"; req3.path = host_and_port_; req3.headers.insert(make_digest_authentication_header( req3, auth, 1, random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); return process_request(strm, req3, res3, false); })) { close_socket(socket, true); success = false; return false; } } } else { res = res2; return false; } } return true; } inline bool SSLClient::initialize_ssl(Socket &socket) { auto ssl = detail::ssl_new( socket.sock, ctx_, ctx_mutex_, [&](SSL *ssl) { if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) { SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); } else if (!ca_cert_file_path_.empty()) { if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { return false; } SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); } else if (ca_cert_store_ != nullptr) { if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { SSL_CTX_set_cert_store(ctx_, ca_cert_store_); } SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); } if (SSL_connect(ssl) != 1) { return false; } if (server_certificate_verification_) { verify_result_ = SSL_get_verify_result(ssl); if (verify_result_ != X509_V_OK) { return false; } auto server_cert = SSL_get_peer_certificate(ssl); if (server_cert == nullptr) { return false; } if (!verify_host(server_cert)) { X509_free(server_cert); return false; } X509_free(server_cert); } return true; }, [&](SSL *ssl) { SSL_set_tlsext_host_name(ssl, host_.c_str()); return true; }); if (ssl) { socket.ssl = ssl; return true; } close_socket(socket, false); return false; } inline void SSLClient::close_socket(Socket &socket, bool process_socket_ret) { detail::close_socket(socket.sock); socket_.sock = INVALID_SOCKET; if (socket.ssl) { detail::ssl_delete(ctx_mutex_, socket.ssl, process_socket_ret); socket_.ssl = nullptr; } } inline bool SSLClient::process_socket(Socket &socket, std::function callback) { assert(socket.ssl); return detail::process_client_socket_ssl(socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, callback); } inline bool SSLClient::is_ssl() const { return true; } inline bool SSLClient::verify_host(X509 *server_cert) const { /* Quote from RFC2818 section 3.1 "Server Identity" If a subjectAltName extension of type dNSName is present, that MUST be used as the identity. Otherwise, the (most specific) Common Name field in the Subject field of the certificate MUST be used. Although the use of the Common Name is existing practice, it is deprecated and Certification Authorities are encouraged to use the dNSName instead. Matching is performed using the matching rules specified by [RFC2459]. If more than one identity of a given type is present in the certificate (e.g., more than one dNSName name, a match in any one of the set is considered acceptable.) Names may contain the wildcard character * which is considered to match any single domain name component or component fragment. E.g., *.a.com matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but not bar.com. In some cases, the URI is specified as an IP address rather than a hostname. In this case, the iPAddress subjectAltName must be present in the certificate and must exactly match the IP in the URI. */ return verify_host_with_subject_alt_name(server_cert) || verify_host_with_common_name(server_cert); } inline bool SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto ret = false; auto type = GEN_DNS; struct in6_addr addr6; struct in_addr addr; size_t addr_len = 0; #ifndef __MINGW32__ if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { type = GEN_IPADD; addr_len = sizeof(struct in6_addr); } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { type = GEN_IPADD; addr_len = sizeof(struct in_addr); } #endif auto alt_names = static_cast( X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); if (alt_names) { auto dsn_matched = false; auto ip_mached = false; auto count = sk_GENERAL_NAME_num(alt_names); for (decltype(count) i = 0; i < count && !dsn_matched; i++) { auto val = sk_GENERAL_NAME_value(alt_names, i); if (val->type == type) { auto name = (const char *) ASN1_STRING_get0_data(val->d.ia5); auto name_len = (size_t) ASN1_STRING_length(val->d.ia5); if (strlen(name) == name_len) { switch (type) { case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; case GEN_IPADD: if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) { ip_mached = true; } break; } } } } if (dsn_matched || ip_mached) { ret = true; } } GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *) alt_names); return ret; } inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { const auto subject_name = X509_get_subject_name(server_cert); if (subject_name != nullptr) { char name[BUFSIZ]; auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, name, sizeof(name)); if (name_len != -1) { return check_host_name(name, static_cast(name_len)); } } return false; } inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) const { if (host_.size() == pattern_len && host_ == pattern) { return true; } // Wildcard match // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 std::vector pattern_components; detail::split(&pattern[0], &pattern[pattern_len], '.', [&](const char *b, const char *e) { pattern_components.emplace_back(std::string(b, e)); }); if (host_components_.size() != pattern_components.size()) { return false; } auto itr = pattern_components.begin(); for (const auto &h : host_components_) { auto &p = *itr; if (p != h && p != "*") { auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && !p.compare(0, p.size() - 1, h)); if (!partial_match) { return false; } } ++itr; } return true; } #endif // ---------------------------------------------------------------------------- } // namespace httplib ================================================ FILE: core-tests/include/httplib_server.h ================================================ /** * httplib.h * * Copyright (c) 2020 Yuji Hirose. All rights reserved. * MIT License * GitHub: https://github.com/yhirose/cpp-httplib */ #pragma once #include "swoole_coroutine_system.h" #include "swoole_socket_impl.h" #include "httplib_client.h" using swoole::Coroutine; using swoole::coroutine::System; using swoole::network::Address; namespace httplib { class ContentReader { public: using Reader = std::function; using MultipartReader = std::function; ContentReader(Reader reader, MultipartReader multipart_reader) : reader_(reader), multipart_reader_(multipart_reader) {} bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { return multipart_reader_(header, receiver); } bool operator()(ContentReceiver receiver) const { return reader_(receiver); } Reader reader_; MultipartReader multipart_reader_; }; class Server { public: using Handler = std::function; using HandlerWithContentReader = std::function; using Expect100ContinueHandler = std::function; Server(); virtual ~Server(); virtual bool is_valid() const; Server &Get(const char *pattern, Handler handler); Server &Post(const char *pattern, Handler handler); Server &Post(const char *pattern, HandlerWithContentReader handler); Server &Put(const char *pattern, Handler handler); Server &Put(const char *pattern, HandlerWithContentReader handler); Server &Patch(const char *pattern, Handler handler); Server &Patch(const char *pattern, HandlerWithContentReader handler); Server &Delete(const char *pattern, Handler handler); Server &Delete(const char *pattern, HandlerWithContentReader handler); Server &Options(const char *pattern, Handler handler); CPPHTTPLIB_DEPRECATED bool set_base_dir(const char *dir, const char *mount_point = nullptr); bool set_mount_point(const char *mount_point, const char *dir); bool remove_mount_point(const char *mount_point); void set_file_extension_and_mimetype_mapping(const char *ext, const char *mime); void set_file_request_handler(Handler handler); void set_error_handler(Handler handler); void set_expect_100_continue_handler(Expect100ContinueHandler handler); void set_logger(Logger logger); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_keep_alive_max_count(size_t count); void set_read_timeout(time_t sec, time_t usec = 0); void set_write_timeout(time_t sec, time_t usec = 0); void set_idle_interval(time_t sec, time_t usec = 0); void set_payload_max_length(size_t length); bool bind_to_port(const char *host, int port, int socket_flags = 0); int bind_to_any_port(const char *host, int socket_flags = 0); bool listen_after_bind(); bool listen(const char *host, int port, int socket_flags = 0); inline void BeforeListen(std::function fn) { before_listen_callback_ = fn; } bool is_running() const; void stop(); protected: bool process_request(Stream &strm, bool close_connection, bool &connection_closed, const std::function &setup_request); SocketImpl *svr_sock_; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; std::function before_listen_callback_ = nullptr; private: using Handlers = std::vector>; using HandlersForContentReader = std::vector>; SocketImpl *create_server_socket(const char *host, int port, int socket_flags, SocketOptions socket_options) const; int bind_internal(const char *host, int port, int socket_flags); bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); bool handle_file_request(Request &req, Response &res, bool head = false); bool dispatch_request(Request &req, Response &res, Handlers &handlers); bool dispatch_request_for_content_reader(Request &req, Response &res, ContentReader content_reader, HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); bool write_content_with_provider( Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); bool read_content(Stream &strm, Request &req, Response &res); bool read_content_with_content_receiver(Stream &strm, Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader mulitpart_header, ContentReceiver multipart_receiver); virtual bool process_and_close_socket(SocketImpl *sock); std::atomic is_running_; std::vector> base_dirs_; std::map file_extension_and_mimetype_map_; Handler file_request_handler_; Handlers get_handlers_; Handlers post_handlers_; HandlersForContentReader post_handlers_for_content_reader_; Handlers put_handlers_; HandlersForContentReader put_handlers_for_content_reader_; Handlers patch_handlers_; HandlersForContentReader patch_handlers_for_content_reader_; Handlers delete_handlers_; HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; Handler error_handler_; Logger logger_; Expect100ContinueHandler expect_100_continue_handler_; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = default_socket_options; }; // HTTP server implementation inline Server::Server() : svr_sock_(nullptr), is_running_(false) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif } inline Server::~Server() { if (svr_sock_) { delete svr_sock_; } } inline Server &Server::Get(const char *pattern, Handler handler) { get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Post(const char *pattern, Handler handler) { post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Post(const char *pattern, HandlerWithContentReader handler) { post_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Put(const char *pattern, Handler handler) { put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Put(const char *pattern, HandlerWithContentReader handler) { put_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Patch(const char *pattern, Handler handler) { patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Patch(const char *pattern, HandlerWithContentReader handler) { patch_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Delete(const char *pattern, Handler handler) { delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Delete(const char *pattern, HandlerWithContentReader handler) { delete_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline Server &Server::Options(const char *pattern, Handler handler) { options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); return *this; } inline bool Server::set_base_dir(const char *dir, const char *mount_point) { return set_mount_point(mount_point, dir); } inline bool Server::set_mount_point(const char *mount_point, const char *dir) { if (detail::is_dir(dir)) { std::string mnt = mount_point ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { base_dirs_.emplace_back(mnt, dir); return true; } } return false; } inline bool Server::remove_mount_point(const char *mount_point) { for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { if (it->first == mount_point) { base_dirs_.erase(it); return true; } } return false; } inline void Server::set_file_extension_and_mimetype_mapping(const char *ext, const char *mime) { file_extension_and_mimetype_map_[ext] = mime; } inline void Server::set_file_request_handler(Handler handler) { file_request_handler_ = std::move(handler); } inline void Server::set_error_handler(Handler handler) { error_handler_ = std::move(handler); } inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } inline void Server::set_socket_options(SocketOptions socket_options) { socket_options_ = socket_options; } inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); } inline void Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); } inline void Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; } inline void Server::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; } inline void Server::set_write_timeout(time_t sec, time_t usec) { write_timeout_sec_ = sec; write_timeout_usec_ = usec; } inline void Server::set_idle_interval(time_t sec, time_t usec) { idle_interval_sec_ = sec; idle_interval_usec_ = usec; } inline void Server::set_payload_max_length(size_t length) { payload_max_length_ = length; } inline bool Server::bind_to_port(const char *host, int port, int socket_flags) { if (bind_internal(host, port, socket_flags) < 0) return false; return true; } inline int Server::bind_to_any_port(const char *host, int socket_flags) { return bind_internal(host, 0, socket_flags); } inline bool Server::listen_after_bind() { return listen_internal(); } inline bool Server::listen(const char *host, int port, int socket_flags) { return bind_to_port(host, port, socket_flags) && listen_internal(); } inline bool Server::is_running() const { return is_running_; } inline void Server::stop() { if (is_running_) { is_running_ = false; svr_sock_->cancel(SW_EVENT_READ); } } inline bool Server::parse_request_line(const char *s, Request &req) { const static std::regex re("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n"); std::cmatch m; if (std::regex_match(s, m, re)) { req.version = std::string(m[5]); req.method = std::string(m[1]); req.target = std::string(m[2]); req.path = detail::decode_url(m[3], false); // Parse query text auto len = std::distance(m[4].first, m[4].second); if (len > 0) { detail::parse_query_text(m[4], req.params); } return true; } return false; } inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { assert(res.status != -1); if (400 <= res.status && error_handler_) { error_handler_(req, res); } detail::BufferStream bstrm; // Response line if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, detail::status_message(res.status))) { return false; } // Headers if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); } if (!close_connection && req.get_header_value("Connection") == "Keep-Alive") { res.set_header("Connection", "Keep-Alive"); } if (!res.has_header("Content-Type") && (!res.body.empty() || res.content_length_ > 0)) { res.set_header("Content-Type", "text/plain"); } if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { res.set_header("Accept-Ranges", "bytes"); } std::string content_type; std::string boundary; if (req.ranges.size() > 1) { boundary = detail::make_multipart_data_boundary(); auto it = res.headers.find("Content-Type"); if (it != res.headers.end()) { content_type = it->second; res.headers.erase(it); } res.headers.emplace("Content-Type", "multipart/byteranges; boundary=" + boundary); } if (res.body.empty()) { if (res.content_length_ > 0) { size_t length = 0; if (req.ranges.empty()) { length = res.content_length_; } else if (req.ranges.size() == 1) { auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; length = offsets.second; auto content_range = detail::make_content_range_header_field(offset, length, res.content_length_); res.set_header("Content-Range", content_range); } else { length = detail::get_multipart_ranges_data_length(req, res, boundary, content_type); } res.set_header("Content-Length", std::to_string(length)); } else { if (res.content_provider_) { res.set_header("Transfer-Encoding", "chunked"); } else { res.set_header("Content-Length", "0"); } } } else { if (req.ranges.empty()) { ; } else if (req.ranges.size() == 1) { auto offsets = detail::get_range_offset_and_length(req, res.body.size(), 0); auto offset = offsets.first; auto length = offsets.second; auto content_range = detail::make_content_range_header_field(offset, length, res.body.size()); res.set_header("Content-Range", content_range); res.body = res.body.substr(offset, length); } else { res.body = detail::make_multipart_ranges_data(req, res, boundary, content_type); } #ifdef CPPHTTPLIB_ZLIB_SUPPORT // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 const auto &encodings = req.get_header_value("Accept-Encoding"); if (encodings.find("gzip") != std::string::npos && detail::can_compress(res.get_header_value("Content-Type"))) { if (detail::compress(res.body)) { res.set_header("Content-Encoding", "gzip"); } } #endif auto length = std::to_string(res.body.size()); res.set_header("Content-Length", length); } if (!detail::write_headers(bstrm, res, Headers())) { return false; } // Flush buffer auto &data = bstrm.get_buffer(); strm.write(data.data(), data.size()); // Body if (req.method != "HEAD") { if (!res.body.empty()) { if (!strm.write(res.body)) { return false; } } else if (res.content_provider_) { if (!write_content_with_provider(strm, req, res, boundary, content_type)) { return false; } } } // Log if (logger_) { logger_(req, res); } return true; } inline bool Server::write_content_with_provider( Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type) { auto is_shutting_down = [this]() { return this->svr_sock_ == nullptr; }; if (res.content_length_) { if (req.ranges.empty()) { if (detail::write_content(strm, res.content_provider_, 0, res.content_length_, is_shutting_down) < 0) { return false; } } else if (req.ranges.size() == 1) { auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; auto length = offsets.second; if (detail::write_content(strm, res.content_provider_, offset, length, is_shutting_down) < 0) { return false; } } else { if (!detail::write_multipart_ranges_data(strm, req, res, boundary, content_type, is_shutting_down)) { return false; } } } else { if (detail::write_content_chunked(strm, res.content_provider_, is_shutting_down) < 0) { return false; } } return true; } inline bool Server::read_content(Stream &strm, Request &req, Response &res) { MultipartFormDataMap::iterator cur; if (read_content_core( strm, req, res, // Regular [&](const char *buf, size_t n) { if (req.body.size() + n > req.body.max_size()) { return false; } req.body.append(buf, n); return true; }, // Multipart [&](const MultipartFormData &file) { cur = req.files.emplace(file.name, file); return true; }, [&](const char *buf, size_t n) { auto &content = cur->second.content; if (content.size() + n > content.max_size()) { return false; } content.append(buf, n); return true; })) { const auto &content_type = req.get_header_value("Content-Type"); if (!content_type.find("application/x-www-form-urlencoded")) { detail::parse_query_text(req.body, req.params); } return true; } return false; } inline bool Server::read_content_with_content_receiver(Stream &strm, Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader multipart_header, ContentReceiver multipart_receiver) { return read_content_core(strm, req, res, receiver, multipart_header, multipart_receiver); } inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader mulitpart_header, ContentReceiver multipart_receiver) { detail::MultipartFormDataParser multipart_form_data_parser; ContentReceiver out; if (req.is_multipart_form_data()) { const auto &content_type = req.get_header_value("Content-Type"); std::string boundary; if (!detail::parse_multipart_boundary(content_type, boundary)) { res.status = 400; return false; } multipart_form_data_parser.set_boundary(std::move(boundary)); out = [&](const char *buf, size_t n) { /* For debug size_t pos = 0; while (pos < n) { auto read_size = std::min(1, n - pos); auto ret = multipart_form_data_parser.parse( buf + pos, read_size, multipart_receiver, mulitpart_header); if (!ret) { return false; } pos += read_size; } return true; */ return multipart_form_data_parser.parse(buf, n, multipart_receiver, mulitpart_header); }; } else { out = receiver; } if (!detail::read_content(strm, req, payload_max_length_, res.status, Progress(), out, true)) { return false; } if (req.is_multipart_form_data()) { if (!multipart_form_data_parser.is_valid()) { res.status = 400; return false; } } return true; } inline bool Server::handle_file_request(Request &req, Response &res, bool head) { for (const auto &kv : base_dirs_) { const auto &mount_point = kv.first; const auto &base_dir = kv.second; // Prefix match if (!req.path.find(mount_point)) { std::string sub_path = "/" + req.path.substr(mount_point.size()); if (detail::is_valid_path(sub_path)) { auto path = base_dir + sub_path; if (path.back() == '/') { path += "index.html"; } if (detail::is_file(path)) { detail::read_file(path, res.body); auto type = detail::find_content_type(path, file_extension_and_mimetype_map_); if (type) { res.set_header("Content-Type", type); } res.status = 200; if (!head && file_request_handler_) { file_request_handler_(req, res); } return true; } } } } return false; } inline SocketImpl *Server::create_server_socket(const char *host, int port, int socket_flags, SocketOptions socket_options) const { struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = socket_flags; hints.ai_protocol = 0; auto service = std::to_string(port); if (swoole_coroutine_getaddrinfo(host, service.c_str(), &hints, &result)) { return nullptr; } SocketImpl *sock = nullptr; for (auto rp = result; rp; rp = rp->ai_next) { sock = new SocketImpl(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock->get_fd() == INVALID_SOCKET) { delete sock; continue; } if (tcp_nodelay_) { sock->set_option(IPPROTO_TCP, TCP_NODELAY, 1); } if (socket_options) { socket_options(sock->get_fd()); } if (rp->ai_family == AF_INET6) { sock->set_option(IPPROTO_IPV6, IPV6_V6ONLY, 0); } if (!sock->bind(rp->ai_addr, static_cast(rp->ai_addrlen))) { delete sock; return nullptr; } if (!sock->listen(512)) { delete sock; return nullptr; } break; } freeaddrinfo(result); return sock; } inline int Server::bind_internal(const char *host, int port, int socket_flags) { if (!is_valid()) { return -1; } svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); if (svr_sock_ == nullptr) { return -1; } if (port == 0) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); if (getsockname(svr_sock_->get_fd(), reinterpret_cast(&addr), &addr_len) == -1) { return -1; } if (addr.ss_family == AF_INET) { return ntohs(reinterpret_cast(&addr)->sin_port); } else if (addr.ss_family == AF_INET6) { return ntohs(reinterpret_cast(&addr)->sin6_port); } else { return -1; } } else { return port; } } struct CoroutineArg { SocketImpl *client_socket; Server *this_; }; inline bool Server::listen_internal() { if (!svr_sock_) { return false; } is_running_ = true; if (before_listen_callback_) { before_listen_callback_(); } while (is_running_) { auto client_sock = svr_sock_->accept(); if (client_sock) { auto arg = new CoroutineArg; arg->client_socket = client_sock; arg->this_ = this; Coroutine::create( [](void *arg) { CoroutineArg *_arg = (CoroutineArg *) arg; _arg->this_->process_and_close_socket(_arg->client_socket); delete _arg; }, arg); continue; } if (svr_sock_->errCode == EMFILE || svr_sock_->errCode == ENFILE) { System::sleep(SW_ACCEPT_RETRY_TIME); continue; } else if (svr_sock_->errCode == ETIMEDOUT || svr_sock_->errCode == SW_ERROR_SSL_BAD_CLIENT) { continue; } else if (svr_sock_->errCode == ECANCELED) { break; } else { swoole_warning("accept failed, Error: %s[%d]", svr_sock_->errMsg, svr_sock_->errCode); break; } } is_running_ = false; return true; } inline bool Server::routing(Request &req, Response &res, Stream &strm) { // File handler bool is_head_request = req.method == "HEAD"; if ((req.method == "GET" || is_head_request) && handle_file_request(req, res, is_head_request)) { return true; } if (detail::expect_content(req)) { // Content reader handler { ContentReader reader( [&](ContentReceiver receiver) { return read_content_with_content_receiver(strm, req, res, receiver, nullptr, nullptr); }, [&](MultipartContentHeader header, ContentReceiver receiver) { return read_content_with_content_receiver(strm, req, res, nullptr, header, receiver); }); if (req.method == "POST") { if (dispatch_request_for_content_reader(req, res, reader, post_handlers_for_content_reader_)) { return true; } } else if (req.method == "PUT") { if (dispatch_request_for_content_reader(req, res, reader, put_handlers_for_content_reader_)) { return true; } } else if (req.method == "PATCH") { if (dispatch_request_for_content_reader(req, res, reader, patch_handlers_for_content_reader_)) { return true; } } else if (req.method == "DELETE") { if (dispatch_request_for_content_reader(req, res, reader, delete_handlers_for_content_reader_)) { return true; } } } // Read content into `req.body` if (!read_content(strm, req, res)) { return false; } } // Regular handler if (req.method == "GET" || req.method == "HEAD") { return dispatch_request(req, res, get_handlers_); } else if (req.method == "POST") { return dispatch_request(req, res, post_handlers_); } else if (req.method == "PUT") { return dispatch_request(req, res, put_handlers_); } else if (req.method == "DELETE") { return dispatch_request(req, res, delete_handlers_); } else if (req.method == "OPTIONS") { return dispatch_request(req, res, options_handlers_); } else if (req.method == "PATCH") { return dispatch_request(req, res, patch_handlers_); } res.status = 400; return false; } inline bool Server::dispatch_request(Request &req, Response &res, Handlers &handlers) { try { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; if (std::regex_match(req.path, req.matches, pattern)) { handler(req, res); return true; } } } catch (const std::exception &ex) { res.status = 500; res.set_header("EXCEPTION_WHAT", ex.what()); } catch (...) { res.status = 500; res.set_header("EXCEPTION_WHAT", "UNKNOWN"); } return false; } inline bool Server::dispatch_request_for_content_reader(Request &req, Response &res, ContentReader content_reader, HandlersForContentReader &handlers) { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; if (std::regex_match(req.path, req.matches, pattern)) { handler(req, res, content_reader); return true; } } return false; } inline bool Server::process_request(Stream &strm, bool close_connection, bool &connection_closed, const std::function &setup_request) { std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); // Connection has been closed on client if (!line_reader.getline()) { return false; } Request req; Response res; res.version = "HTTP/1.1"; // Check if the request URI doesn't exceed the limit if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { Headers dummy; detail::read_headers(strm, dummy); res.status = 414; return write_response(strm, close_connection, req, res); } // Request line and headers if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { res.status = 400; return write_response(strm, close_connection, req, res); } if (req.get_header_value("Connection") == "close") { connection_closed = true; } if (req.version == "HTTP/1.0" && req.get_header_value("Connection") != "Keep-Alive") { connection_closed = true; } strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); req.set_header("REMOTE_ADDR", req.remote_addr); req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { // TODO: error } } if (setup_request) { setup_request(req); } if (req.get_header_value("Expect") == "100-continue") { auto status = 100; if (expect_100_continue_handler_) { status = expect_100_continue_handler_(req, res); } switch (status) { case 100: case 417: strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, detail::status_message(status)); break; default: return write_response(strm, close_connection, req, res); } } // Rounting if (routing(req, res, strm)) { if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } } else { if (res.status == -1) { res.status = 404; } } return write_response(strm, close_connection, req, res); } inline bool Server::is_valid() const { return true; } namespace detail { template inline bool process_server_socket_core(socket_t sock, size_t keep_alive_max_count, T callback) { assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; while (count > 0 && keep_alive(sock)) { auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); if (!ret || connection_closed) { break; } count--; } return ret; }; template inline bool process_server_socket(socket_t sock, size_t keep_alive_max_count, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback) { return process_server_socket_core(sock, keep_alive_max_count, [&](bool close_connection, bool connection_closed) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); return callback(strm, close_connection, connection_closed); }); } } // namespace detail class CoSocketStream : public detail::SocketStream { public: CoSocketStream(SocketImpl *sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec) : detail::SocketStream( sock->get_fd(), read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec) { sock_ = sock; sock->set_timeout((double) read_timeout_sec + ((double) read_timeout_usec / 1000000), SW_TIMEOUT_READ); sock->set_timeout((double) write_timeout_sec + ((double) write_timeout_usec / 1000000), SW_TIMEOUT_WRITE); } ~CoSocketStream() {} bool is_readable() const { return true; } bool is_writable() const { return true; } ssize_t read(char *ptr, size_t size) { return sock_->recv_with_buffer(ptr, size); } ssize_t write(const char *ptr, size_t size) { return sock_->write(ptr, size); } void get_remote_ip_and_port(std::string &ip, int &port) const { Address sa; sock_->getpeername(&sa); ip = std::string(sa.get_addr()); port = sa.get_port(); } private: SocketImpl *sock_; }; inline bool Server::process_and_close_socket(SocketImpl *sock) { size_t keep_alive_max_count = keep_alive_max_count_; time_t read_timeout_sec = read_timeout_sec_; time_t read_timeout_usec = read_timeout_usec_; time_t write_timeout_sec = write_timeout_sec_; time_t write_timeout_usec = write_timeout_usec_; CoSocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; do { auto close_connection = count == 1; auto connection_closed = false; ret = process_request(strm, close_connection, connection_closed, nullptr); if (!ret || connection_closed) { break; } count--; } while (count > 0 && sock->check_liveness()); sock->shutdown(); sock->close(); delete sock; return ret; } } // namespace httplib ================================================ FILE: core-tests/include/redis_client.h ================================================ #pragma once #include "swoole.h" #include #include #include #include "hiredis.h" namespace swoole { class RedisReply { private: redisReply *ptr_; public: RedisReply(void *ptr) : ptr_(reinterpret_cast(ptr)) {} RedisReply(RedisReply &&_o) { ptr_ = _o.ptr_; _o.ptr_ = nullptr; } ~RedisReply() { if (ptr_) { freeReplyObject(ptr_); } } redisReply *operator->() { return ptr_; } inline bool empty() { return ptr_ == nullptr; } inline const char *str() { return ptr_->str; } inline size_t len() { return ptr_->len; } inline size_t type() { return ptr_->type; } }; class RedisClient { private: redisContext *ctx = nullptr; public: RedisClient() = default; ~RedisClient() { if (ctx) { redisFree(ctx); } } RedisReply Request(const std::vector &args); RedisReply Request(int argc, const char **argv, const size_t *argvlen); bool Connect(const std::string &host = "127.0.0.1", int port = 6379, struct timeval timeout = {}); std::string Get(const std::string &key); bool Set(const std::string &key, const std::string &value); long Ttl(const std::string &key); bool Select(int db); std::string Role(); }; } // namespace swoole ================================================ FILE: core-tests/include/test_core.h ================================================ #pragma once #include "swoole_client.h" #include "swoole_socket_impl.h" #include #include #include #include #include #include #include #include #include #include #include #define TEST_HOST "127.0.0.1" #define TEST_HOST6 "::1" #define TEST_PORT 9501 #define TEST_TMP_FILE "/tmp/swoole_core_test_file" #define TEST_TMP_DIR "/tmp/swoole_core_test_dir" #define TEST_JPG_FILE "/examples/test.jpg" #define TEST_JPG_MD5SUM "64a42b4c0f3c65a14c23b60d3880a917" #define TEST_HTTP_PROXY_HOST "127.0.0.1" #define TEST_HTTP_PROXY_PORT 8888 #define TEST_HTTP_PROXY_USER "user" #define TEST_HTTP_PROXY_PASSWORD "password" #define TEST_SOCKS5_PROXY_HOST "127.0.0.1" #define TEST_SOCKS5_PROXY_PORT 8080 #define TEST_SOCKS5_PROXY_NO_AUTH_PORT 8081 #define TEST_SOCKS5_PROXY_USER "user" #define TEST_SOCKS5_PROXY_PASSWORD "password" #define TEST_DOMAIN_BAIDU "www.baidu.com" #define TEST_HTTP_DOMAIN "www.gov.cn" #define TEST_HTTP_EXPECT "Location: https://www.gov.cn/" #define TEST_HTTPS_EXPECT "中国政府网" #define TEST_STR "hello world, hello swoole\n" #define TEST_STR2 "I am Rango\n" #define TEST_LOG_FILE "/tmp/swoole.log" #define TEST_SOCK_FILE "/tmp/swoole-core-tests.sock" #define TEST_WRITEV_OFFSET 87 #define TEST_READV_OFFSET 1949 #define TEST_COUNTER_NUM 32 #define TEST_REQUEST_BAIDU \ "GET / HTTP/1.1\r\n" \ "Host: www.baidu.com\r\n" \ "Connection: close\r\n" \ "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " \ "Chrome/51.0.2704.106 Safari/537.36" \ "\r\n\r\n" #define CRLF "\r\n" #define EOF_PACKET "hello world" CRLF #define EOF_PACKET_2 "php&swoole, java&golang" CRLF #define RECV_TIMEOUT 10.0 #define ASSERT_MEMEQ(x, y, n) ASSERT_EQ(memcmp((x), (y), n), 0) #define EXPECT_MEMEQ(x, y, n) EXPECT_EQ(memcmp((x), (y), n), 0) #define ASSERT_ERREQ(x) ASSERT_EQ(swoole_get_last_error(), x) #define EXPECT_ERREQ(x) EXPECT_EQ(swoole_get_last_error(), x) #define TEST_WRITE(fd, s) ASSERT_EQ(write(fd, s, strlen(s)), strlen(s)) #define TIMER_PARAMS swoole::Timer *timer, swoole::TimerNode *tnode #ifdef SW_VERBOSE #define DEBUG() swoole::test::debug_output.get() #define debug_info printf #else #define DEBUG() swoole::test::null_stream #define debug_info(...) #endif #ifdef __ANDROID__ #define sysv_signal signal #endif namespace swoole { struct HttpProxy; struct Socks5Proxy; namespace test { class NullStream : public std::ostream { public: NullStream() : std::ostream(nullptr) {} }; extern NullStream null_stream; extern std::reference_wrapper debug_output; const std::string &get_root_path(); std::string get_ssl_dir(); std::string get_jpg_file(); bool is_github_ci(); int exec_js_script(const std::string &file, const std::string &args); std::string http_get_request(const std::string &domain, const std::string &path); int get_random_port(); int has_threads(); int has_child_processes(); int wait_all_child_processes(bool verbose = false); bool is_valid_fd(int fd); pid_t spawn_exec(const std::function &fn); int spawn_exec_and_wait(const std::function &fn); void counter_init(); int *counter_ptr(); int counter_incr(int index, int add = 1); int counter_get(int index); void counter_set(int index, int value); void counter_incr_and_put_log(int index, const char *msg); int dump_cert_info(const char *data, size_t len); int recursive_rmdir(const char *path); std::pair, std::shared_ptr> create_socket_pair(); static inline int dump_cert_info(const String *str) { return dump_cert_info(str->str, str->length); } } // namespace test }; // namespace swoole ================================================ FILE: core-tests/include/test_coroutine.h ================================================ #pragma once #include "test_core.h" #include "swoole_coroutine.h" #include "swoole_coroutine_channel.h" #include "swoole_coroutine_system.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_api.h" namespace swoole { namespace test { class coroutine { public: coroutine(const CoroutineFunc &_fn, void *_arg, int *_complete_num) : fn(_fn), arg(_arg), complete_num(_complete_num) {} void start() { fn(arg); (*complete_num)++; } inline static void create(const CoroutineFunc &fn, void *arg, int *complete_num) { auto test = new coroutine(fn, arg, complete_num); long cid = swoole::Coroutine::create( [](void *arg) { ((coroutine *) arg)->start(); delete (coroutine *) arg; }, test); ASSERT_GT(cid, 0); } inline static void run(std::initializer_list> args) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Coroutine::activate(); for (const auto &arg : args) { create(arg.first, arg.second, &complete_num); } swoole_event_wait(); Coroutine::deactivate(); } inline static void run(std::initializer_list fns) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Coroutine::activate(); for (const auto &fn : fns) { create(fn, nullptr, &complete_num); } swoole_event_wait(); Coroutine::deactivate(); } inline static void run(const CoroutineFunc &fn, void *arg = nullptr) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Coroutine::activate(); create(fn, arg, &complete_num); swoole_event_wait(); Coroutine::deactivate(); } private: CoroutineFunc fn; void *arg; int *complete_num; }; } // namespace test } // namespace swoole ================================================ FILE: core-tests/include/test_process.h ================================================ #pragma once #include #include "test_core.h" #include "swoole_process_pool.h" using namespace std; namespace swoole { namespace test { //------------------------------------------------------------------------------- class Process { private: std::function handler; public: Worker worker = {}; Process(std::function fn, int pipe_type = SOCK_DGRAM); ~Process(); pid_t start(); ssize_t write(const void *__buf, size_t __n); ssize_t read(void *__buf, size_t __nbytes); }; //------------------------------------------------------------------------------- }} ================================================ FILE: core-tests/include/test_server.h ================================================ #pragma once #include "test_core.h" #include "swoole_server.h" #define SERVER_THIS ((swoole::test::Server *) serv->private_data_2) #define ON_START_PARAMS swoole::Server *serv #define ON_WORKER_START_PARAMS swoole::Server *serv, swoole::Worker *worker #define ON_PACKET_PARAMS swoole::Server *serv, swoole::RecvData *req #define ON_RECEIVE_PARAMS swoole::Server *serv, swoole::RecvData *req namespace swoole { namespace test { //-------------------------------------------------------------------------------------------------------- class Server { private: swoole::Server serv; std::vector ports; std::unordered_map private_data; std::string host; int port; int mode; int type; std::string tolower(const std::string &str); public: DgramPacket *packet = nullptr; Server(std::string _host, int _port, swoole::Server::Mode _mode, int _type); ~Server(); void on(const std::string &event, const std::function &fn); void on(const std::string &event, const std::function &fn); void on(const std::string &event, const std::function &fn); void on(const std::string &event, const std::function &fn); void on(const std::string &event, const std::function &fn); void on(const std::string &event, const std::function &fn); bool start(); bool listen(const std::string &host, int port, enum swSocketType type); int send(int session_id, const void *data, uint32_t length); ssize_t sendto(const swoole::network::Address &address, const char *__buf, size_t __n, int server_socket = -1); int close(int session_id, int reset); inline void *get_private_data(const std::string &key) { auto it = private_data.find(key); if (it == private_data.end()) { return nullptr; } else { return it->second; } } inline void set_private_data(const std::string &key, void *data) { private_data[key] = data; } }; //-------------------------------------------------------------------------------------------------------- } // namespace test } // namespace swoole ================================================ FILE: core-tests/js/.gitignore ================================================ node_modules package-lock.json ================================================ FILE: core-tests/js/mqtt.js ================================================ const mqtt = require('mqtt'); const port = process.argv[2]; const pino = require('pino'); const logger = pino(pino.destination('/tmp/swoole.log')); const client = mqtt.connect(`mqtt://localhost:${port}`); client.on('connect', () => { logger.info('the client is connected'); client.subscribe('test/topic', (err) => { if (err) { console.error('subscribe fail:', err); return; } logger.info('subscribed: test/topic'); client.publish('test/topic', 'Hello MQTT from Node.js!'); }); }); client.on('disconnect', () => { logger.info('the client is disconnected'); client.end() }) client.on('message', (topic, message) => { logger.info(`received message, topic: ${topic}, content: ${message.toString()}`); }); client.on('error', (err) => { console.error('error:', err); }); ================================================ FILE: core-tests/js/package.json ================================================ { "dependencies": { "mqtt": "^4.3.8", "pino": "^6.14.0", "ws": "^8.18.2" } } ================================================ FILE: core-tests/js/ws_1.js ================================================ const WebSocket = require('ws'); const pino = require('pino'); const port = process.argv[2]; const logger = pino(pino.destination('/tmp/swoole.log')); const ws_1 = new WebSocket(`ws://127.0.0.1:${port}/`); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ws_1.on('error', console.error); ws_1.on('close', function () { logger.info('the node websocket client is closed'); }) ws_1.on('open', async () => { ws_1.send('hello', {fin: false}); await delay(50); ws_1.send(' ', {fin: false}); await delay(50); ws_1.send('world', {fin: true}); await delay(200); ws_1.ping("keep alive") await delay(200); ws_1.close() }); ws_1.on('message', function message(data) { logger.info('received: ' + data); }); ================================================ FILE: core-tests/js/ws_2.js ================================================ const WebSocket = require('ws'); const pino = require('pino'); const port = process.argv[2]; const logger = pino(pino.destination('/tmp/swoole.log')); const ws = new WebSocket(`ws://127.0.0.1:${port}/ws/close`); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ws.on('error', console.error); ws.on('close', function (code, reason) { logger.info('the node websocket client is closed, code: ' + code + ', reason: ' + reason.toString()); }) ws.on('open', async () => { }); ws.on('message', function message(data) { }); ================================================ FILE: core-tests/samples/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8) project(samples) #set(CMAKE_BUILD_TYPE Released) set(CMAKE_CXX_STANDARD 11) file(GLOB_RECURSE SOURCE_FILES FOLLOW_SYMLINKS src/*.cc) add_definitions(-DHAVE_CONFIG_H) link_directories($ENV{SWOOLE_DIR}/lib) include_directories(. ./include $ENV{SWOOLE_DIR} $ENV{SWOOLE_DIR}/include BEFORE) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) add_executable(core_samples ${SOURCE_FILES}) target_link_libraries(core_samples swoole) ================================================ FILE: core-tests/samples/src/s1.cc ================================================ #include "swoole.h" #include "swoole_client.h" #include "swoole_server.h" #include "swoole_coroutine.h" #include "swoole_socket_impl.h" #include "swoole_coroutine_system.h" #include using swoole::Coroutine; using swoole::coroutine::System; struct A { int x; int *y; }; static A G_a = {0, 0}; int main(int argc, char **argv) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); // coroutine 1 Coroutine::create([](void *arg) { G_a.x = 1234; int y = 5678; G_a.y = &y; // After the coroutine 1 is suspended here, the coroutine 2 will be executed, and the x, y values is updated in // the coroutine 2. After the coroutine 2 suspends, go back to coroutine 1, where the values of x and y will be // no longer as expected System::sleep(1); // output 100 std::cout << "X=" << G_a.x << std::endl; // read invalid point std::cout << "Y=" << *G_a.y << std::endl; }); // coroutine 2 Coroutine::create([](void *arg) { G_a.x = 100; G_a.y = nullptr; }); swoole_event_wait(); return 0; } ================================================ FILE: core-tests/src/_lib/http.cpp ================================================ #include "test_core.h" #include "httplib_client.h" #include "swoole_http.h" namespace websocket = swoole::websocket; using swoole::Protocol; using swoole::String; namespace httplib { bool Client::Upgrade(const char *_path, Headers &_headers) { set_keep_alive(true); _headers.emplace("Connection", "Upgrade"); _headers.emplace("Upgrade", "websocket"); _headers.emplace("Sec-Websocket-Key", "sN9cRrP/n9NdMgdcy2VJFQ=="); _headers.emplace("Sec-WebSocket-Version", "13"); auto resp = Get(_path, _headers); if (resp == nullptr or resp->status != SW_HTTP_SWITCHING_PROTOCOLS) { return false; } return true; } bool Client::Push(const char *data, size_t length, int opcode) { if (!socket_.is_open()) { return false; } return process_socket(socket_, [&](Stream &strm) { String buffer = {}; char buf[32]; buffer.size = sizeof(buf); buffer.str = buf; auto flags = websocket::FLAG_FIN | websocket::FLAG_ENCODE_HEADER_ONLY; if (websocket_mask_) { flags |= websocket::FLAG_MASK; } websocket::encode(&buffer, data, length, opcode, flags); if (strm.write(buffer.str, buffer.length) != (ssize_t) buffer.length) { return false; } if (websocket_mask_) { std::unique_ptr marked = std::make_unique(length + 1); memcpy(marked.get(), data, length); websocket::mask(marked.get(), length, buffer.str + buffer.length - SW_WEBSOCKET_MASK_LEN); return strm.write(marked.get(), length) == (ssize_t) length; } else { return strm.write(data, length) == (ssize_t) length; } }); } std::shared_ptr Client::Recv() { auto msg = std::make_shared(); auto retval = process_socket(socket_, [&](Stream &strm) { Protocol proto = {}; proto.package_length_size = SW_WEBSOCKET_HEADER_LEN; proto.get_package_length = websocket::get_package_length; proto.package_max_length = SW_INPUT_BUFFER_SIZE; char buf[1024]; ssize_t packet_len; if (strm.read(buf, SW_WEBSOCKET_HEADER_LEN) <= 0) { return false; } swoole::PacketLength pl{ buf, SW_WEBSOCKET_HEADER_LEN, }; packet_len = proto.get_package_length(&proto, nullptr, &pl); if (packet_len < 0) { return false; } if (packet_len == 0) { if (strm.read(buf + SW_WEBSOCKET_HEADER_LEN, pl.header_len - SW_WEBSOCKET_HEADER_LEN) <= 0) { return false; } pl.buf_size = pl.header_len; packet_len = proto.get_package_length(&proto, nullptr, &pl); if (packet_len <= 0) { return false; } } char *data = (char *) malloc(packet_len + 1); if (data == nullptr) { return false; } data[packet_len] = 0; uint32_t header_len = pl.header_len > 0 ? pl.header_len : SW_WEBSOCKET_HEADER_LEN; memcpy(data, buf, header_len); ssize_t read_bytes = header_len; while (read_bytes < packet_len) { auto n_read = strm.read(data + read_bytes, packet_len - read_bytes); if (n_read <= 0) { free(data); return false; } read_bytes += n_read; } return websocket::decode(msg.get(), data, packet_len); }); return retval ? msg : nullptr; } // HTTP client implementation Client::Client(const std::string &host) : Client(host, 80, std::string(), std::string()) {} Client::Client(const std::string &host, int port) : Client(host, port, std::string(), std::string()) {} Client::Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : host_(host), port_(port), host_and_port_(host_ + ":" + std::to_string(port_)), client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} Client::~Client() { stop(); } bool Client::is_valid() const { return true; } socket_t Client::create_client_socket() const { if (!proxy_host_.empty()) { return detail::create_client_socket(proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, connection_timeout_sec_, connection_timeout_usec_, interface_); } return detail::create_client_socket(host_.c_str(), port_, tcp_nodelay_, socket_options_, connection_timeout_sec_, connection_timeout_usec_, interface_); } bool Client::create_and_connect_socket(Socket &socket) { auto sock = create_client_socket(); if (sock == INVALID_SOCKET) { return false; } socket.sock = sock; return true; } void Client::close_socket(Socket &socket, bool /*process_socket_ret*/) { detail::close_socket(socket.sock); socket_.sock = INVALID_SOCKET; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT socket_.ssl = nullptr; #endif } bool Client::read_response_line(Stream &strm, Response &res) { std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); if (!line_reader.getline()) { return false; } const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n"); std::cmatch m; if (std::regex_match(line_reader.ptr(), m, re)) { res.version = std::string(m[1]); res.status = std::stoi(std::string(m[2])); } return true; } bool Client::send(const Request &req, Response &res) { std::lock_guard request_mutex_guard(request_mutex_); { std::lock_guard guard(socket_mutex_); auto is_alive = false; if (socket_.is_open()) { is_alive = detail::select_write(socket_.sock, 0, 0) > 0; if (!is_alive) { close_socket(socket_, false); } } if (!is_alive) { if (!create_and_connect_socket(socket_)) { return false; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT // TODO: refactoring if (is_ssl()) { auto &scli = static_cast(*this); if (!proxy_host_.empty()) { bool success = false; if (!scli.connect_with_proxy(socket_, res, success)) { return success; } } if (!scli.initialize_ssl(socket_)) { return false; } } #endif } } auto close_connection = !keep_alive_; auto ret = process_socket(socket_, [&](Stream &strm) { return handle_request(strm, req, res, close_connection); }); if (close_connection) { stop(); } return ret; } bool Client::handle_request(Stream &strm, const Request &req, Response &res, bool close_connection) { if (req.path.empty()) { return false; } bool ret; if (!is_ssl() && !proxy_host_.empty()) { auto req2 = req; req2.path = "http://" + host_and_port_ + req.path; ret = process_request(strm, req2, res, close_connection); } else { ret = process_request(strm, req, res, close_connection); } if (!ret) { return false; } if (300 < res.status && res.status < 400 && follow_location_) { ret = redirect(req, res); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if ((res.status == 401 || res.status == 407) && req.authorization_count_ < 5) { auto is_proxy = res.status == 407; const auto &username = is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; const auto &password = is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; if (!username.empty() && !password.empty()) { std::map auth; if (parse_www_authenticate(res, auth, is_proxy)) { Request new_req = req; new_req.authorization_count_ += 1; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; new_req.headers.erase(key); new_req.headers.insert(make_digest_authentication_header( req, auth, new_req.authorization_count_, random_string(10), username, password, is_proxy)); Response new_res; ret = send(new_req, new_res); if (ret) { res = new_res; } } } } #endif return ret; } bool Client::redirect(const Request &req, Response &res) { if (req.redirect_count == 0) { return false; } auto location = res.get_header_value("location"); if (location.empty()) { return false; } const static std::regex re(R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); std::smatch m; if (!std::regex_match(location, m, re)) { return false; } auto scheme = is_ssl() ? "https" : "http"; auto next_scheme = m[1].str(); auto next_host = m[2].str(); auto port_str = m[3].str(); auto next_path = m[4].str(); auto next_port = port_; if (!port_str.empty()) { next_port = std::stoi(port_str); } else if (!next_scheme.empty()) { next_port = next_scheme == "https" ? 443 : 80; } if (next_scheme.empty()) { next_scheme = scheme; } if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } if (next_scheme == scheme && next_host == host_ && next_port == port_) { return detail::redirect(*this, req, res, next_path); } else { if (next_scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT SSLClient cli(next_host.c_str(), next_port); cli.copy_settings(*this); return detail::redirect(cli, req, res, next_path); #else return false; #endif } else { Client cli(next_host.c_str(), next_port); cli.copy_settings(*this); return detail::redirect(cli, req, res, next_path); } } } bool Client::write_request(Stream &strm, const Request &req, bool close_connection) { detail::BufferStream bstrm; // Request line const auto &path = detail::encode_url(req.path); bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); // Additonal headers Headers headers; if (close_connection) { headers.emplace("Connection", "close"); } if (!req.has_header("Host")) { if (is_ssl()) { if (port_ == 443) { headers.emplace("Host", host_); } else { headers.emplace("Host", host_and_port_); } } else { if (port_ == 80) { headers.emplace("Host", host_); } else { headers.emplace("Host", host_and_port_); } } } if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } if (!req.has_header("User-Agent")) { headers.emplace("User-Agent", USER_AGENT); } if (req.body.empty()) { if (req.content_provider) { auto length = std::to_string(req.content_length); headers.emplace("Content-Length", length); } else { headers.emplace("Content-Length", "0"); } } else { if (!req.has_header("Content-Type")) { headers.emplace("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { auto length = std::to_string(req.body.size()); headers.emplace("Content-Length", length); } } if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) { headers.insert(make_basic_authentication_header(basic_auth_username_, basic_auth_password_, false)); } if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty()) { headers.insert(make_basic_authentication_header(proxy_basic_auth_username_, proxy_basic_auth_password_, true)); } detail::write_headers(bstrm, req, headers); // Flush buffer auto &data = bstrm.get_buffer(); if (!detail::write_data(strm, data.data(), data.size())) { return false; } // Body if (req.body.empty()) { if (req.content_provider) { size_t offset = 0; size_t end_offset = req.content_length; bool ok = true; DataSink data_sink; data_sink.write = [&](const char *d, size_t l) { if (ok) { if (detail::write_data(strm, d, l)) { offset += l; } else { ok = false; } } }; data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; while (offset < end_offset) { if (!req.content_provider(offset, end_offset - offset, data_sink)) { return false; } if (!ok) { return false; } } } } else { return detail::write_data(strm, req.body.data(), req.body.size()); } return true; } std::shared_ptr Client::send_with_content_provider(const char *method, const char *path, const Headers &headers, const std::string &body, size_t content_length, ContentProvider content_provider, const char *content_type) { Request req; req.method = method; req.headers = headers; req.path = path; if (content_type) { req.headers.emplace("Content-Type", content_type); } #ifdef CPPHTTPLIB_ZLIB_SUPPORT if (compress_) { if (content_provider) { size_t offset = 0; DataSink data_sink; data_sink.write = [&](const char *data, size_t data_len) { req.body.append(data, data_len); offset += data_len; }; data_sink.is_writable = [&](void) { return true; }; while (offset < content_length) { if (!content_provider(offset, content_length - offset, data_sink)) { return nullptr; } } } else { req.body = body; } if (!detail::compress(req.body)) { return nullptr; } req.headers.emplace("Content-Encoding", "gzip"); } else #endif { if (content_provider) { req.content_length = content_length; req.content_provider = content_provider; } else { req.body = body; } } auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } bool Client::process_request(Stream &strm, const Request &req, Response &res, bool close_connection) { // Send request if (!write_request(strm, req, close_connection)) { return false; } // Receive response and headers if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { return false; } if (req.response_handler) { if (!req.response_handler(res)) { return false; } } // Body if (req.method != "HEAD" && req.method != "CONNECT") { auto out = req.content_receiver ? static_cast([&](const char *buf, size_t n) { return req.content_receiver(buf, n); }) : static_cast([&](const char *buf, size_t n) { if (res.body.size() + n > res.body.max_size()) { return false; } res.body.append(buf, n); return true; }); int dummy_status; if (!detail::read_content( strm, res, (std::numeric_limits::max)(), dummy_status, req.progress, out, decompress_)) { return false; } } if (res.get_header_value("Connection") == "close" || res.version == "HTTP/1.0") { stop(); } // Log if (logger_) { logger_(req, res); } return true; } bool Client::process_socket(Socket &socket, std::function callback) { return detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, callback); } bool Client::is_ssl() const { return false; } std::shared_ptr Client::Get(const char *path) { return Get(path, Headers(), Progress()); } std::shared_ptr Client::Get(const char *path, Progress progress) { return Get(path, Headers(), std::move(progress)); } std::shared_ptr Client::Get(const char *path, const Headers &headers) { return Get(path, headers, Progress()); } std::shared_ptr Client::Get(const char *path, const Headers &headers, Progress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; req.progress = std::move(progress); auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } std::shared_ptr Client::Get(const char *path, ContentReceiver content_receiver) { return Get(path, Headers(), nullptr, std::move(content_receiver), Progress()); } std::shared_ptr Client::Get(const char *path, ContentReceiver content_receiver, Progress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } std::shared_ptr Client::Get(const char *path, const Headers &headers, ContentReceiver content_receiver) { return Get(path, headers, nullptr, std::move(content_receiver), Progress()); } std::shared_ptr Client::Get(const char *path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } std::shared_ptr Client::Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver) { return Get(path, headers, std::move(response_handler), content_receiver, Progress()); } std::shared_ptr Client::Get(const char *path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; req.response_handler = std::move(response_handler); req.content_receiver = std::move(content_receiver); req.progress = std::move(progress); auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } std::shared_ptr Client::Head(const char *path) { return Head(path, Headers()); } std::shared_ptr Client::Head(const char *path, const Headers &headers) { Request req; req.method = "HEAD"; req.headers = headers; req.path = path; auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } std::shared_ptr Client::Post(const char *path) { return Post(path, std::string(), nullptr); } std::shared_ptr Client::Post(const char *path, const std::string &body, const char *content_type) { return Post(path, Headers(), body, content_type); } std::shared_ptr Client::Post(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return send_with_content_provider("POST", path, headers, body, 0, nullptr, content_type); } std::shared_ptr Client::Post(const char *path, const Params ¶ms) { return Post(path, Headers(), params); } std::shared_ptr Client::Post(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return Post(path, Headers(), content_length, content_provider, content_type); } std::shared_ptr Client::Post(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return send_with_content_provider( "POST", path, headers, std::string(), content_length, content_provider, content_type); } std::shared_ptr Client::Post(const char *path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } std::shared_ptr Client::Post(const char *path, const MultipartFormDataItems &items) { return Post(path, Headers(), items); } std::shared_ptr Client::Post(const char *path, const Headers &headers, const MultipartFormDataItems &items) { auto boundary = detail::make_multipart_data_boundary(); std::string body; for (const auto &item : items) { body += "--" + boundary + "\r\n"; body += "Content-Disposition: form-data; name=\"" + item.name + "\""; if (!item.filename.empty()) { body += "; filename=\"" + item.filename + "\""; } body += "\r\n"; if (!item.content_type.empty()) { body += "Content-Type: " + item.content_type + "\r\n"; } body += "\r\n"; body += item.content + "\r\n"; } body += "--" + boundary + "--\r\n"; std::string content_type = "multipart/form-data; boundary=" + boundary; return Post(path, headers, body, content_type.c_str()); } std::shared_ptr Client::Put(const char *path) { return Put(path, std::string(), nullptr); } std::shared_ptr Client::Put(const char *path, const std::string &body, const char *content_type) { return Put(path, Headers(), body, content_type); } std::shared_ptr Client::Put(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return send_with_content_provider("PUT", path, headers, body, 0, nullptr, content_type); } std::shared_ptr Client::Put(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return Put(path, Headers(), content_length, content_provider, content_type); } std::shared_ptr Client::Put(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return send_with_content_provider( "PUT", path, headers, std::string(), content_length, content_provider, content_type); } std::shared_ptr Client::Put(const char *path, const Params ¶ms) { return Put(path, Headers(), params); } std::shared_ptr Client::Put(const char *path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } std::shared_ptr Client::Patch(const char *path, const std::string &body, const char *content_type) { return Patch(path, Headers(), body, content_type); } std::shared_ptr Client::Patch(const char *path, const Headers &headers, const std::string &body, const char *content_type) { return send_with_content_provider("PATCH", path, headers, body, 0, nullptr, content_type); } std::shared_ptr Client::Patch(const char *path, size_t content_length, ContentProvider content_provider, const char *content_type) { return Patch(path, Headers(), content_length, content_provider, content_type); } std::shared_ptr Client::Patch(const char *path, const Headers &headers, size_t content_length, ContentProvider content_provider, const char *content_type) { return send_with_content_provider( "PATCH", path, headers, std::string(), content_length, content_provider, content_type); } std::shared_ptr Client::Delete(const char *path) { return Delete(path, Headers(), std::string(), nullptr); } std::shared_ptr Client::Delete(const char *path, const std::string &body, const char *content_type) { return Delete(path, Headers(), body, content_type); } std::shared_ptr Client::Delete(const char *path, const Headers &headers) { return Delete(path, headers, std::string(), nullptr); } std::shared_ptr Client::Delete(const char *path, const Headers &headers, const std::string &body, const char *content_type) { Request req; req.method = "DELETE"; req.headers = headers; req.path = path; if (content_type) { req.headers.emplace("Content-Type", content_type); } req.body = body; auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } std::shared_ptr Client::Options(const char *path) { return Options(path, Headers()); } std::shared_ptr Client::Options(const char *path, const Headers &headers) { Request req; req.method = "OPTIONS"; req.path = path; req.headers = headers; auto res = std::make_shared(); return send(req, *res) ? res : nullptr; } size_t Client::is_socket_open() const { std::lock_guard guard(socket_mutex_); return socket_.is_open(); } void Client::stop() { std::lock_guard guard(socket_mutex_); if (socket_.is_open()) { detail::shutdown_socket(socket_.sock); std::this_thread::sleep_for(std::chrono::milliseconds(1)); close_socket(socket_, true); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void Client::set_timeout_sec(time_t timeout_sec) { set_connection_timeout(timeout_sec, 0); } void Client::set_connection_timeout(time_t sec, time_t usec) { connection_timeout_sec_ = sec; connection_timeout_usec_ = usec; } void Client::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; } void Client::set_write_timeout(time_t sec, time_t usec) { write_timeout_sec_ = sec; write_timeout_usec_ = usec; } void Client::set_basic_auth(const char *username, const char *password) { basic_auth_username_ = username; basic_auth_password_ = password; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void Client::set_digest_auth(const char *username, const char *password) { digest_auth_username_ = username; digest_auth_password_ = password; } #endif void Client::set_keep_alive(bool on) { keep_alive_ = on; } void Client::set_follow_location(bool on) { follow_location_ = on; } void Client::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } void Client::set_socket_options(SocketOptions socket_options) { socket_options_ = socket_options; } void Client::set_compress(bool on) { compress_ = on; } void Client::set_decompress(bool on) { decompress_ = on; } void Client::set_interface(const char *intf) { interface_ = intf; } void Client::set_proxy(const char *host, int port) { proxy_host_ = host; proxy_port_ = port; } void Client::set_proxy_basic_auth(const char *username, const char *password) { proxy_basic_auth_username_ = username; proxy_basic_auth_password_ = password; } void Client::set_websocket_mask(bool on) { websocket_mask_ = on; } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void Client::set_proxy_digest_auth(const char *username, const char *password) { proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } #endif void Client::set_logger(Logger logger) { logger_ = std::move(logger); } } // namespace httplib ================================================ FILE: core-tests/src/_lib/process.cpp ================================================ #include "test_process.h" using swoole::UnixSocket; using swoole::test::Process; Process::Process(std::function fn, int pipe_type) : handler(fn) { if (pipe_type > 0) { auto pipe = new UnixSocket(true, SOCK_DGRAM); worker.pipe_master = pipe->get_socket(true); worker.pipe_worker = pipe->get_socket(false); worker.pipe_object = pipe; worker.pipe_current = worker.pipe_master; } } Process::~Process() { if (worker.pipe_object) { delete worker.pipe_object; } } pid_t Process::start() { // std::system("ls /proc/self/task"); pid_t pid = swoole_fork(0); if (pid < 0) { printf("[Worker] Fatal Error: fork() failed"); exit(1); } else if (pid == 0) { worker.child_process = 1; worker.pipe_current = worker.pipe_worker; handler(this); exit(0); } else { worker.pid = pid; worker.child_process = 0; return pid; } } ssize_t Process::write(const void *__buf, size_t __n) { return worker.pipe_current->write(__buf, __n); } ssize_t Process::read(void *__buf, size_t __nbytes) { return worker.pipe_current->read(__buf, __nbytes); } ================================================ FILE: core-tests/src/_lib/redis.cpp ================================================ #include "redis_client.h" #include "test_core.h" #include using namespace std; namespace swoole { bool RedisClient::Connect(const string &host, int port, struct timeval timeout) { redisContext *c = redisConnectWithTimeout(host.c_str(), port, timeout); if (c == NULL) { printf("Connection error: can't allocate redis context\n"); return false; } if (c->err) { printf("Connection error: %s\n", c->errstr); redisFree(c); return false; } ctx = c; return true; } string RedisClient::Get(const string &key) { const char *argv[] = {"GET", key.c_str()}; size_t argvlen[] = {strlen(argv[0]), key.length()}; auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); if (!reply.empty() && reply->str) { return string(reply->str, reply->len); } else { return ""; } } long RedisClient::Ttl(const std::string &key) { const char *argv[] = {"TTL", key.c_str()}; size_t argvlen[] = {strlen(argv[0]), key.length()}; auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); if (!reply.empty() && reply->integer) { return reply->integer; } else { return 0; } } bool RedisClient::Select(int db) { auto _db = std::to_string(db); const char *argv[] = {"SELECT", _db.c_str()}; size_t argvlen[] = {strlen(argv[0]), _db.length()}; auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); if (!reply.empty() && reply->type == REDIS_REPLY_STATUS && strncmp(reply->str, "OK", 2) == 0) { return true; } else { return false; } } std::string RedisClient::Role() { const char *argv[] = {"ROLE"}; size_t argvlen[] = {strlen(argv[0])}; auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); if (!reply.empty() && reply->str) { return string(reply->str, reply->len); } else { return ""; } } bool RedisClient::Set(const string &key, const string &value) { const char *argv[] = {"SET", key.c_str(), value.c_str()}; size_t argvlen[] = {strlen(argv[0]), key.length(), value.length()}; auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); if (!reply.empty() && reply->type == REDIS_REPLY_STATUS && strncmp(reply->str, "OK", 2) == 0) { return true; } else { return false; } } RedisReply RedisClient::Request(int argc, const char **argv, const size_t *argvlen) { return redisCommandArgv(ctx, argc, argv, argvlen); } RedisReply RedisClient::Request(const vector &args) { ctx->err = 0; size_t n = args.size(); const char **argv = new const char *[n]; size_t *argvlen = new size_t[n]; for (size_t i = 0; i < args.size(); i++) { argv[i] = args[i].c_str(); argvlen[i] = args[i].length(); } auto reply = Request(args.size(), (const char **) argv, (const size_t *) argvlen); delete[] argv; delete[] argvlen; return reply; } } // namespace swoole ================================================ FILE: core-tests/src/_lib/server.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "test_server.h" #include "swoole_memory.h" #include #include using namespace swoole::test; using swoole::network::Address; Server::Server(std::string _host, int _port, swoole::Server::Mode _mode, int _type) : serv(_mode), host(_host), port(_port), mode(_mode), type(_type) { serv.worker_num = 1; if (mode == swoole::Server::MODE_BASE) { serv.reactor_num = 1; serv.worker_num = 1; } serv.dispatch_mode = 2; serv.private_data_2 = this; if (!listen(host, port, (swSocketType) type)) { swoole_sys_warning("listen(%s:%d) failed", host.c_str(), port); exit(0); } if (serv.create() < 0) { swoole_sys_warning("create server failed"); exit(0); } } Server::~Server() {} std::string Server::tolower(const std::string &str) { std::string str_copy = str; std::transform(str_copy.begin(), str_copy.end(), str_copy.begin(), [](unsigned char c) { return std::tolower(c); }); return str_copy; } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "workerstart") { serv.onWorkerStart = fn; } else if (event == "workerstop") { serv.onWorkerStop = fn; } } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "start") { serv.onStart = fn; } else if (event == "shutdown") { serv.onShutdown = fn; } } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "pipemessage") { serv.onPipeMessage = fn; } } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "task") { serv.onTask = fn; } else if (event == "finish") { serv.onFinish = fn; } } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "packet") { serv.onPacket = fn; } else if (event == "receive") { serv.onReceive = fn; } } void Server::on(const std::string &_event, const std::function &fn) { auto event = tolower(_event); if (event == "connect") { serv.onConnect = fn; } else if (event == "close") { serv.onClose = fn; } } bool Server::start() { return serv.start() == 0; } bool Server::listen(const std::string &host, int port, enum swSocketType type) { ListenPort *ls = serv.add_port(type, (char *) host.c_str(), port); if (ls == nullptr) { return false; } ports.push_back(ls); return true; } int Server::send(int session_id, const void *data, uint32_t length) { return serv.send(session_id, data, length); } ssize_t Server::sendto(const Address &address, const char *__buf, size_t __n, int server_socket_fd) { network::Socket *server_socket; if (server_socket_fd < 0) { server_socket = serv.udp_socket_ipv6 ? serv.udp_socket_ipv6 : serv.udp_socket_ipv4; } else { server_socket = serv.get_server_socket(server_socket_fd); } return server_socket->sendto(address, __buf, __n, 0); } int Server::close(int session_id, int reset) { return serv.close(session_id, reset); } ================================================ FILE: core-tests/src/_lib/ssl.cpp ================================================ #include "test_core.h" namespace swoole { namespace test { void printAllSubjectEntries(X509_NAME *name) { if (!name) return; int entry_count = X509_NAME_entry_count(name); for (int i = 0; i < entry_count; ++i) { X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry); // 获取字段的短名称(如 CN、O、ST 等) char obj_txt[80] = {0}; OBJ_obj2txt(obj_txt, sizeof(obj_txt), obj, 0); // 获取字段的值 unsigned char *utf8 = nullptr; int length = ASN1_STRING_to_UTF8(&utf8, data); if (length >= 0 && utf8) { sw_printf("%s: %.*s\n", obj_txt, length, utf8); OPENSSL_free(utf8); } } } void printX509Info(X509 *cert) { X509_NAME *subject_name = X509_get_subject_name(cert); printAllSubjectEntries(subject_name); char *subject = X509_NAME_oneline(subject_name, 0, 0); if (subject) { sw_printf("Peer certificate subject: %s\n", subject); OPENSSL_free(subject); } X509_NAME *issuer_name = X509_get_issuer_name(cert); printAllSubjectEntries(issuer_name); // 获取证书有效期 ASN1_TIME *not_before = X509_get_notBefore(cert); ASN1_TIME *not_after = X509_get_notAfter(cert); BIO *bio = BIO_new(BIO_s_mem()); ASN1_TIME_print(bio, not_before); char buf[256] = {0}; int len = BIO_read(bio, buf, sizeof(buf) - 1); buf[len] = 0; sw_printf("Validity Not Before: %s\n", buf); ASN1_TIME_print(bio, not_after); len = BIO_read(bio, buf, sizeof(buf) - 1); buf[len] = 0; sw_printf("Validity Not After: %s\n", buf); BIO_free(bio); // 获取公钥 EVP_PKEY *pubkey = X509_get_pubkey(cert); if (pubkey) { sw_printf("Public key type: %d\n", EVP_PKEY_id(pubkey)); EVP_PKEY_free(pubkey); } } int dump_cert_info(const char *data, size_t len) { BIO *bio = BIO_new_mem_buf(data, (int) len); if (!bio) { std::cerr << "Failed to create BIO" << std::endl; return 1; } // 从 BIO 中读取证书 X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); if (!cert) { std::cerr << "Failed to parse X509 certificate" << std::endl; BIO_free(bio); return 1; } // 打印证书信息 printX509Info(cert); // 释放资源 X509_free(cert); BIO_free(bio); EVP_cleanup(); return 0; } } // namespace test } // namespace swoole ================================================ FILE: core-tests/src/core/base.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_server.h" #include "swoole_file.h" #include "swoole_util.h" #include "swoole.h" #include "swoole_config.h" #include "swoole_api.h" #include using namespace swoole; using namespace std; static const string test_data("hello world\n"); TEST(base, datahead_dump) { swDataHead data = {}; data.fd = 123; char buf[128]; size_t n = data.dump(buf, sizeof(buf)); data.print(); ASSERT_GT(std::string(buf, n).find("int fd = 123;"), 1); } TEST(base, dec2hex) { auto result = swoole_dec2hex(2684326179, 16); ASSERT_STREQ(result, "9fff9123"); sw_free(result); } TEST(base, hex2dec) { size_t n_parsed; ASSERT_EQ(swoole_hex2dec("9fff9123", &n_parsed), 2684326179); ASSERT_EQ(n_parsed, 8); ASSERT_EQ(swoole_hex2dec("0x9fff9123", &n_parsed), 2684326179); ASSERT_EQ(n_parsed, 10); ASSERT_EQ(swoole_hex2dec("f", &n_parsed), 15); ASSERT_EQ(n_parsed, 1); } TEST(base, random_string) { char buf[1024] = {}; swoole_random_string(buf, sizeof(buf) - 1); ASSERT_EQ(strlen(buf), sizeof(buf) - 1); } static size_t test_sw_vsnprintf(char *buf, size_t size, const char *format, ...) { va_list args; va_start(args, format); size_t result = sw_vsnprintf(buf, size, format, args); va_end(args); return result; } TEST(base, file_put_contents) { char buf[65536]; swoole_random_string(buf, sizeof(buf) - 1); ASSERT_TRUE(file_put_contents(TEST_TMP_FILE, buf, sizeof(buf))); auto result = file_get_contents(TEST_TMP_FILE); ASSERT_STREQ(buf, result->value()); } TEST(base, file_get_size) { File f(TEST_TMP_FILE, File::WRITE | File::CREATE); char buf[65536]; swoole_random_string(buf, sizeof(buf) - 1); ASSERT_TRUE(f.ready()); f.truncate(0); f.set_offset(0); f.write(buf, sizeof(buf) - 1); f.close(); ASSERT_EQ(file_get_size(TEST_TMP_FILE), sizeof(buf) - 1); } TEST(base, version_compare) { ASSERT_EQ(swoole_version_compare("1.2.1", "1.2.0"), 1); ASSERT_EQ(swoole_version_compare("1.2.3", "1.3.0"), -1); ASSERT_EQ(swoole_version_compare("1.2.3", "1.2.9"), -1); ASSERT_EQ(swoole_version_compare("1.2.0", "1.2.0"), 0); } TEST(base, common_divisor) { ASSERT_EQ(swoole_common_divisor(16, 12), 4); ASSERT_EQ(swoole_common_divisor(6, 15), 3); ASSERT_EQ(swoole_common_divisor(32, 16), 16); } TEST(base, common_multiple) { ASSERT_EQ(swoole_common_multiple(16, 12), 48); ASSERT_EQ(swoole_common_multiple(6, 15), 30); ASSERT_EQ(swoole_common_multiple(32, 16), 32); } TEST(base, shell_exec) { pid_t pid; string str = "md5sum " + test::get_jpg_file(); int _pipe = swoole_shell_exec(str.c_str(), &pid, 0); ASSERT_GT(_pipe, 0); ASSERT_GT(pid, 0); char buf[1024] = {}; ssize_t n = read(_pipe, buf, sizeof(buf) - 1); ASSERT_GT(n, 0); ASSERT_STREQ(string(buf).substr(0, sizeof(TEST_JPG_MD5SUM) - 1).c_str(), TEST_JPG_MD5SUM); close(_pipe); str = "md5sum test.abcdef"; _pipe = swoole_shell_exec(str.c_str(), &pid, 1); memset(buf, 0, sizeof(buf)); ssize_t length = 0; while (1) { n = read(_pipe, buf + length, sizeof(buf) - 1 - length); length += n; if (n > 0) { continue; } break; } ASSERT_GT(length, 0); ASSERT_STREQ(buf, string("md5sum: test.abcdef: No such file or directory\n").c_str()); close(_pipe); } TEST(base, file_size) { auto file = test::get_jpg_file(); ssize_t file_size = file_get_size(file); ASSERT_GT(file_size, 0); auto fp = fopen(file.c_str(), "r+"); ASSERT_TRUE(fp); ASSERT_EQ(file_get_size(fp), file_size); fclose(fp); } TEST(base, eventdata_pack) { EventData ed1{}; ASSERT_TRUE(Server::task_pack(&ed1, test_data.c_str(), test_data.length())); ASSERT_EQ(string(ed1.data, ed1.info.len), test_data); EventData ed2{}; ASSERT_EQ(swoole_random_bytes(sw_tg_buffer()->str, SW_BUFFER_SIZE_BIG), SW_BUFFER_SIZE_BIG); ASSERT_TRUE(Server::task_pack(&ed2, sw_tg_buffer()->str, SW_BUFFER_SIZE_BIG)); String _buffer(SW_BUFFER_SIZE_BIG); PacketPtr packet; ASSERT_TRUE(Server::task_unpack(&ed2, &_buffer, &packet)); ASSERT_EQ(memcmp(sw_tg_buffer()->str, _buffer.str, SW_BUFFER_SIZE_BIG), 0); } TEST(base, stack_defer_fn) { int count = 0; ON_SCOPE_EXIT { count++; ASSERT_EQ(count, 2); }; ON_SCOPE_EXIT { count++; ASSERT_EQ(count, 1); }; } TEST(base, string_format) { char *data = swoole_string_format(128, "hello %d world, %s is best.", 2020, "swoole"); ASSERT_STREQ(data, "hello 2020 world, swoole is best."); sw_free(data); } TEST(base, dirname) { ASSERT_EQ(dirname("/hello/world/index.html.abc"), "/hello/world"); ASSERT_EQ(dirname("/hello/world"), "/hello"); ASSERT_EQ(dirname("/root"), "/"); ASSERT_EQ(dirname("/"), "/"); } TEST(base, mkdir_recursive) { String dir(PATH_MAX + 2); dir.append_random_bytes(PATH_MAX, true); ASSERT_FALSE(swoole_mkdir_recursive(dir.to_std_string())); } TEST(base, set_task_tmpdir) { auto ori_tmpdir = swoole_get_task_tmpdir(); ASSERT_FALSE(swoole_set_task_tmpdir("aaa")); size_t length = SW_TASK_TMP_PATH_SIZE + 1; char too_long_dir[SW_TASK_TMP_PATH_SIZE + 2] = {}; swoole_random_string(too_long_dir + 1, length - 1); too_long_dir[0] = '/'; ASSERT_FALSE(swoole_set_task_tmpdir(too_long_dir)); const char *tmpdir = "/tmp/swoole/core_tests/base"; ASSERT_TRUE(swoole_set_task_tmpdir(tmpdir)); File fp = make_tmpfile(); ASSERT_TRUE(fp.ready()); char buf[128]; swoole_random_string(buf, sizeof(buf) - 2); buf[sizeof(buf) - 2] = '\n'; buf[sizeof(buf) - 1] = '\0'; fp.write(buf, sizeof(buf) - 1); fp.close(); ASSERT_EQ(swoole::dirname(fp.get_path()), tmpdir); ASSERT_STREQ(swoole::file_get_contents(fp.get_path())->str, buf); unlink(fp.get_path().c_str()); rmdir(tmpdir); char buf2[264]; swoole_random_string(buf2, sizeof(buf2) - 1); memcpy(buf2, "/tmp/", 5); buf2[64] = '/'; buf2[128] = '/'; buf2[192] = '/'; buf2[256] = '/'; std::string dir(buf2); ASSERT_FALSE(swoole_set_task_tmpdir(dir)); test::recursive_rmdir(dir.c_str()); ASSERT_TRUE(swoole_set_task_tmpdir(ori_tmpdir)); } TEST(base, version) { ASSERT_STREQ(swoole_version(), SWOOLE_VERSION); ASSERT_EQ(swoole_version_id(), SWOOLE_VERSION_ID); ASSERT_EQ(swoole_api_version_id(), SWOOLE_API_VERSION_ID); } TEST(base, hook) { int count = 0; swoole_add_hook( SW_GLOBAL_HOOK_END, [](void *data) -> void { int *_count = (int *) data; *_count = 9999; }, 1); ASSERT_TRUE(swoole_isset_hook(SW_GLOBAL_HOOK_END)); swoole_call_hook(SW_GLOBAL_HOOK_END, &count); ASSERT_EQ(count, 9999); } TEST(base, intersection) { std::vector vec1{"index.php", "index.html", "default.html"}; std::set vec2{".", "..", "default.html", "index.php", "test.html", "a.json", "index.php"}; ASSERT_EQ("index.php", swoole::intersection(vec1, vec2)); std::set vec3{"a", "zh中", "、r\n"}; ASSERT_EQ("", swoole::intersection(vec1, vec3)); } TEST(base, itoa) { char buf[128]; long value = 123456987; int n = swoole_itoa(buf, value); ASSERT_EQ(n, 9); ASSERT_STREQ(buf, "123456987"); } TEST(base, get_systemd_listen_fds) { ASSERT_EQ(swoole_get_systemd_listen_fds(), -1); setenv("LISTEN_FDS", to_string(SW_MAX_LISTEN_PORT + 1).c_str(), 1); ASSERT_EQ(swoole_get_systemd_listen_fds(), -1); setenv("LISTEN_FDS", to_string(SW_MAX_LISTEN_PORT - 1).c_str(), 1); ASSERT_EQ(swoole_get_systemd_listen_fds(), SW_MAX_LISTEN_PORT - 1); } TEST(base, type_size) { ASSERT_EQ(swoole_type_size('c'), 1); ASSERT_EQ(swoole_type_size('s'), 2); ASSERT_EQ(swoole_type_size('l'), 4); ASSERT_EQ(swoole_type_size('b'), 0); ASSERT_EQ(swoole_type_size('q'), 8); ASSERT_EQ(swoole_type_size('P'), 8); } size_t swoole_fatal_error_impl(const char *format, ...) { size_t retval = 0; va_list args; va_start(args, format); char buf[128]; retval += sw_vsnprintf(buf, 128, format, args); va_end(args); return retval; } TEST(base, vsnprintf) { ASSERT_GT(swoole_fatal_error_impl("Hello %s", "World!!!"), 0); char buffer[10]; { // The 9th byte will be set to \ 0, discarding one character size_t result = test_sw_vsnprintf(buffer, 9, "Test %d", 1234); EXPECT_STREQ(buffer, "Test 123"); EXPECT_EQ(result, 8); } { size_t result = test_sw_vsnprintf(buffer, sizeof(buffer), "Test %d is too long", 12345); EXPECT_EQ(buffer[sizeof(buffer) - 1], '\0'); EXPECT_EQ(result, sizeof(buffer) - 1); EXPECT_STREQ(buffer, "Test 1234"); } } TEST(base, snprintf) { char buffer[10]; { // The 9th byte will be set to \ 0, discarding one character size_t result = sw_snprintf(buffer, 9, "Test %d", 1234); EXPECT_STREQ(buffer, "Test 123"); EXPECT_EQ(result, 8); } { size_t result = sw_snprintf(buffer, sizeof(buffer), "Test %d is too long", 12345); EXPECT_EQ(buffer[sizeof(buffer) - 1], '\0'); EXPECT_EQ(result, sizeof(buffer) - 1); EXPECT_STREQ(buffer, "Test 1234"); } } TEST(base, log_level) { int level = sw_logger()->get_level(); swoole_set_log_level(SW_LOG_TRACE); swoole_print_backtrace(); EXPECT_EQ(SW_LOG_TRACE, sw_logger()->get_level()); swoole_set_log_level(level); } TEST(base, trace_flag) { int flags = SwooleG.trace_flags; swoole_set_trace_flags(SW_TRACE_CARES); EXPECT_EQ(SW_TRACE_CARES, SwooleG.trace_flags); swoole_set_trace_flags(flags); } TEST(base, only_dump) { // just dump something std::string data = "hello world"; swoole_dump_ascii(data.c_str(), data.length()); swoole_dump_bin((uchar *) data.c_str(), 'C', data.length()); swoole_dump_hex((uchar *) data.c_str(), data.length()); ASSERT_TRUE(true); } TEST(base, redirect_stdout) { auto file = TEST_LOG_FILE; auto out_1 = "hello world, hello swoole!\n"; auto out_2 = "write to /dev/null\n"; auto status = test::spawn_exec_and_wait([&]() { swoole_redirect_stdout(file); printf("%s\n", out_1); fflush(stdout); swoole_redirect_stdout("/dev/null"); printf("%s\n", out_2); fflush(stdout); swoole_clear_last_error(); swoole_redirect_stdout("/tmp/not-exists/test.log"); ASSERT_ERREQ(ENOTDIR); }); ASSERT_EQ(status, 0); auto rs = swoole::file_get_contents(file); ASSERT_NE(rs, nullptr); ASSERT_TRUE(rs->contains(out_1)); ASSERT_FALSE(rs->contains(out_2)); unlink(file); } TEST(base, fatal_error) { const char *msg = "core tests fatal error"; auto status = test::spawn_exec_and_wait([msg]() { swoole_set_log_file(TEST_LOG_FILE); swoole_fatal_error(9999, msg); }); ASSERT_EQ(WEXITSTATUS(status), 1); auto rs = file_get_contents(TEST_LOG_FILE); ASSERT_NE(rs, nullptr); ASSERT_TRUE(rs->contains(msg)); ASSERT_TRUE(rs->contains("(ERROR 9999)")); File::remove(TEST_LOG_FILE); } TEST(base, spinlock) { test::counter_init(); auto counter = test::counter_ptr(); int n = 4096; auto test_fn = [counter, n]() { SW_LOOP_N(n) { sw_spinlock((sw_atomic_t *) &counter[0]); counter[1]++; if (i % 100 == 0) { usleep(5); } sw_spinlock_release((sw_atomic_t *) &counter[0]); } }; std::thread t1(test_fn); std::thread t2(test_fn); t1.join(); t2.join(); ASSERT_EQ(counter[1], n * 2); } TEST(base, futex) { sw_atomic_t value = 1; std::thread t1([&value] { DEBUG() << "wait 1\n"; ASSERT_EQ(sw_atomic_futex_wait(&value, -1), SW_OK); // no wait value = 0; DEBUG() << "wait 2\n"; ASSERT_EQ(sw_atomic_futex_wait(&value, 0.05), SW_ERR); // timed out ASSERT_EQ(sw_atomic_futex_wait(&value, 0.5), SW_OK); // success DEBUG() << "wait 3\n"; value = 0; ASSERT_EQ(sw_atomic_futex_wait(&value, -1), SW_OK); // no timeout }); std::thread t2([&value] { usleep(100000); DEBUG() << "wakeup 1\n"; ASSERT_EQ(sw_atomic_futex_wakeup(&value, 1), 1); DEBUG() << "wakeup 2\n"; usleep(100000); ASSERT_EQ(sw_atomic_futex_wakeup(&value, 1), 1); }); t1.join(); t2.join(); } static int test_fork_fail(const std::function &after_fork_fail = nullptr) { rlimit rl{}; rlim_t ori_nproc_max; int count = 0; rlim_t nproc_max = 32; // 获取当前 NPROC 限制 if (getrlimit(RLIMIT_NPROC, &rl) != 0) { perror("getrlimit failed"); return 1; } printf("Current NPROC limit: soft=%lu, hard=%lu\n", rl.rlim_cur, rl.rlim_max); ori_nproc_max = rl.rlim_max; rl.rlim_cur = nproc_max; if (setrlimit(RLIMIT_NPROC, &rl) != 0) { perror("setrlimit failed"); return 1; } printf("New NPROC limit: soft=%lu\n", rl.rlim_cur); std::vector children; // 循环创建子进程直到失败 while (true) { pid_t pid = fork(); if (pid < 0) { // fork 失败 printf("fork() failed after %d processes: %s\n", count, strerror(errno)); break; } else if (pid == 0) { sleep(30); exit(0); } else { // 父进程 count++; children.push_back(pid); printf("Created child process #%d (PID: %d)\n", count, pid); } } if (after_fork_fail) { after_fork_fail(); } printf("Cleaning up child processes...\n"); for (const int i : children) { kill(i, SIGKILL); } test::wait_all_child_processes(); rl.rlim_cur = ori_nproc_max; // 恢复 NPROC 限制 if (setrlimit(RLIMIT_NPROC, &rl) != 0) { perror("setrlimit failed"); return 1; } return 0; } #if 0 TEST(base, fork_fail) { auto status = test::spawn_exec_and_wait([]() { if (geteuid() == 0) { Server::worker_set_isolation("nobody", "nobody", ""); } ASSERT_EQ(test_fork_fail([]() { pid_t pid; auto pipe_fd = swoole_shell_exec("sleep 10", &pid, 0); ASSERT_EQ(pipe_fd, -1); }), 0); ASSERT_ERREQ(EAGAIN); }); ASSERT_EQ(status, 0); } #endif TEST(base, undefined_behavior) { swoole_init(); // no effect delete SwooleG.logger; SwooleG.logger = nullptr; // avoid double free in swoole_shutdown() ASSERT_EQ(swoole_get_log_level(), SW_LOG_NONE); SwooleG.logger = new Logger(); } ================================================ FILE: core-tests/src/core/channel.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_channel.h" using namespace std; using namespace swoole; const int N = 10000000; TEST(channel, push) { auto *c = Channel::make(128 * 1024, 8192, SW_CHAN_LOCK | SW_CHAN_NOTIFY); map m; size_t bytes = 0; int index = 0; while (bytes < N) { char buf[8000]; int n = swoole_random_bytes(buf, (rand() % (sizeof(buf) / 2)) + (sizeof(buf) / 2)); if (n <= 0) { swoole_trace("no enough data, n=%d, errno=%d\n", n, errno); continue; } m[index++] = string(buf, n); bytes += n; } swoole_trace("size=%lu", m.size()); thread t1([&]() { auto next = m.find(0); int index = 1; size_t bytes = 0; while (bytes < N) { if (c->push(next->second.c_str(), next->second.length()) == SW_OK) { swoole_trace("[PUSH] index=%d, size=%lu", index, next->second.length()); bytes += next->second.length(); next = m.find(index++); if (next == m.end()) { break; } } else { usleep(10); } } }); thread t2([&]() { char buf[8000]; size_t bytes = 0; int index = 0; while (bytes < N) { int retval = c->pop(buf, sizeof(buf)); if (retval > 0) { swoole_trace("[POP] index=%d, size=%d", index, retval); string &_data = m[index++]; bytes += retval; ASSERT_EQ(_data, string(buf, retval)); } else { usleep(10); } } }); t1.join(); t2.join(); c->destroy(); } TEST(channel, peek) { char buf[8000]; auto *c = Channel::make(128 * 1024, 8192, SW_CHAN_LOCK | SW_CHAN_NOTIFY); ASSERT_EQ(c->peek(buf, sizeof(buf)), SW_ERR); string value = "test"; c->push(value.c_str(), value.length()); ASSERT_EQ(c->peek((void *) buf, sizeof(buf)), value.length()); c->destroy(); } TEST(channel, notify) { auto *c = Channel::make(128 * 1024, 8192, SW_CHAN_LOCK | SW_CHAN_NOTIFY); thread t1([&]() { sleep(0.02); string value = "test"; c->push(value.c_str(), value.length()); c->notify(); }); thread t2([&]() { while (c->wait()) { char buf[8000]; ASSERT_GT(c->pop((void *) buf, sizeof(buf)), 0); break; } }); t1.join(); t2.join(); c->print(); c->destroy(); } ================================================ FILE: core-tests/src/core/hash.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_hash.h" static const int hash_count = 8192; static const int str_max_len = 1024; static const char *data = "hello world, PHP the best."; TEST(hash, crc32) { ASSERT_EQ(swoole_crc32(data, strlen(data)), 2962796788); } static void test_hash_func(uint64_t (*hash_fn)(const char *key, size_t len), int n) { std::vector hashes; std::vector data; hashes.resize(n); data.resize(n); SW_LOOP_N(n) { size_t len = 1 + swoole_random_int() % str_max_len; char buf[str_max_len]; ASSERT_EQ(swoole_random_bytes(buf, len), len); hashes[i] = hash_fn(buf, len); data[i] = std::string(buf, len); } usleep(100); SW_LOOP_N(n) { auto &s = data.at(i); ASSERT_EQ(hashes[i], hash_fn(s.c_str(), s.length())); } } TEST(hash, php) { test_hash_func(swoole_hash_php, hash_count); } TEST(hash, jenkins) { test_hash_func(swoole_hash_jenkins, hash_count); } TEST(hash, austin) { test_hash_func(swoole_hash_austin, hash_count); } ================================================ FILE: core-tests/src/core/heap.cpp ================================================ #include "test_core.h" #include "swoole_heap.h" #include typedef struct node_t { int pri; int val; } node_t; #define SIZE 100 TEST(heap, random) { node_t *ns; node_t *n; swoole::Heap pq(SIZE, swoole::Heap::MAX_HEAP); std::map _map; ASSERT_EQ(pq.peek(), nullptr); int i; for (i = 0; i < SIZE * 2 - 1; i++) { int pri = swoole_system_random(10000, 99999); ns = (node_t *) malloc(sizeof(node_t)); ns->val = i; ns->pri = pri; pq.push(pri, ns); _map[i] = pri; if (0 == i) { pq.print(); // print once } } n = (node_t *) pq.peek(); ASSERT_EQ(_map[n->val], n->pri); while ((n = (node_t *) pq.pop())) { ASSERT_EQ(_map[n->val], n->pri); free(n); } } ================================================ FILE: core-tests/src/core/log.cpp ================================================ #include "test_core.h" #include "swoole_file.h" #include "swoole_process_pool.h" #include #include using namespace swoole; const char *file = "/tmp/swoole_log_test.log"; TEST(log, level) { std::vector processTypes = {SW_MASTER, SW_MANAGER, SW_WORKER, SW_TASK_WORKER}; int originType = swoole_get_worker_type(); for (auto iter = processTypes.begin(); iter != processTypes.end(); iter++) { swoole_set_worker_type(*iter); sw_logger()->reset(); ASSERT_FALSE(sw_logger()->is_opened()); sw_logger()->set_level(999); ASSERT_EQ(sw_logger()->get_level(), SW_LOG_NONE); sw_logger()->set_level(SW_LOG_DEBUG - 10); ASSERT_EQ(sw_logger()->get_level(), SW_LOG_DEBUG); sw_logger()->set_level(SW_LOG_NOTICE); sw_logger()->open(file); ASSERT_TRUE(sw_logger()->is_opened()); sw_logger()->put(SW_LOG_DEBUG, SW_STRL("hello no debug")); sw_logger()->put(SW_LOG_TRACE, SW_STRL("hello no trace")); sw_logger()->put(SW_LOG_INFO, SW_STRL("hello info")); sw_logger()->put(SW_LOG_NOTICE, SW_STRL("hello notice")); sw_logger()->put(SW_LOG_WARNING, SW_STRL("hello warning")); sw_logger()->set_level(SW_LOG_DEBUG); sw_logger()->put(SW_LOG_DEBUG, SW_STRL("hello debug")); sw_logger()->put(SW_LOG_TRACE, SW_STRL("hello trace")); auto content = file_get_contents(file); sw_logger()->close(); unlink(file); ASSERT_FALSE(content->contains(SW_STRL("hello no debug"))); ASSERT_FALSE(content->contains(SW_STRL("hello no trace"))); ASSERT_TRUE(content->contains(SW_STRL("hello debug"))); ASSERT_TRUE(content->contains(SW_STRL("hello trace"))); ASSERT_FALSE(content->contains(SW_STRL("hello info"))); ASSERT_TRUE(content->contains(SW_STRL("hello notice"))); ASSERT_TRUE(content->contains(SW_STRL("hello warning"))); swoole_set_worker_type(originType); } } TEST(log, date_format) { sw_logger()->reset(); sw_logger()->set_date_format("day %d of %B in the year %Y. Time: %I:%S %p"); sw_logger()->open(file); sw_logger()->put(SW_LOG_WARNING, SW_STRL("hello world")); auto content = file_get_contents(file); sw_logger()->close(); unlink(file); int data[16]; char *month = nullptr; char *am = nullptr; int n = std::sscanf(content->value(), "[day %d of %s in the year %d. Time: %d:%d %s @%d.%d]\tWARNING\thello world", data, month, data + 1, data + 2, data + 3, am, data + 4, data + 5); ASSERT_TRUE(n); } TEST(log, date_format_long_string) { sw_logger()->reset(); sw_logger()->set_level(SW_LOG_ERROR); std::unique_ptr content(new String(256)); auto str = content.get(); str->repeat("x", 1, 120); str->append(SW_STRL("day %d of %B in the year %Y. Time: %I:%S %p")); bool retval = sw_logger()->set_date_format(str->str); ASSERT_FALSE(retval); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); } TEST(log, date_with_microseconds) { sw_logger()->reset(); sw_logger()->set_date_with_microseconds(true); sw_logger()->open(file); sw_logger()->put(SW_LOG_WARNING, SW_STRL("hello world")); auto content = file_get_contents(file); sw_logger()->close(); unlink(file); std::regex e("\\[\\S+\\s\\d{2}:\\d{2}:\\d{2}\\<\\.(\\d+)\\>\\s%\\d+\\.\\d+\\]\tWARNING\thello world"); ASSERT_TRUE(std::regex_search(content->value(), e)); } TEST(log, rotation) { std::vector types = { SW_LOG_ROTATION_DAILY, SW_LOG_ROTATION_EVERY_MINUTE, SW_LOG_ROTATION_HOURLY, SW_LOG_ROTATION_MONTHLY}; for (auto iter = types.begin(); iter != types.end(); iter++) { sw_logger()->reset(); sw_logger()->set_rotation(*iter); sw_logger()->open(file); sw_logger()->put(SW_LOG_DEBUG, SW_STRL("hello world")); sw_logger()->put(SW_LOG_TRACE, SW_STRL("hello world")); sw_logger()->put(SW_LOG_NOTICE, SW_STRL("hello world")); sw_logger()->put(SW_LOG_WARNING, SW_STRL("hello world")); sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world")); sw_logger()->put(SW_LOG_INFO, SW_STRL("hello world")); ASSERT_EQ(access(sw_logger()->get_file(), R_OK), -1); ASSERT_EQ(errno, ENOENT); ASSERT_EQ(access(sw_logger()->get_real_file(), R_OK), 0); sw_logger()->close(); unlink(sw_logger()->get_real_file()); } } TEST(log, redirect_1) { auto status = test::spawn_exec_and_wait([]() { sw_logger()->reset(); ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(true)); // no log file opened ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(false)); // no redirected ASSERT_TRUE(sw_logger()->open(file)); ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(true)); ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(true)); // has been redirected printf("hello world\n"); auto content = file_get_contents(file); ASSERT_NE(content.get(), nullptr); sw_logger()->close(); ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(false)); unlink(sw_logger()->get_real_file()); ASSERT_TRUE(content->contains(SW_STRL("hello world\n"))); }); ASSERT_EQ(status, 0); } TEST(log, redirect_2) { auto status = test::spawn_exec_and_wait([]() { auto file = TEST_LOG_FILE; auto str = "hello world, hello swoole\n"; sw_logger()->reset(); sw_logger()->open(file); sw_logger()->redirect_stdout_and_stderr(true); printf("%s\n", str); File f(file, File::READ); auto rs = f.read_content(); ASSERT_TRUE(rs->contains(str)); sw_logger()->redirect_stdout_and_stderr(false); printf("%s\n", str); sw_logger()->close(); unlink(sw_logger()->get_real_file()); }); ASSERT_EQ(status, 0); } namespace TestA { class TestPrettyName { public: static void fun(bool strip, const char *expect_str); }; void TestPrettyName::fun(bool strip, const char *expect_str) { ASSERT_STREQ(Logger::get_pretty_name(__PRETTY_FUNCTION__, strip).c_str(), expect_str); } static void test_pretty_name(bool strip, const char *expect_str) { ASSERT_STREQ(Logger::get_pretty_name(__PRETTY_FUNCTION__, strip).c_str(), expect_str); } static void test_pretty_name_lambda(bool strip, const char *expect_str) { auto fn = [](bool strip, const char *expect_str) { ASSERT_STREQ(Logger::get_pretty_name(__PRETTY_FUNCTION__, strip).c_str(), expect_str); }; fn(strip, expect_str); } } // namespace TestA static void test_pretty_name(bool strip, const char *expect_str) { ASSERT_STREQ(Logger::get_pretty_name(__PRETTY_FUNCTION__, strip).c_str(), expect_str); } static void test_pretty_name_lambda(bool strip, const char *expect_str) { auto fn = [](bool strip, const char *expect_str) { ASSERT_STREQ(Logger::get_pretty_name(__PRETTY_FUNCTION__, strip).c_str(), expect_str); }; fn(strip, expect_str); } TEST(log, pretty_name) { TestA::TestPrettyName::fun(false, "TestA::TestPrettyName::fun"); TestA::test_pretty_name(false, "TestA::test_pretty_name"); test_pretty_name(false, "test_pretty_name"); TestA::TestPrettyName::fun(true, "TestPrettyName::fun"); TestA::test_pretty_name(true, "test_pretty_name"); test_pretty_name(true, "test_pretty_name"); } TEST(log, pretty_name_lambda) { TestA::test_pretty_name_lambda(true, "test_pretty_name_lambda"); test_pretty_name_lambda(true, "test_pretty_name_lambda"); TestA::test_pretty_name_lambda(false, "TestA::test_pretty_name_lambda"); test_pretty_name_lambda(false, "test_pretty_name_lambda"); } TEST(log, ignore_error) { sw_logger()->reset(); sw_logger()->set_level(SW_LOG_NOTICE); sw_logger()->open(file); const int ignored_errcode = 999999; const int errcode = 888888; swoole_ignore_error(ignored_errcode); swoole_error_log(SW_LOG_WARNING, ignored_errcode, "error 1"); swoole_error_log(SW_LOG_WARNING, errcode, "error 2"); auto content = file_get_contents(file); sw_logger()->close(); unlink(file); ASSERT_FALSE(content->contains(SW_STRL("error 1"))); ASSERT_TRUE(content->contains(SW_STRL("error 2"))); } TEST(log, open_fail) { sw_logger()->reset(); sw_logger()->set_level(SW_LOG_NOTICE); sw_logger()->open("/tmp/not-exists/swoole.log"); sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world\n")); } TEST(log, set_stream) { sw_logger()->reset(); char *buffer = NULL; size_t size = 0; FILE *stream = open_memstream(&buffer, &size); sw_logger()->set_stream(stream); sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world")); sw_logger()->set_stream(stdout); sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world")); ASSERT_NE(strstr(buffer, "ERROR\thello world"), nullptr); fclose(stream); free(buffer); } ================================================ FILE: core-tests/src/core/string.cpp ================================================ #include "test_core.h" #include "swoole_util.h" using namespace std; using swoole::String; TEST(string, ltrim) { char buf[1024]; char *ptr_buf; strcpy(buf, " hello world"); ptr_buf = buf; swoole::ltrim(&ptr_buf, strlen(buf)); ASSERT_EQ(strcmp("hello world", ptr_buf), 0); ASSERT_NE(strcmp(" hello world", ptr_buf), 0); strcpy(buf, " "); ptr_buf = buf; swoole::ltrim(&ptr_buf, strlen(buf)); ASSERT_EQ(strlen(ptr_buf), 0); memcpy(buf, " a\0b\0", 6); ptr_buf = buf; swoole::ltrim(&ptr_buf, strlen(buf)); ASSERT_EQ(strcmp("a", ptr_buf), 0); buf[0] = '\0'; ptr_buf = buf; swoole::ltrim(&ptr_buf, strlen(buf)); ASSERT_EQ(strcmp("", ptr_buf), 0); } TEST(string, rtrim) { char buf[1024]; strcpy(buf, "hello world "); swoole::rtrim(buf, strlen(buf)); ASSERT_EQ(strcmp("hello world", buf), 0); ASSERT_NE(strcmp("hello world ", buf), 0); strcpy(buf, " "); swoole::rtrim(buf, strlen(buf)); ASSERT_EQ(strlen(buf), 0); buf[0] = '\0'; swoole::rtrim(buf, strlen(buf)); ASSERT_EQ(strcmp("", buf), 0); } TEST(string, move_and_copy) { String s1(TEST_STR); ASSERT_MEMEQ(s1.str, TEST_STR, s1.length); String s2(s1); ASSERT_MEMEQ(s2.str, TEST_STR, s2.length); ASSERT_NE(s1.str, nullptr); String s3(std::move(s1)); ASSERT_MEMEQ(s3.str, TEST_STR, s3.length); ASSERT_EQ(s1.str, nullptr); String s4; s4 = s3; ASSERT_MEMEQ(s4.str, TEST_STR, s4.length); ASSERT_NE(s3.str, nullptr); String s5; s5 = std::move(s3); ASSERT_MEMEQ(s5.str, TEST_STR, s5.length); ASSERT_EQ(s3.str, nullptr); String s6(SW_STRL(TEST_STR)); ASSERT_MEMEQ(s6.str, TEST_STR, s6.length); } TEST(string, append) { String s1(TEST_STR); s1.append(12345678); String s2(TEST_STR2); s1.append(s2); ASSERT_MEMEQ(s1.str, TEST_STR "12345678" TEST_STR2, s1.length); } TEST(string, write) { String s1; s1.reserve(32); String s2(TEST_STR); s1.repeat(" ", 1, 30); s1.write(30, s2); auto s3 = s1.substr(30, s2.length); ASSERT_MEMEQ(s3.str, TEST_STR, s3.length); } TEST(string, repeat) { auto end_str = "[end]"; String s1; s1.repeat(SW_STRL("hello\r\n"), 5); s1.append(end_str); int count = 0; auto offset = s1.split(SW_STRL("\r\n"), [&](const char *data, size_t length) -> bool { count++; EXPECT_MEMEQ(data, "hello\r\n", 7); return true; }); ASSERT_EQ(offset, s1.length - strlen(end_str)); ASSERT_MEMEQ(s1.str + offset, end_str, strlen(end_str)); ASSERT_EQ(count, 5); } TEST(string, release) { String s1(TEST_STR); ASSERT_MEMEQ(s1.str, TEST_STR, s1.length); auto s2 = s1.release(); ASSERT_EQ(s1.str, nullptr); ASSERT_EQ(s1.length, 0); ASSERT_MEMEQ(s2, TEST_STR, strlen(TEST_STR)); sw_free(s2); } TEST(string, ub) { String s1(TEST_STR); String s2(TEST_STR2); s1 = s2; s1 = s1; auto rs = s1.substr(s1.length, 10); ASSERT_EQ(rs.str, nullptr); } TEST(string, strnpos) { { string haystack = "hello world"; string needle = " "; int pos; pos = swoole_strnpos(haystack.c_str(), haystack.length(), needle.c_str(), needle.length()); ASSERT_EQ(pos, 5); } { string haystack = "hello world"; string needle = "*"; int pos; pos = swoole_strnpos(haystack.c_str(), haystack.length(), needle.c_str(), needle.length()); ASSERT_EQ(-1, pos); } } TEST(string, strnstr) { { string haystack = "hello world"; string needle = " "; const char *pos = swoole_strnstr(haystack.c_str(), haystack.length(), needle.c_str(), needle.length()); ASSERT_EQ(haystack.c_str() + 5, pos); } { string haystack = "hello world"; string needle = "*"; const char *pos = swoole_strnstr(haystack.c_str(), haystack.length(), needle.c_str(), needle.length()); ASSERT_EQ(NULL, pos); } { string haystack = "hello world\r\n"; string needle = "\r\n\r\n"; const char *pos = swoole_strnstr(haystack.c_str(), haystack.length(), needle.c_str(), needle.length()); ASSERT_EQ(NULL, pos); } } TEST(string, explode) { string haystack = "hello world"; string needle = " "; String str; str.str = (char *) haystack.c_str(); str.length = haystack.length(); int value_1 = 0; const char *explode_str = nullptr; size_t explode_length = 0; str.split(needle.c_str(), needle.length(), [&](const char *data, size_t length) -> int { explode_str = data; explode_length = length; value_1 = 5; return false; }); ASSERT_EQ(haystack, explode_str); ASSERT_EQ(6, explode_length); ASSERT_EQ(5, value_1); } TEST(string, explode_2) { string haystack = "hello,world,swoole,php,last"; string needle = ","; String str; str.str = (char *) haystack.c_str(); str.length = haystack.length(); int count = 0; vector list; size_t n = str.split(needle.c_str(), needle.length(), [&](const char *data, size_t length) -> int { list.push_back(string(data, length - 1)); count++; return true; }); ASSERT_EQ(list[0], string("hello")); ASSERT_EQ(list[1], string("world")); ASSERT_EQ(list[2], string("swoole")); ASSERT_EQ(list[3], string("php")); ASSERT_EQ("last", string(str.str + n, str.length - n)); ASSERT_EQ(4, count); ASSERT_EQ(list.size(), count); } static const int init_size = 1024; static string test_data = "hello,world,swoole,php,last"; TEST(string, pop_1) { auto str = swoole::make_string(init_size); std::unique_ptr s(str); char *str_1 = str->str; const int len_1 = 11; str->append(test_data.c_str(), test_data.length()); str->offset = len_1; char *str_2 = str->pop(init_size); EXPECT_EQ(str_1, str_2); EXPECT_EQ(string("hello,world"), string(str_2, len_1)); EXPECT_EQ(string(",swoole,php,last"), string(str->str, str->length)); EXPECT_EQ(init_size, str->size); str->allocator->free(str_1); } TEST(string, pop_2) { auto str = swoole::make_string(init_size); std::unique_ptr s(str); char *str_1 = str->str; const int len_1 = test_data.length(); str->append(test_data.c_str(), test_data.length()); str->offset = len_1; char *str_2 = str->pop(init_size); EXPECT_EQ(str_1, str_2); EXPECT_EQ(test_data, string(str_2, len_1)); EXPECT_EQ(str->length, 0); EXPECT_EQ(init_size, str->size); str->allocator->free(str_1); } TEST(string, reduce_1) { auto str = swoole::make_string(init_size); std::unique_ptr s(str); const int len_1 = 11; str->append(test_data.c_str(), test_data.length()); str->offset = len_1; str->reduce(str->offset); EXPECT_EQ(string(",swoole,php,last"), string(str->str, str->length)); } TEST(string, reduce_2) { auto str = swoole::make_string(init_size); std::unique_ptr s(str); str->append(test_data.c_str(), test_data.length()); str->offset = str->length; str->reduce(str->offset); EXPECT_EQ(str->length, 0); } TEST(string, reduce_3) { auto str = swoole::make_string(init_size); std::unique_ptr s(str); str->append(test_data.c_str(), test_data.length()); str->offset = 0; str->reduce(str->offset); EXPECT_EQ(str->length, test_data.length()); } const auto FORMAT_INT = 999999999999999; const auto FORMAT_STR = "hello world"; TEST(string, format_1) { String str1(1024); str1.append_random_bytes(1024, true); size_t n = str1.format("str=%s, value=%ld", FORMAT_STR, FORMAT_INT); std::string str2("str=hello world, value=999999999999999"); ASSERT_EQ(str1.get_length(), n); ASSERT_MEMEQ(str1.value(), str2.c_str(), n); } TEST(string, format_2) { String str1(1024); str1.append_random_bytes(1024, true); std::string str2(str1.value(), str1.get_length()); size_t n = str1.format_impl(String::FORMAT_APPEND, "str=%s, value=%ld", FORMAT_STR, FORMAT_INT); str2 += std::string(str1.value() + str2.length(), n); EXPECT_MEMEQ(str1.value(), str2.c_str(), str1.get_length()); } TEST(string, substr_len) { const char *str1 = "hello: swoole & world"; ASSERT_EQ(swoole::substr_len(str1, strlen(str1), ':', true), 5); ASSERT_EQ(swoole::substr_len(str1, strlen(str1), ':', false), 15); } TEST(string, starts_with) { const char *str1 = "hello world"; ASSERT_TRUE(swoole::starts_with(str1, strlen(str1), SW_STRL("hello"))); ASSERT_FALSE(swoole::starts_with(str1, strlen(str1), SW_STRL("php"))); ASSERT_TRUE(swoole::starts_with(str1, strlen(str1), str1, strlen(str1))); } TEST(string, ends_with) { const char *str1 = "hello world"; ASSERT_TRUE(swoole::ends_with(str1, strlen(str1), SW_STRL("world"))); ASSERT_FALSE(swoole::ends_with(str1, strlen(str1), SW_STRL("php"))); ASSERT_TRUE(swoole::ends_with(str1, strlen(str1), str1, strlen(str1))); } TEST(string, append_number) { string data = "hello"; auto str = swoole::make_string(data.length() + 32); str->append(data.c_str(), data.length()); str->append(123); str->set_null_terminated(); EXPECT_STREQ(str->str, data.append("123").c_str()); str->print(true); str->print(false); delete str; } ================================================ FILE: core-tests/src/core/time.cpp ================================================ #include "test_core.h" #include "swoole_util.h" TEST(time, get_ms) { const int us = 3000; long ms1 = swoole::time(); usleep(us); long ms2 = swoole::time(); EXPECT_GE(ms2 - ms1, us / 1000); } TEST(time, get_ms_steady) { const int us = 3000; long ms1 = swoole::time(true); usleep(us); long ms2 = swoole::time(true); EXPECT_GE(ms2 - ms1, us / 1000); } TEST(time, get_seconds) { long sec1 = swoole::time(); time_t sec2 = time(NULL); ASSERT_TRUE(sec1 == sec2 or sec1 == sec2 - 1); } TEST(time, get_timezone) { ASSERT_GE(swoole::get_timezone(), 0); } ================================================ FILE: core-tests/src/core/util.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_util.h" TEST(util, bitmap) { swoole::BitMap m(4096); m.set(199); m.set(1234); m.set(3048); ASSERT_EQ(m.get(199), true); ASSERT_EQ(m.get(1234), true); ASSERT_EQ(m.get(3048), true); ASSERT_EQ(m.get(2048), false); ASSERT_EQ(m.get(128), false); m.unset(1234); ASSERT_EQ(m.get(1234), false); m.clear(); } ================================================ FILE: core-tests/src/coroutine/accept.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" using namespace swoole::test; using swoole::Coroutine; TEST(coroutine_hook, accept) { coroutine::run([](void *arg) { // Create a TCP socket using coroutine API int server_sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); ASSERT_GT(server_sock, 0); // Bind the socket to localhost with port 0 (auto-assign) struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = 0; int retval = ::bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); ASSERT_EQ(retval, 0); // Listen on the socket retval = ::listen(server_sock, 128); ASSERT_EQ(retval, 0); struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); // Test that swoole_coroutine_accept works correctly Coroutine::create([&](void *arg) { // Give the server time to start listening usleep(10000); // Connect to the server using coroutine API int client_sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); ASSERT_GT(client_sock, 0); // Get the actual server port struct sockaddr_in actual_server_addr; socklen_t addr_len = sizeof(actual_server_addr); ASSERT_EQ(getsockname(server_sock, (struct sockaddr *) &actual_server_addr, &addr_len), 0); // Connect to the server retval = swoole_coroutine_connect(client_sock, (struct sockaddr *) &actual_server_addr, addr_len); ASSERT_EQ(retval, 0); // Send a test message const char *test_message = "test_data"; ssize_t sent_bytes = swoole_coroutine_send(client_sock, test_message, strlen(test_message), 0); ASSERT_EQ(sent_bytes, (ssize_t) strlen(test_message)); // Close the client socket swoole_coroutine_close(client_sock); }); // Accept the connection using coroutine API int client_sock = swoole_coroutine_accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len); ASSERT_GT(client_sock, 0); // Receive data from client char buffer[256] = {}; ssize_t received_bytes = swoole_coroutine_recv(client_sock, buffer, sizeof(buffer) - 1, 0); ASSERT_GT(received_bytes, 0); ASSERT_STREQ(buffer, "test_data"); // Close the client socket swoole_coroutine_close(client_sock); swoole_coroutine_close(server_sock); }); } ================================================ FILE: core-tests/src/coroutine/async.cpp ================================================ #include "test_coroutine.h" #include "swoole_async.h" #include #include using namespace std; using swoole::AsyncEvent; using swoole::test::coroutine; const int magic_code = 0x7009501; TEST(coroutine_async, usleep) { coroutine::run([](void *arg) { AsyncEvent ev = {}; bool retval = swoole::coroutine::async( [](AsyncEvent *event) { usleep(1000); event->retval = magic_code; }, ev); ASSERT_EQ(retval, true); ASSERT_EQ(ev.retval, magic_code); }); } TEST(coroutine_async, gethostbyname) { coroutine::run([](void *arg) { string domain("www.baidu.com"), ip; bool retval = swoole::coroutine::async([&]() { char buf[128]; if (swoole::network::gethostbyname(AF_INET, domain.c_str(), buf) == SW_OK) { char addr[128]; inet_ntop(AF_INET, buf, addr, sizeof(addr)); ip = addr; } else { ip = "unknown"; } }); ASSERT_EQ(retval, true); match_results result; try { const regex pattern("(\\d{1,3}).(\\d{1,3}).(\\d{1,3}).(\\d{1,3})"); ASSERT_EQ(regex_match(ip, result, pattern), true); } catch (std::exception &ex) { std::cerr << "regex error: gcc version must be 4.9+" << std::endl; } }); } TEST(coroutine_async, error) { coroutine::run([](void *arg) { int retval = 0x7009501; const char *test_file = "/tmp/swoole_core_test_file_not_exists"; swoole::coroutine::async([&](void) { retval = open(test_file, O_RDONLY); }); ASSERT_EQ(retval, -1); ASSERT_EQ(errno, ENOENT); }); } TEST(coroutine_async, cancel) { coroutine::run([](void *arg) { AsyncEvent ev = {}; auto co = swoole::Coroutine::get_current(); swoole_timer_after(50, [co](TIMER_PARAMS) { co->cancel(); }); bool retval = swoole::coroutine::async( [](AsyncEvent *event) { usleep(200000); event->retval = magic_code; }, ev); ASSERT_EQ(retval, false); ASSERT_EQ(ev.error, SW_ERROR_CO_CANCELED); DEBUG() << "done\n"; }); } TEST(coroutine_async, timeout) { coroutine::run([](void *arg) { AsyncEvent ev = {}; bool retval = swoole::coroutine::async( [](AsyncEvent *event) { usleep(200000); event->retval = magic_code; }, ev, 0.1); ASSERT_EQ(retval, false); ASSERT_EQ(ev.error, SW_ERROR_CO_TIMEDOUT); DEBUG() << "done\n"; }); } ================================================ FILE: core-tests/src/coroutine/base.cpp ================================================ #include "test_coroutine.h" using namespace swoole; using swoole::coroutine::System; TEST(coroutine_base, create) { long _cid; long cid = Coroutine::create([](void *arg) { *(long *) arg = Coroutine::get_current_cid(); }, &_cid); ASSERT_GT(cid, 0); ASSERT_EQ(cid, _cid); } TEST(coroutine_base, get_current) { long _cid; long cid = Coroutine::create( [](void *arg) { auto co = Coroutine::get_current(); *(long *) arg = co->get_cid(); }, &_cid); ASSERT_GT(cid, 0); ASSERT_EQ(cid, _cid); } TEST(coroutine_base, get_init_msec) { Coroutine::create([](void *arg) { auto co = Coroutine::get_current(); long init_msec = co->get_init_msec(); ASSERT_GT(init_msec, 0); }); } TEST(coroutine_base, yield_resume) { Coroutine::set_on_yield([](void *arg) { auto task = static_cast(Coroutine::get_current_task()); ASSERT_NE(task, nullptr); ASSERT_EQ(*task, Coroutine::get_current_cid()); }); Coroutine::set_on_resume([](void *arg) { Coroutine *current = Coroutine::get_current(); ASSERT_EQ(current, nullptr); }); Coroutine::set_on_close([](void *arg) { auto task = static_cast(Coroutine::get_current_task()); ASSERT_NE(task, nullptr); ASSERT_EQ(*task, Coroutine::get_current_cid()); }); long _cid, _cid2; long cid = Coroutine::create( [&_cid2](void *arg) { _cid2 = Coroutine::get_current_cid(); Coroutine *co = Coroutine::get_by_cid(_cid2); co->set_task(&_cid2); co->yield(); *static_cast(arg) = Coroutine::get_current_cid(); }, &_cid); ASSERT_GT(cid, 0); Coroutine::get_by_cid(cid)->resume(); Coroutine::set_on_close(nullptr); ASSERT_EQ(cid, _cid); } TEST(coroutine_base, get_cid) { Coroutine::create([](void *arg) { auto co = Coroutine::get_current(); long cid = co->get_cid(); ASSERT_GT(cid, 0); }); } TEST(coroutine_base, get_origin) { Coroutine::create([](void *arg) { auto *co = Coroutine::get_current(); Coroutine::create( [](void *arg) { auto current_co = Coroutine::get_current(); auto origin_co = current_co->get_origin(); ASSERT_EQ(arg, origin_co); }, co); }); } TEST(coroutine_base, get_origin_cid) { Coroutine::create([](void *arg) { auto _cid = Coroutine::get_current_cid(); Coroutine::create( [](void *arg) { auto origin_cid = Coroutine::get_current()->get_origin_cid(); ASSERT_EQ(*(long *) arg, origin_cid); }, &_cid); }); } TEST(coroutine_base, is_end) { Coroutine::create([](void *_arg) { auto co = Coroutine::get_current(); ASSERT_FALSE(co->is_end()); }); } TEST(coroutine_base, set_task) { Coroutine::create([](void *_arg) { int task; auto co = Coroutine::get_current(); co->set_task(&task); void *actual = co->get_task(); ASSERT_EQ(actual, &task); }); } TEST(coroutine_base, get_current_task) { Coroutine::create([](void *_arg) { int task; auto co = Coroutine::get_current(); co->set_task(&task); void *actual = co->get_task(); ASSERT_EQ(actual, Coroutine::get_current_task()); }); } TEST(coroutine_base, get_current_cid) { Coroutine::create([](void *_arg) { auto co = Coroutine::get_current(); auto actual = co->get_cid(); ASSERT_EQ(actual, Coroutine::get_current_cid()); ASSERT_EQ(actual, swoole_coroutine_get_id()); }); } TEST(coroutine_base, get_by_cid) { Coroutine::create([](void *_arg) { auto actual = Coroutine::get_current(); auto cid = actual->get_cid(); ASSERT_EQ(actual, Coroutine::get_by_cid(cid)); }); } TEST(coroutine_base, get_task_by_cid) { Coroutine::create([](void *_arg) { int task; auto co = Coroutine::get_current(); co->set_task(&task); auto actual = co->get_task(); ASSERT_EQ(actual, Coroutine::get_task_by_cid(co->get_cid())); }); } TEST(coroutine_base, get_last_cid) { Coroutine::create([](void *_arg) {}); Coroutine::create([](void *_arg) {}); long cid = Coroutine::create([](void *_arg) {}); ASSERT_EQ(Coroutine::get_last_cid(), cid); } TEST(coroutine_base, count) { Coroutine::create([](void *_arg) { ASSERT_EQ(Coroutine::count(), 1); Coroutine::create([](void *_arg) { ASSERT_EQ(Coroutine::count(), 2); }); }); ASSERT_EQ(Coroutine::count(), 0); } TEST(coroutine_base, get_peak_num) { Coroutine::create( [](void *_arg) { Coroutine::create([](void *_arg) { ASSERT_GE(Coroutine::get_peak_num(), 2); }); }); } TEST(coroutine_base, get_elapsed) { long elapsed_time = 0; Coroutine::create( [](void *arg) { auto co = Coroutine::get_current(); usleep(2000); *(long *) arg = Coroutine::get_elapsed(co->get_cid()); }, &elapsed_time); ASSERT_GE(elapsed_time, 2); } TEST(coroutine_base, run) { long cid = coroutine::run([](void *ptr) { }); ASSERT_GE(cid, 1); } TEST(coroutine_base, cancel) { coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); Coroutine::create([co](void *) { System::sleep(0.002); co->cancel(); }); ASSERT_EQ(co->yield_ex(-1), false); ASSERT_EQ(co->is_canceled(), true); }); } TEST(coroutine_base, noncancelable) { std::unordered_map flags; coroutine::run([&flags](void *arg) { auto cid = Coroutine::create([&flags](void *_arg) { Coroutine *current = Coroutine::get_current(); flags["yield_1"] = true; current->yield(); ASSERT_FALSE(current->is_canceled()); flags["yield_2"] = true; current->yield_ex(-1); ASSERT_TRUE(current->is_canceled()); }); auto co = Coroutine::get_by_cid(cid); flags["cancel_1"] = true; ASSERT_FALSE(co->cancel()); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_CO_CANNOT_CANCEL); flags["resume_1"] = true; co->resume(); flags["cancel_2"] = true; ASSERT_TRUE(co->cancel()); flags["resume_2"] = true; flags["done"] = true; }); ASSERT_TRUE(flags["yield_1"]); ASSERT_TRUE(flags["yield_2"]); ASSERT_TRUE(flags["cancel_1"]); ASSERT_TRUE(flags["resume_1"]); ASSERT_TRUE(flags["cancel_2"]); ASSERT_TRUE(flags["resume_2"]); ASSERT_TRUE(flags["done"]); } TEST(coroutine_base, timeout) { coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); ASSERT_EQ(co->yield_ex(0.005), false); ASSERT_EQ(co->is_timedout(), true); }); } TEST(coroutine_base, gdb) { Coroutine::create([](void *) { Coroutine *current = Coroutine::get_current(); long cid = current->get_cid(); ASSERT_EQ(swoole_coroutine_count(), 1); ASSERT_EQ(swoole_coroutine_get(cid), current); ASSERT_EQ(swoole_coroutine_get(999999), nullptr); swoole_coroutine_iterator_reset(); ASSERT_EQ(swoole_coroutine_iterator_each(), current); ASSERT_EQ(swoole_coroutine_iterator_each(), nullptr); swoole_coroutine_iterator_reset(); ASSERT_EQ(swoole_coroutine_iterator_each(), current); Coroutine::print_list(); }); } TEST(coroutine_base, bailout) { int status; status = test::spawn_exec_and_wait([]() { std::unordered_map flags; coroutine::run([&flags](void *arg) { Coroutine::create([&flags](void *_arg) { Coroutine *current = Coroutine::get_current(); current->bailout([&flags]() { flags["exit"] = true; }); flags["end"] = true; }); }); ASSERT_TRUE(flags["exit"]); ASSERT_FALSE(flags["end"]); }); ASSERT_EQ(status, 0); status = test::spawn_exec_and_wait([]() { std::unordered_map flags; coroutine::run([&flags](void *arg) { Coroutine *current = Coroutine::get_current(); current->bailout(nullptr); flags["end"] = true; }); ASSERT_TRUE(flags["exit"]); ASSERT_FALSE(flags["end"]); }); ASSERT_EQ(WEXITSTATUS(status), 1); status = test::spawn_exec_and_wait([]() { std::unordered_map flags; coroutine::run([&flags](void *arg) { Coroutine *current = Coroutine::get_current(); swoole_event_defer( [current, &flags](void *args) { flags["bailout"] = true; current->bailout(nullptr); flags["end"] = true; }, nullptr); flags["exit"] = true; }); ASSERT_TRUE(flags["exit"]); ASSERT_TRUE(flags["end"]); }); ASSERT_EQ(WEXITSTATUS(status), 0); } TEST(coroutine_base, undefined_behavior) { int status; status = test::spawn_exec_and_wait([]() { test::coroutine::run([](void *) { swoole_fork(0); }); }); ASSERT_EQ(1, WEXITSTATUS(status)); status = test::spawn_exec_and_wait([]() { std::atomic handle_count(0); AsyncEvent event = {}; event.object = &handle_count; event.callback = [](AsyncEvent *event) {}; event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; swoole_event_init(0); auto ret = async::dispatch(&event); ASSERT_NE(ret, nullptr); swoole_fork(0); }); ASSERT_EQ(1, WEXITSTATUS(status)); ASSERT_EQ(0, swoole_fork(SW_FORK_PRECHECK)); } TEST(coroutine_base, c_api) { int code = 0x9501; auto c1 = swoole_coroutine_create( [](void *_arg) { ASSERT_EQ(*(int *) _arg, 0x9501); swoole_coroutine_create([](void *_arg) { swoole_coroutine_usleep(100000); }, nullptr); }, &code); ASSERT_GE(c1, 1); } ================================================ FILE: core-tests/src/coroutine/channel.cpp ================================================ #include "test_coroutine.h" using swoole::Coroutine; using swoole::coroutine::Channel; using namespace std; using namespace swoole::test; TEST(coroutine_channel, push_pop) { coroutine::run([](void *arg) { Channel chan(1); int i = 1; bool ret; ret = chan.push(&i); ASSERT_TRUE(ret); ASSERT_EQ(*(int *) chan.pop(), i); }); } TEST(coroutine_channel, push_yield) { Channel chan(1); coroutine::run({make_pair( [](void *arg) { auto chan = (Channel *) arg; int i = 1; bool ret; ret = chan->push(new int(i)); ASSERT_TRUE(ret); ret = chan->push(new int(i)); ASSERT_TRUE(ret); }, &chan), make_pair( [](void *arg) { auto chan = (Channel *) arg; ASSERT_EQ(*(int *) chan->pop(), 1); ASSERT_EQ(*(int *) chan->pop(), 1); }, &chan)}); } TEST(coroutine_channel, pop_yield) { Channel chan(1); coroutine::run({make_pair( [](void *arg) { auto chan = (Channel *) arg; ASSERT_EQ(*(int *) chan->pop(), 1); ASSERT_EQ(*(int *) chan->pop(), 1); }, &chan), make_pair( [](void *arg) { auto chan = (Channel *) arg; int i = 1; bool ret; ret = chan->push(&i); ASSERT_TRUE(ret); ret = chan->push(&i); ASSERT_TRUE(ret); }, &chan)}); } TEST(coroutine_channel, push_timeout) { coroutine::run([](void *arg) { Channel chan(1); bool ret; ret = chan.push(nullptr, 0.001); ASSERT_TRUE(ret); ret = chan.push(nullptr, 0.001); ASSERT_FALSE(ret); }); } TEST(coroutine_channel, pop_timeout) { coroutine::run([](void *arg) { Channel chan(1); void *ret; ret = chan.pop(0.001); ASSERT_EQ(ret, nullptr); }); } TEST(coroutine_channel, close) { Channel chan(1); coroutine::run( [](void *arg) { int value = 1; auto chan = (Channel *) arg; while (1) { if (!chan->push((void *) &value)) { ASSERT_EQ(chan->get_error(), Channel::ErrorCode::ERROR_CLOSED); ASSERT_FALSE(chan->push(nullptr)); break; } } }, &chan); ASSERT_TRUE(chan.close()); ASSERT_FALSE(chan.close()); Channel chan2(1); coroutine::run( [](void *arg) { auto chan = (Channel *) arg; while (1) { if (!chan->pop(0)) { ASSERT_EQ(chan->get_error(), Channel::ErrorCode::ERROR_CLOSED); ASSERT_EQ(chan->pop(), nullptr); break; } } }, &chan2); ASSERT_TRUE(chan2.close()); } TEST(coroutine_channel, cancel) { Channel chan(1); coroutine::run( [](void *arg) { auto chan = (Channel *) arg; auto cid = Coroutine::create([chan](void *args) { ASSERT_EQ(chan->pop(), nullptr); ASSERT_EQ(chan->get_error(), Channel::ERROR_CANCELED); }); auto co = Coroutine::get_by_cid(cid); ASSERT_TRUE(co->cancel()); ASSERT_TRUE(chan->close()); ASSERT_EQ(chan->pop(), nullptr); ASSERT_EQ(chan->get_error(), Channel::ERROR_CLOSED); }, &chan); } ================================================ FILE: core-tests/src/coroutine/file.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include #include #include "test_coroutine.h" #include "swoole_file.h" using namespace std; using namespace swoole; using swoole::coroutine::System; TEST(coroutine_async_file, async_file) { coroutine::run([](void *arg) { string filename = "/tmp/file.txt"; auto file = new AsyncFile(filename, O_CREAT | O_RDWR, 0666); ON_SCOPE_EXIT { delete file; }; ASSERT_EQ(file->ready(), true); ASSERT_EQ(file->truncate(0), true); ASSERT_EQ(file->set_offset(0), 0); ASSERT_EQ(file->get_offset(), 0); const char *data = "Hello World!"; size_t length = strlen(data); ASSERT_EQ(file->write((void *) data, length), static_cast(length)); ASSERT_EQ(file->sync(), true); ASSERT_EQ(file->set_offset(0), 0); char buf[1024]; ASSERT_EQ(file->read((void *) buf, 1024), static_cast(length)); buf[length] = '\0'; ASSERT_STREQ(data, buf); struct stat statbuf {}; ASSERT_EQ(file->stat(&statbuf), true); ASSERT_TRUE(statbuf.st_size > 0); }); } ================================================ FILE: core-tests/src/coroutine/gethostbyname.cpp ================================================ #include "test_coroutine.h" using swoole::Coroutine; using swoole::Timer; using swoole::coroutine::System; using swoole::test::coroutine; TEST(coroutine_gethostbyname, resolve_cache) { coroutine::run([](void *arg) { System::set_dns_cache_capacity(10); std::string addr1 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_NE(addr1, ""); for (int i = 0; i < 100; ++i) { std::string addr2 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_EQ(addr1, addr2); } ASSERT_GT(System::get_dns_cache_hit_ratio(), 0.99); System::set_dns_cache_capacity(0); for (int i = 0; i < 5; ++i) { std::string addr2 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_NE(addr2, ""); } ASSERT_LT(System::get_dns_cache_hit_ratio(), 0.01); }); } TEST(coroutine_gethostbyname, impl_async) { coroutine::run([](void *arg) { auto result = swoole::coroutine::gethostbyname_impl_with_async(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_EQ(result.empty(), false); }); } TEST(coroutine_gethostbyname, resolve_cache_inet4_and_inet6) { coroutine::run([](void *arg) { System::set_dns_cache_capacity(10); std::string addr1 = System::gethostbyname("ipv6.sjtu.edu.cn", AF_INET); std::string addr2 = System::gethostbyname("ipv6.sjtu.edu.cn", AF_INET6); ASSERT_NE(addr1, ""); ASSERT_NE(addr2, ""); ASSERT_EQ(addr1.find(":"), addr1.npos); ASSERT_NE(addr2.find(":"), addr2.npos); int64_t start = Timer::get_absolute_msec(); for (int i = 0; i < 100; ++i) { std::string addr3 = System::gethostbyname("ipv6.sjtu.edu.cn", AF_INET); std::string addr4 = System::gethostbyname("ipv6.sjtu.edu.cn", AF_INET6); ASSERT_EQ(addr1, addr3); ASSERT_EQ(addr2, addr4); } ASSERT_LT(Timer::get_absolute_msec() - start, 5); }); } TEST(coroutine_gethostbyname, dns_expire) { coroutine::run([](void *arg) { System::set_dns_cache_expire(1); System::clear_dns_cache(); System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); ASSERT_GE(System::get_dns_cache_hit_ratio(), 0.5); sleep(2); System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); ASSERT_LT(System::get_dns_cache_hit_ratio(), 0.35); System::clear_dns_cache(); System::set_dns_cache_expire(60); }); } ================================================ FILE: core-tests/src/coroutine/hook.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_file.h" #include "swoole_util.h" using namespace swoole::test; using swoole::Coroutine; using swoole::File; using swoole::String; using swoole::coroutine::System; using swoole::test::coroutine; const char *host_1 = "www.baidu.com"; const char *host_2 = "www.xxxxxxxxxxxxxxxxxxxxx00000xxxxxxxxx----not_found.com"; static const char *test_file = "/tmp/swoole-core-test"; void static test_file_hook() { char buf[8192]; size_t n_buf = sizeof(buf); ASSERT_EQ(swoole_random_bytes(buf, n_buf), n_buf); int fd = swoole_coroutine_open(test_file, O_WRONLY | O_TRUNC | O_CREAT, 0666); ASSERT_EQ(swoole_coroutine_write(fd, buf, n_buf), n_buf); ASSERT_EQ(swoole_coroutine_fsync(fd), 0); ASSERT_EQ(swoole_coroutine_fdatasync(fd), 0); swoole_coroutine_close(fd); fd = swoole_coroutine_open(test_file, O_RDONLY, 0); char data[8192]; ASSERT_EQ(swoole_coroutine_read(fd, data, n_buf), n_buf); ASSERT_EQ(std::string(buf, n_buf), std::string(data, n_buf)); swoole_coroutine_close(fd); struct stat statbuf; swoole_coroutine_stat(test_file, &statbuf); File f1(test_file, File::RW); ASSERT_EQ(statbuf.st_size, f1.get_size()); struct stat statbuf2; swoole_coroutine_lstat(test_file, &statbuf2); ASSERT_EQ(statbuf2.st_size, f1.get_size()); f1.close(); ASSERT_EQ(swoole_coroutine_unlink(test_file), 0); File f2(test_file, File::RW | File::CREATE); swoole_coroutine_lseek(f2.get_fd(), 0, SEEK_SET); auto fp2 = swoole_coroutine_fdopen(f2.get_fd(), "w+"); ASSERT_NE(fp2, nullptr); swoole_coroutine_fputs("hello\n", fp2); swoole_coroutine_fputs("world\n", fp2); ASSERT_EQ(swoole_coroutine_fflush(fp2), 0); swoole_coroutine_lseek(f2.get_fd(), 0, SEEK_SET); auto fp3 = swoole_coroutine_fdopen(f2.get_fd(), "r+"); ASSERT_NE(fp3, nullptr); char rbuf[2048] = {}; ASSERT_EQ(swoole_coroutine_fread(rbuf, sizeof(rbuf), 1, fp3), 0); ASSERT_STREQ(rbuf, "hello\nworld\n"); f2.close(); swoole_coroutine_fclose(fp2); swoole_coroutine_fclose(fp3); ASSERT_EQ(swoole_coroutine_unlink(test_file), 0); } TEST(coroutine_hook, file) { coroutine::run([](void *arg) { test_file_hook(); }); test_file_hook(); } TEST(coroutine_hook, gethostbyname) { coroutine::run([](void *arg) { auto result1 = swoole_coroutine_gethostbyname(host_1); ASSERT_NE(result1, nullptr); auto result2 = swoole_coroutine_gethostbyname(host_2); ASSERT_EQ(result2, nullptr); ASSERT_EQ(h_errno, HOST_NOT_FOUND); }); } TEST(coroutine_hook, getaddrinfo) { coroutine::run([](void *arg) { struct addrinfo hints; sw_memset_zero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; struct addrinfo *result, *curr; int count; result = nullptr; auto result1 = swoole_coroutine_getaddrinfo(host_1, "http", &hints, &result); ASSERT_EQ(result1, 0); curr = result; count = 0; while (curr && curr->ai_addr) { curr = curr->ai_next; count++; } ASSERT_GE(count, 1); freeaddrinfo(result); result = nullptr; auto result2 = swoole_coroutine_getaddrinfo(host_2, nullptr, &hints, &result); ASSERT_EQ(result2, EAI_NONAME); ASSERT_EQ(result, nullptr); freeaddrinfo(result); }); } static void test_fstat() { int fd = swoole_coroutine_open(TEST_TMP_FILE, O_RDONLY, 0); struct stat statbuf_1; swoole_coroutine_fstat(fd, &statbuf_1); struct stat statbuf_2; fstat(fd, &statbuf_2); ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); swoole_coroutine_close(fd); } TEST(coroutine_hook, fstat) { coroutine::run([](void *arg) { test_fstat(); }); test_fstat(); } static void test_statvfs() { struct statvfs statbuf_1; swoole_coroutine_statvfs("/tmp", &statbuf_1); struct statvfs statbuf_2; statvfs("/tmp", &statbuf_2); ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); } TEST(coroutine_hook, statvfs) { coroutine::run([](void *arg) { test_statvfs(); }); test_statvfs(); } static void test_hook_dir() { ASSERT_EQ(swoole_coroutine_mkdir(TEST_TMP_DIR, 0666), 0); ASSERT_EQ(swoole_coroutine_access(TEST_TMP_DIR, R_OK), 0); ASSERT_EQ(swoole_coroutine_rmdir(TEST_TMP_DIR), 0); ASSERT_EQ(access(TEST_TMP_DIR, R_OK), -1); } TEST(coroutine_hook, dir) { coroutine::run([](void *arg) { test_hook_dir(); }); test_hook_dir(); } static void test_hook_socket() { int sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); ASSERT_GT(sock, 0); swoole::network::Address sa; std::string ip = System::gethostbyname("www.baidu.com", AF_INET, 10); sa.assign(SW_SOCK_TCP, ip, 80); ASSERT_EQ(swoole_coroutine_connect(sock, &sa.addr.ss, sa.len), 0); ASSERT_EQ(swoole_coroutine_socket_wait_event(sock, SW_EVENT_WRITE, 5), SW_OK); const char req[] = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nKeepAlive: off\r\n\r\n"; ASSERT_EQ(swoole_coroutine_send(sock, req, strlen(req), 0), strlen(req)); swoole::String resp(1024); while (1) { ssize_t n = swoole_coroutine_recv(sock, resp.value() + resp.length, resp.size - resp.length, 0); if (n <= 0) { break; } resp.length += n; if (resp.length == resp.size) { resp.reserve(resp.size * 2); } } ASSERT_GT(resp.length, 100); ASSERT_TRUE(resp.contains("baidu.com")); swoole_coroutine_close(sock); } TEST(coroutine_hook, socket) { coroutine::run([](void *arg) { test_hook_socket(); }); test_hook_socket(); } TEST(coroutine_hook, rename) { coroutine::run([](void *arg) { char buf[8192]; size_t n_buf = sizeof(buf); ASSERT_EQ(swoole_random_bytes(buf, n_buf), n_buf); int fd = swoole_coroutine_open(test_file, O_WRONLY | O_TRUNC | O_CREAT, 0666); ASSERT_EQ(swoole_coroutine_write(fd, buf, n_buf), n_buf); swoole_coroutine_close(fd); std::string to_file_name = std::string(test_file) + ".bak"; ASSERT_EQ(swoole_coroutine_rename(test_file, to_file_name.c_str()), 0); ASSERT_EQ(access(TEST_TMP_DIR, F_OK), -1); ASSERT_EQ(access(to_file_name.c_str(), F_OK), 0); swoole_coroutine_unlink(to_file_name.c_str()); }); } TEST(coroutine_hook, flock) { long start_time = swoole::time(); coroutine::run([&](void *arg) { Coroutine::create([&](void *arg) { int fd = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); ASSERT_EQ(swoole_coroutine_flock(fd, 16), SW_ERR); ASSERT_ERREQ(EINVAL); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX), 0); System::sleep(0.1); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_SH), 0); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); ASSERT_LE(swoole::time() - start_time, 1000); swoole_coroutine_close(fd); }); Coroutine::create([&](void *arg) { int fd = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_SH), 0); System::sleep(0.5); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); swoole_coroutine_close(fd); }); }); // LOCK_NB coroutine::run([](void *arg) { int fd1 = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); ASSERT_EQ(swoole_coroutine_flock(fd1, LOCK_EX), 0); int fd2 = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); ASSERT_EQ(swoole_coroutine_flock(fd2, LOCK_EX | LOCK_NB), -1); ASSERT_EQ(swoole_coroutine_flock(fd1, LOCK_UN), 0); swoole_coroutine_close(fd1); swoole_coroutine_close(fd2); }); } TEST(coroutine_hook, read_dir) { auto fp = opendir("/tmp"); std::string dir1(readdir(fp)->d_name); std::string dir2(readdir(fp)->d_name); closedir(fp); auto fn = [&]() { auto fp = swoole_coroutine_opendir("/tmp"); ASSERT_NE(fp, nullptr); struct dirent *entry; entry = swoole_coroutine_readdir(fp); ASSERT_NE(entry, nullptr); ASSERT_STREQ(entry->d_name, dir1.c_str()); entry = swoole_coroutine_readdir(fp); ASSERT_NE(entry, nullptr); ASSERT_STREQ(entry->d_name, dir2.c_str()); swoole_coroutine_closedir(fp); }; coroutine::run([&](void *arg) { fn(); }); fn(); } TEST(coroutine_hook, readlink) { auto fn = []() { char buf1[1024] = {}; char buf2[1024] = {}; auto retval = swoole_coroutine_readlink("/proc/self/cwd", buf1, sizeof(buf1)); ASSERT_NE(retval, -1); getcwd(buf2, sizeof(buf2)); ASSERT_STREQ(buf1, buf2); }; coroutine::run([&](void *arg) { fn(); }); fn(); } TEST(coroutine_hook, stdio_1) { auto fn = []() { FILE *fp1 = swoole_coroutine_fopen(test_file, "w+"); const char *str = "hello world"; int n = swoole_coroutine_fputs(str, fp1); ASSERT_TRUE(n); swoole_coroutine_fclose(fp1); FILE *fp2 = swoole_coroutine_fopen(test_file, "r+"); char buf[1024]; char *str2 = swoole_coroutine_fgets(buf, sizeof(buf), fp2); ASSERT_STREQ(str2, str); swoole_coroutine_fclose(fp2); unlink(test_file); }; coroutine::run([&](void *arg) { fn(); }); fn(); } TEST(coroutine_hook, stdio_2) { auto fn = []() { size_t size = 1024; FILE *fp1 = swoole_coroutine_fopen(test_file, "w+"); String str(size); str.append_random_bytes(size); size_t n = swoole_coroutine_fwrite(str.str, 1, size, fp1); ASSERT_EQ(n, size); swoole_coroutine_fclose(fp1); FILE *fp2 = swoole_coroutine_fopen(test_file, "r+"); char buf[size]; size_t len = swoole_coroutine_fread(buf, 1, size, fp2); ASSERT_EQ(len, size); len = swoole_coroutine_fread(buf, 1, size, fp2); ASSERT_EQ(len, 0); ASSERT_TRUE(swoole_coroutine_feof(fp2)); ASSERT_MEMEQ(buf, str.str, size); swoole_coroutine_fclose(fp2); unlink(test_file); }; coroutine::run([&](void *arg) { fn(); }); fn(); } TEST(coroutine_hook, sleep) { coroutine::run([&](void *arg) { { long ms1 = swoole::time(); swoole_coroutine_sleep(2); long ms2 = swoole::time(); ASSERT_GE(ms2 - ms1, 1900); } { long us_1 = swoole::time(); swoole_coroutine_usleep(50000); long us_2 = swoole::time(); ASSERT_GE(us_2 - us_1, 45000); } }); } TEST(coroutine_hook, exists) { coroutine::run([&](void *arg) { const int fd = 100; // fake fd ASSERT_EQ(swoole_coroutine_socket_create(fd), 0); ASSERT_TRUE(swoole_coroutine_socket_exists(fd)); auto sock = swoole_coroutine_get_socket_object(fd); ASSERT_EQ(sock->get_fd(), fd); swoole_coroutine_close(fd); }); } TEST(coroutine_hook, timeout) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; size_t length = text.length(); // unregister fd errno = 0; ASSERT_EQ(swoole_coroutine_socket_set_timeout(pairs[0], SO_SNDTIMEO, 0.05), -1); ASSERT_EQ(errno, EINVAL); errno = 0; ASSERT_EQ(swoole_coroutine_socket_set_connect_timeout(pairs[0], 0.05), -1); ASSERT_EQ(errno, EINVAL); swoole::Coroutine::create([&](void *) { ASSERT_EQ(swoole_coroutine_socket_create(pairs[0]), 0); // unknown which ASSERT_EQ(swoole_coroutine_socket_set_timeout(pairs[0], 100, 0.05), -1); swoole_coroutine_socket_set_timeout(pairs[0], SO_SNDTIMEO, 0.05); size_t result = swoole_coroutine_write(pairs[0], text.c_str(), length); ASSERT_EQ(swoole_coroutine_close(pairs[0]), 0); ASSERT_EQ(result, length); }); char data[length + 1]; ASSERT_EQ(swoole_coroutine_socket_create(pairs[1]), 0); swoole_coroutine_socket_set_timeout(pairs[1], SO_RCVTIMEO, 0.05); size_t result = swoole_coroutine_read(pairs[1], data, length); data[result] = '\0'; ASSERT_EQ(swoole_coroutine_close(pairs[1]), 0); ASSERT_EQ(result, length); ASSERT_STREQ(data, text.c_str()); }); } TEST(coroutine_hook, sendmsg_and_recvmsg) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; size_t length = text.length(); swoole::Coroutine::create([&](void *) { struct msghdr msg; struct iovec ivec; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = (void *) text.c_str(); ivec.iov_len = length; ssize_t ret = swoole_coroutine_sendmsg(pairs[0], &msg, 0); ASSERT_EQ(swoole_coroutine_close(pairs[0]), 0); ASSERT_EQ(ret, length); }); struct msghdr msg; struct iovec ivec; char buf[length + 1]; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = buf; ivec.iov_len = length; ssize_t ret = swoole_coroutine_recvmsg(pairs[1], &msg, 0); buf[ret] = '\0'; ASSERT_EQ(swoole_coroutine_close(pairs[1]), 0); ASSERT_STREQ(buf, text.c_str()); }); } TEST(coroutine_hook, lseek) { std::string file = get_jpg_file(); int fd = swoole_coroutine_open(file.c_str(), O_RDONLY, 'r'); off_t offset = swoole_coroutine_lseek(fd, 0, SEEK_SET); swoole_coroutine_close(fd); ASSERT_EQ(offset, 0); } TEST(coroutine_hook, socket_close) { coroutine::run([&](void *arg) { auto pair = create_socket_pair(); auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); std::map results; auto _sock = pair.first; auto _fd = _sock->move_fd(); swoole_coroutine_socket_create(_fd); // write co Coroutine::create([&](void *) { SW_LOOP_N(32) { ssize_t result = swoole_coroutine_write(_fd, buffer->value(), buffer->get_length()); if (result < 0 && errno == ECANCELED) { ASSERT_EQ(swoole_coroutine_close(_fd), -1); ASSERT_EQ(errno, SW_ERROR_CO_SOCKET_CLOSE_WAIT); results["write"] = true; break; } } }); // read co Coroutine::create([&](void *) { SW_LOOP_N(32) { char buf[4096]; ssize_t result = swoole_coroutine_read(_fd, buf, sizeof(buf)); if (result < 0 && errno == ECANCELED) { ASSERT_EQ(swoole_coroutine_close(_fd), 0); results["read"] = true; break; } } }); System::sleep(0.1); ASSERT_EQ(swoole_coroutine_close(_fd), -1); ASSERT_EQ(errno, SW_ERROR_CO_SOCKET_CLOSE_WAIT); ASSERT_TRUE(results["write"]); ASSERT_TRUE(results["read"]); }); } TEST(coroutine_hook, poll) { coroutine::run([&](void *arg) { auto pair = create_socket_pair(); auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); std::map results; auto _sock0 = pair.first; auto _fd0 = _sock0->move_fd(); swoole_coroutine_socket_create(_fd0); auto _sock1 = pair.second; auto _fd1 = _sock1->move_fd(); swoole_coroutine_socket_create(_fd1); Coroutine::create([&](void *) { ssize_t result; result = swoole_coroutine_write(_fd0, buffer->value(), buffer->get_length()); ASSERT_GT(result, 0); System::sleep(0.01); result = swoole_coroutine_write(_fd1, buffer->value(), 16 * 1024); ASSERT_GT(result, 0); }); struct pollfd fds[2]; char buf[4096]; bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN; fds[1].fd = _fd1; fds[1].events = POLLIN; ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 1); ASSERT_TRUE(fds[1].revents & POLLIN); ssize_t result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); ASSERT_GT(result, 1024); System::sleep(0.02); bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN; fds[1].fd = _fd1; fds[1].events = POLLIN; ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 2); ASSERT_TRUE(fds[0].revents & POLLIN); ASSERT_TRUE(fds[1].revents & POLLIN); result = swoole_coroutine_read(_fd0, buf, sizeof(buf)); ASSERT_GT(result, 1024); result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); ASSERT_GT(result, 1024); System::sleep(0.02); bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN | POLLOUT; fds[1].fd = _fd1; fds[1].events = POLLIN | POLLOUT; ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 2); ASSERT_TRUE(fds[0].revents & POLLIN); ASSERT_TRUE(fds[1].revents & POLLIN); ASSERT_FALSE(fds[0].revents & POLLOUT); // not writable ASSERT_TRUE(fds[1].revents & POLLOUT); result = swoole_coroutine_read(_fd0, buf, sizeof(buf)); ASSERT_GT(result, 1024); result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); ASSERT_GT(result, 1024); }); } TEST(coroutine_hook, poll_fake) { coroutine::run([&](void *arg) { auto pair = create_socket_pair(); auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); std::map results; auto _sock0 = pair.first; auto _fd0 = _sock0->move_fd(); swoole_coroutine_socket_create(_fd0); auto _sock1 = pair.second; auto _fd1 = _sock1->move_fd(); swoole_coroutine_socket_create(_fd1); Coroutine::create([&](void *) { ssize_t result; result = swoole_coroutine_write(_fd0, buffer->value(), buffer->get_length()); ASSERT_GT(result, 0); System::sleep(0.01); result = swoole_coroutine_write(_fd1, buffer->value(), 16 * 1024); ASSERT_GT(result, 0); }); struct pollfd fds[2]; char buf[4096]; bzero(fds, sizeof(pollfd)); fds[0].fd = _fd1; fds[0].events = POLLIN; ASSERT_EQ(swoole_coroutine_poll_fake(fds, 1, 1000), 1); ASSERT_TRUE(fds[0].revents & POLLIN); ssize_t result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); ASSERT_GT(result, 1024); bzero(fds, sizeof(pollfd)); ASSERT_EQ(swoole_coroutine_poll_fake(fds, 2, 1000), -1); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); System::sleep(0.02); bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN | POLLOUT; ASSERT_EQ(swoole_coroutine_poll_fake(fds, 1, 1000), 1); ASSERT_TRUE(fds[0].revents & POLLIN); ASSERT_TRUE(fds[0].revents & POLLOUT); }); } TEST(coroutine_hook, unwrap) { auto pair = create_socket_pair(); auto _sock0 = pair.first; auto _sock1 = pair.second; ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), -1); coroutine::run([&](void *arg) { ASSERT_EQ(swoole_coroutine_socket_unwrap(999999), -1); ASSERT_EQ(swoole_coroutine_socket_create(_sock0->get_fd()), 0); ASSERT_GT(swoole_coroutine_write(_sock0->get_fd(), SW_STRL(TEST_STR)), 0); // blocking, wrap-socket not exists char buf[128]; ASSERT_EQ(swoole_coroutine_read(_sock1->get_fd(), buf, sizeof(buf)), strlen(TEST_STR)); ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock1->get_fd()), -1); // unwrap ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), 0); // fail to unwrap ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), -1); }); } static void test_freopen() { auto output_file = "/tmp/output.txt"; unlink(output_file); unlink(TEST_LOG_FILE); auto fp = swoole_coroutine_fopen(TEST_LOG_FILE, "w"); if (fp == NULL) { fprintf(stderr, "Failed to open '%s': %s (errno=%d)\n", TEST_LOG_FILE, strerror(errno), errno); } ASSERT_NE(fp, nullptr); swoole_coroutine_fputs("hello\n", fp); ASSERT_NE(swoole_coroutine_freopen(output_file, "w", fp), nullptr); swoole_coroutine_fputs("world\n", fp); swoole_coroutine_fclose(fp); auto rs1 = swoole::file_get_contents(output_file); ASSERT_FALSE(rs1->contains("hello\n")); ASSERT_TRUE(rs1->contains("world\n")); auto rs2 = swoole::file_get_contents(TEST_LOG_FILE); ASSERT_TRUE(rs2->contains("hello\n")); ASSERT_FALSE(rs2->contains("world\n")); unlink(TEST_LOG_FILE); unlink(output_file); } TEST(coroutine_hook, freopen) { coroutine::run([&](void *arg) { test_freopen(); }); test_freopen(); } TEST(coroutine_hook, ftruncate) { coroutine::run([&](void *arg) { int fd = swoole_coroutine_open("/tmp/123.txt", O_CREAT | O_RDWR, 0); ASSERT_TRUE(fd > 0); const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; size_t length = strlen(data); ssize_t write_length = swoole_coroutine_write(fd, (const void *) data, length); ASSERT_TRUE(write_length == static_cast(length)); ASSERT_TRUE(swoole_coroutine_ftruncate(fd, 0) == 0); swoole_coroutine_close(fd); }); } TEST(coroutine_hook, get_socket_fail) { { auto rs = swoole_coroutine_get_socket_object(999); ASSERT_EQ(errno, ENOTSOCK); ASSERT_EQ(rs, nullptr); } { int fd; coroutine::run([&](void *arg) { fd = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); ASSERT_GT(errno, 0); }); auto rs = swoole_coroutine_get_socket_object_ex(fd); ASSERT_EQ(errno, EWOULDBLOCK); ASSERT_EQ(rs, nullptr); swoole_coroutine_close(fd); } } TEST(coroutine_hook, create_socket_fail) { coroutine::run([&](void *arg) { int fd = swoole_coroutine_socket(AF_INET + 99, SOCK_CLOEXEC, 0); ASSERT_EQ(fd, -1); ASSERT_EQ(errno, EAFNOSUPPORT); }); } ================================================ FILE: core-tests/src/coroutine/http_server.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "httplib_server.h" using namespace swoole::test; using namespace httplib; using namespace std; TEST(coroutine_http_server, get) { Server svr; mutex lock; int port = swoole::test::get_random_port(); lock.lock(); thread t1([&lock, port]() { lock.lock(); Client cli(TEST_HOST, port); auto resp1 = cli.Get("/hi"); EXPECT_EQ(resp1->status, 200); EXPECT_EQ(resp1->body, string("Hello World!")); auto resp2 = cli.Get("/stop"); EXPECT_EQ(resp2->status, 200); EXPECT_EQ(resp2->body, string("Stop Server!")); }); coroutine::run([&lock, &svr, port](void *arg) { svr.Get("/hi", [](const Request &req, Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.Get("/stop", [&svr](const Request &req, Response &res) { res.set_content("Stop Server!", "text/plain"); svr.stop(); }); svr.Post("/post", [](const Request &req, Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.BeforeListen([&lock]() { lock.unlock(); }); ASSERT_TRUE(svr.listen(TEST_HOST, port)); }); t1.join(); } TEST(coroutine_http_server, post) { Server svr; mutex lock; int port = swoole::test::get_random_port(); lock.lock(); std::thread t1([&lock, port]() { lock.lock(); Client cli(TEST_HOST, port); httplib::Params params; params.emplace("name", "john"); params.emplace("note", "coder"); auto resp1 = cli.Post("/post", params); EXPECT_EQ(resp1->status, 200); EXPECT_EQ(resp1->body, string("Hello World!")); auto resp2 = cli.Get("/stop"); EXPECT_EQ(resp2->status, 200); EXPECT_EQ(resp2->body, string("Stop Server!")); }); coroutine::run([&lock, &svr, port](void *arg) { svr.Get("/stop", [&svr](const Request &req, Response &res) { res.set_content("Stop Server!", "text/plain"); svr.stop(); }); svr.Post("/post", [](const Request &req, Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.BeforeListen([&lock]() { lock.unlock(); }); svr.listen(TEST_HOST, port); }); t1.join(); } ================================================ FILE: core-tests/src/coroutine/iouring.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_iouring.h" #include #include #ifdef SW_USE_IOURING using swoole::Iouring; using swoole::Reactor; using swoole::coroutine::System; using swoole::test::coroutine; using swoole::test::create_socket_pair; TEST(iouring, create) { coroutine::run([](void *arg) { SwooleG.iouring_entries = 4; SwooleG.iouring_workers = 65536; auto fd = Iouring::open(TEST_TMP_FILE, O_CREAT, 0666); ASSERT_GE(fd, 0); ASSERT_NE(Iouring::close(fd), -1); }); } TEST(iouring, list_all_opcode) { auto list = Iouring::list_all_opcode(); for (auto &item : list) { DEBUG() << "opcode: " << item.first << ", value: " << item.second << "\n"; } ASSERT_TRUE(list.size() > 0); } TEST(iouring, open_and_close) { coroutine::run([](void *arg) { const char *test_file = "/tmp/file_1"; int fd = Iouring::open(test_file, O_CREAT, 0666); ASSERT_TRUE(fd > 0); int result = Iouring::close(fd); ASSERT_TRUE(result == 0); result = Iouring::unlink(test_file); ASSERT_TRUE(result == 0); }); } TEST(iouring, mkdir_and_rmdir) { coroutine::run([](void *arg) { const char *directory = "/tmp/aaaa"; int rv; rv = Iouring::mkdir(directory, 0755); ASSERT_EQ(rv, 0); rv = Iouring::rmdir(directory); ASSERT_EQ(rv, 0); }); } TEST(iouring, write_and_read) { coroutine::run([](void *arg) { const char *test_file = "/tmp/file_2"; int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); ASSERT_TRUE(fd > 0); const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; size_t length = strlen(data); ssize_t result = Iouring::write(fd, (const void *) data, length); ASSERT_TRUE(result > 0); ASSERT_TRUE(result == static_cast(length)); lseek(fd, 0, SEEK_SET); char buf[128]; result = Iouring::read(fd, (void *) buf, 128); ASSERT_TRUE(result > 0); ASSERT_TRUE(result == static_cast(length)); buf[result] = '\0'; ASSERT_STREQ(data, buf); result = Iouring::close(fd); ASSERT_TRUE(result == 0); result = Iouring::unlink(test_file); ASSERT_TRUE(result == 0); }); } TEST(iouring, rename) { coroutine::run([](void *arg) { const char *oldpath = "/tmp/file_2"; const char *newpath = "/tmp/file_3"; int fd = Iouring::open(oldpath, O_CREAT | O_RDWR, 0666); ASSERT_TRUE(fd > 0); int result = Iouring::close(fd); ASSERT_TRUE(result == 0); result = Iouring::rename(oldpath, newpath); ASSERT_TRUE(result == 0); result = Iouring::unlink(newpath); ASSERT_TRUE(result == 0); }); } #ifdef HAVE_IOURING_STATX TEST(iouring, fstat_and_stat) { coroutine::run([](void *arg) { struct stat statbuf {}; int fd = Iouring::open(TEST_TMP_FILE, O_CREAT | O_RDWR, 0666); ASSERT_TRUE(fd > 0); ASSERT_EQ(Iouring::write(fd, TEST_STR, strlen(TEST_STR)), strlen(TEST_STR)); int result = Iouring::fstat(fd, &statbuf); ASSERT_TRUE(result == 0); ASSERT_TRUE(statbuf.st_size > 0); result = Iouring::close(fd); ASSERT_TRUE(result == 0); statbuf = {}; result = Iouring::stat(TEST_TMP_FILE, &statbuf); ASSERT_TRUE(result == 0); ASSERT_TRUE(statbuf.st_size > 0); }); } #endif TEST(iouring, fsync_and_fdatasync) { coroutine::run([](void *arg) { const char *test_file = "/tmp/file_2"; int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); ASSERT_TRUE(fd > 0); const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; size_t length = strlen(data); ssize_t write_length = Iouring::write(fd, (const void *) data, length); ASSERT_TRUE(write_length == static_cast(length)); int result = Iouring::fsync(fd); ASSERT_TRUE(result == 0); write_length = Iouring::write(fd, (const void *) data, length); ASSERT_TRUE(write_length == static_cast(length)); result = Iouring::fdatasync(fd); ASSERT_TRUE(result == 0); result = Iouring::close(fd); ASSERT_TRUE(result == 0); result = Iouring::unlink(test_file); ASSERT_TRUE(result == 0); }); } #ifdef HAVE_IOURING_FTRUNCATE TEST(iouring, ftruncate) { coroutine::run([&](void *arg) { const char *test_file = "/tmp/file_3"; int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); ASSERT_TRUE(fd > 0); const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; size_t length = strlen(data); ssize_t write_length = Iouring::write(fd, (const void *) data, length); ASSERT_TRUE(write_length == static_cast(length)); int result = Iouring::ftruncate(fd, 0); ASSERT_TRUE(result == 0); Iouring::close(fd); }); } #endif #endif TEST(iouring, connect) { signal(SIGPIPE, SIG_IGN); coroutine::run([](void *arg) { int fd = Iouring::socket(AF_INET, SOCK_STREAM, 0); ASSERT_NE(fd, -1); swoole::network::Address addr{}; ASSERT_TRUE(addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80, true)); int rv = Iouring::connect(fd, &addr.addr.ss, addr.len); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); rv = Iouring::write(fd, req.c_str(), req.length()); ASSERT_EQ(rv, req.length()); char buf[4096]; rv = Iouring::read(fd, buf, sizeof(buf)); ASSERT_GT(rv, 100); std::string s{buf}; ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); Iouring::close(fd); }); } TEST(iouring, send_recv) { signal(SIGPIPE, SIG_IGN); coroutine::run([](void *arg) { int fd = Iouring::socket(AF_INET, SOCK_STREAM, 0); ASSERT_NE(fd, -1); swoole::network::Address addr{}; ASSERT_TRUE(addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80, true)); int rv = Iouring::connect(fd, &addr.addr.ss, addr.len); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); rv = Iouring::send(fd, req.c_str(), req.length(), 0); ASSERT_EQ(rv, req.length()); char buf[4096]; rv = Iouring::recv(fd, buf, sizeof(buf), 0); ASSERT_GT(rv, 100); std::string s{buf}; ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); Iouring::close(fd); }); } TEST(iouring, sendfile) { swoole::File file(swoole::test::get_jpg_file(), O_RDONLY); coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { off_t offset = 0; auto rv = Iouring::sendfile(pairs[0], file.get_fd(), &offset, file.get_size()); ASSERT_EQ(rv, file.get_size()); }); char data[250000]; CoSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.read(data, 250000); data[result] = '\0'; sock.close(); ASSERT_EQ(result, file.get_size()); }); } static int uring_create_server_socket(struct sockaddr_in *actual_server_addr, socklen_t *actual_server_addr_len) { // Create a TCP socket using coroutine API int server_sock = Iouring::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); EXPECT_GT(server_sock, 0); // Bind the socket to localhost with port 0 (auto-assign) struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = 0; int retval = Iouring::bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); EXPECT_EQ(retval, 0); // Listen on the socket retval = Iouring::listen(server_sock, 128); EXPECT_EQ(retval, 0); // Get the actual server port EXPECT_EQ(getsockname(server_sock, (struct sockaddr *) actual_server_addr, actual_server_addr_len), 0); return server_sock; } TEST(iouring, accept) { coroutine::run([](void *arg) { struct sockaddr_in actual_server_addr; socklen_t actual_server_addr_len = sizeof(actual_server_addr); auto server_sock = uring_create_server_socket(&actual_server_addr, &actual_server_addr_len); struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); // Test that swoole_coroutine_accept works correctly Coroutine::create([&](void *arg) { // Give the server time to start listening System::sleep(0.01); // Connect to the server using coroutine API int client_sock = Iouring::socket(AF_INET, SOCK_STREAM, 0); ASSERT_GT(client_sock, 0); // Connect to the server auto retval = Iouring::connect(client_sock, (struct sockaddr *) &actual_server_addr, actual_server_addr_len); ASSERT_EQ(retval, 0); // Send a test message const char *test_message = "test_data"; ssize_t sent_bytes = Iouring::send(client_sock, test_message, strlen(test_message), 0); ASSERT_EQ(sent_bytes, (ssize_t) strlen(test_message)); // Close the client socket Iouring::close(client_sock); }); // Accept the connection using coroutine API int client_sock = Iouring::accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len); ASSERT_GT(client_sock, 0); // Receive data from client char buffer[256] = {}; ssize_t received_bytes = Iouring::recv(client_sock, buffer, sizeof(buffer) - 1, 0); ASSERT_GT(received_bytes, 0); ASSERT_STREQ(buffer, "test_data"); // Close the client socket Iouring::close(client_sock); Iouring::close(server_sock); }); } TEST(iouring, sleep) { coroutine::run([](void *arg) { { auto begin = swoole::microtime(); ASSERT_EQ(Iouring::sleep(1, 200 * SW_NUM_MILLION), 0); auto end = swoole::microtime(); ASSERT_GE(end - begin, 1.2); } { auto begin = swoole::microtime(); ASSERT_EQ(Iouring::sleep(0, 300 * SW_NUM_MILLION), 0); auto end = swoole::microtime(); ASSERT_GE(end - begin, 0.3); } }); } TEST(iouring, wait_success) { auto pid = swoole::test::spawn_exec([]() { sleep(1); }); coroutine::run([pid](void *arg) { int status; ASSERT_EQ(Iouring::wait(&status, 5), pid); ASSERT_EQ(status, 0); }); } TEST(iouring, wait_timeout) { auto pid = swoole::test::spawn_exec([]() { sleep(2000); }); coroutine::run([pid](void *arg) { int status = 0x9501; ASSERT_EQ(Iouring::wait(&status, 0.5), -1); ASSERT_EQ(errno, ETIMEDOUT); ASSERT_EQ(status, 0x9501); // After the timeout, the status will not be set. }); kill(pid, SIGKILL); } TEST(iouring, waitpid) { auto pid = swoole::test::spawn_exec([]() { sleep(2000); }); coroutine::run([pid](void *arg) { int status; ASSERT_EQ(Iouring::waitpid(pid, &status, WNOHANG, -1), 0); ASSERT_EQ(Iouring::waitpid(pid, &status, 0, 0.1), -1); ASSERT_EQ(errno, ETIMEDOUT); kill(pid, SIGKILL); System::sleep(0.3); ASSERT_EQ(Iouring::waitpid(pid, &status, 0, 0.1), pid); }); } TEST(iouring, poll) { auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); coroutine::run([=](void *arg) { int pair[2]; struct pollfd fds[2]; char buf[4096]; socketpair(AF_UNIX, SOCK_STREAM, 0, pair); std::map results; auto _fd0 = pair[0]; auto _fd1 = pair[1]; bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN | POLLOUT; ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); ASSERT_TRUE(fds[0].revents & POLLOUT); Coroutine::create([=](void *) { ssize_t result; result = Iouring::write(_fd0, buffer->value(), buffer->get_length()); ASSERT_GT(result, 0); Iouring::sleep(0.1); result = Iouring::write(_fd1, buffer->value(), 16 * 1024); ASSERT_GT(result, 0); }); bzero(fds, sizeof(pollfd)); fds[0].fd = _fd1; fds[0].events = POLLIN; ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); ASSERT_TRUE(fds[0].revents & POLLIN); ssize_t result = Iouring::read(_fd1, buf, sizeof(buf)); ASSERT_GT(result, 1024); bzero(fds, sizeof(pollfd)); ASSERT_EQ(Iouring::poll(fds, 2, 1000), -1); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); System::sleep(0.02); bzero(fds, sizeof(pollfd)); fds[0].fd = _fd0; fds[0].events = POLLIN | POLLOUT; ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); ASSERT_TRUE(fds[0].revents & POLLIN); }); } TEST(iouring, accept_timeout) { coroutine::run([](void *arg) { struct sockaddr_in actual_server_addr; socklen_t actual_server_addr_len = sizeof(actual_server_addr); auto server_sock = uring_create_server_socket(&actual_server_addr, &actual_server_addr_len); struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int client_sock = Iouring::accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len, 0, 0.1); ASSERT_EQ(client_sock, -1); Iouring::close(server_sock); }); } ================================================ FILE: core-tests/src/coroutine/socket.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_process.h" #include "test_coroutine.h" #include "test_server.h" using swoole::Coroutine; using swoole::HttpProxy; using swoole::Protocol; using swoole::RecvData; using swoole::Socks5Proxy; using swoole::String; using swoole::coroutine::Socket; using swoole::coroutine::System; using swoole::network::Address; using swoole::network::IOVector; using swoole::test::coroutine; using swoole::test::create_socket_pair; using swoole::test::Process; using swoole::test::Server; const std::string host = "www.baidu.com"; TEST(coroutine_socket, connect_refused) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", 9801); ASSERT_EQ(retval, false); ASSERT_EQ(sock.errCode, ECONNREFUSED); }); } TEST(coroutine_socket, connect_timeout) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); sock.set_timeout(0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_DNS), 0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_CONNECT), 0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_READ), 0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_WRITE), 0.5); sock.set_timeout(1.5, SW_TIMEOUT_RDWR); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_DNS), 0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_CONNECT), 0.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_READ), 1.5); ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_WRITE), 1.5); bool retval = sock.connect("192.0.0.1", 9801); ASSERT_EQ(retval, false); ASSERT_EQ(sock.errCode, ETIMEDOUT); }); } TEST(coroutine_socket, timeout_controller) { coroutine::run([](void *arg) { const int port = __LINE__ + TEST_PORT; Coroutine::create([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); conn->send(TEST_STR); System::sleep(1); delete conn; }); Socket sock(SW_SOCK_TCP); Socket::TimeoutController tc(&sock, 0.5, SW_TIMEOUT_ALL); ASSERT_TRUE(sock.connect("127.0.0.1", port)); char buf[128]; off_t offset = 0; sock.errCode = 0; while (true) { if (sw_unlikely(tc.has_timedout(SW_TIMEOUT_READ))) { break; } auto rv = sock.recv(buf + offset, sizeof(buf) - offset); if (rv <= 0) { break; } offset += rv; } ASSERT_TRUE(tc.has_timedout(SW_TIMEOUT_READ)); ASSERT_EQ(sock.errCode, ETIMEDOUT); }); } TEST(coroutine_socket, timeout_setter) { coroutine::run([](void *arg) { const int port = __LINE__ + TEST_PORT; Coroutine::create([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); conn->send(TEST_STR); System::sleep(1); delete conn; }); Socket sock(SW_SOCK_TCP); Socket::TimeoutSetter ts(&sock, 0.5, SW_TIMEOUT_ALL); ASSERT_TRUE(sock.connect("127.0.0.1", port)); char buf[128]; off_t offset = 0; sock.errCode = 0; while (true) { auto rv = sock.recv(buf + offset, sizeof(buf) - offset); if (rv <= 0) { break; } offset += rv; } ASSERT_EQ(sock.errCode, ETIMEDOUT); }); } TEST(coroutine_socket, connect_with_dns) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect(host, 80); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); }); } TEST(coroutine_socket, tcp6) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP6); bool retval = sock.connect("::1", 80); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); }); } TEST(coroutine_socket, unixsock_fail) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_UNIX_STREAM); bool retval = sock.connect("/tmp/unix.sock"); ASSERT_EQ(retval, false); ASSERT_EQ(sock.errCode, ENOENT); }); } TEST(coroutine_socket, recv_success) { pid_t pid; int port = swoole::test::get_random_port(); Process proc([port](Process *proc) { Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); serv.on("Receive", [](ON_RECEIVE_PARAMS) { SERVER_THIS->send(req->info.fd, req->data, req->info.len); return 0; }); serv.start(); }); pid = proc.start(); sleep(1); // wait for the test server to start coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect(TEST_HOST, port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send(SW_STRS("hello world\n")); char buf[128]; int n = sock.recv(buf, sizeof(buf)); buf[n] = 0; ASSERT_EQ(strcmp(buf, "hello world\n"), 0); }); kill(pid, SIGTERM); int status; wait(&status); } TEST(coroutine_socket, recv_fail) { pid_t pid; int port = swoole::test::get_random_port(); Process proc([port](Process *proc) { Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); serv.on("Receive", [](ON_PACKET_PARAMS) -> int { serv->close(req->info.fd, 0); return 0; }); serv.start(); }); pid = proc.start(); sleep(1); // wait for the test server to start coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect(TEST_HOST, port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("close", 6); char buf[128]; int n = sock.recv(buf, sizeof(buf)); ASSERT_EQ(n, 0); }); kill(pid, SIGTERM); int status; wait(&status); } TEST(coroutine_socket, bind_success) { const int port = __LINE__ + TEST_PORT; coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); Socket sock_1(SW_SOCK_UNIX_DGRAM); retval = sock_1.bind("/tmp/swoole-core-tests.sock"); ASSERT_EQ(retval, true); }); } TEST(coroutine_socket, bind_fail) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("192.111.11.1", 9909); ASSERT_EQ(retval, false); ASSERT_EQ(sock.errCode, EADDRNOTAVAIL); Socket sock_1(SW_SOCK_TCP); retval = sock_1.bind("127.0.0.1", 70000); ASSERT_EQ(retval, false); }); } TEST(coroutine_socket, listen) { const int port = __LINE__ + TEST_PORT; coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); }); } TEST(coroutine_socket, accept) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); ASSERT_NE(conn, nullptr); delete conn; }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.close(); }}); } static void socket_set_eof_protocol(Socket &sock) { memcpy(sock.protocol.package_eof, SW_STRL(CRLF)); sock.protocol.package_eof_len = 2; sock.open_eof_check = true; } TEST(coroutine_socket, eof_1) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); conn->send(EOF_PACKET); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); size_t eof_packet_len = strlen(EOF_PACKET); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, eof_packet_len); ASSERT_EQ(string(buf->str, l), string(EOF_PACKET)); ASSERT_EQ(buf->length, eof_packet_len); ASSERT_EQ(buf->offset, eof_packet_len); }}); } TEST(coroutine_socket, eof_2) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); ASSERT_TRUE(sock.bind("127.0.0.1", port)); ASSERT_TRUE(sock.listen(128)); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); conn->send(EOF_PACKET EOF_PACKET_2); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); // packet 1 { ssize_t l = sock.recv_packet(RECV_TIMEOUT); size_t eof_packet_len = strlen(EOF_PACKET); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, eof_packet_len); ASSERT_EQ(string(buf->str, l), string(EOF_PACKET)); ASSERT_EQ(buf->length, strlen(EOF_PACKET EOF_PACKET_2)); ASSERT_EQ(buf->offset, eof_packet_len); } // packet 2 { ssize_t l = sock.recv_packet(RECV_TIMEOUT); size_t eof_packet_len = strlen(EOF_PACKET_2); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, eof_packet_len); ASSERT_EQ(string(buf->str, l), string(EOF_PACKET_2)); ASSERT_EQ(buf->length, strlen(EOF_PACKET_2)); ASSERT_EQ(buf->offset, eof_packet_len); } }}); } TEST(coroutine_socket, eof_3) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); conn->shutdown(); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, 0); }}); } TEST(coroutine_socket, eof_4) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); conn->send(EOF_PACKET, strlen(EOF_PACKET) - strlen(CRLF)); // no eof conn->shutdown(); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, 0); auto buf = sock.get_read_buffer(); ASSERT_EQ(string(buf->str, 10), string(EOF_PACKET, 10)); }}); } TEST(coroutine_socket, eof_5) { const int port = __LINE__ + TEST_PORT; size_t pkt_len = 512 * 1024; coroutine::run({[pkt_len, port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); String *s = swoole::make_string(pkt_len); s->repeat("A", 1, pkt_len - 16); s->append(SW_STRL(CRLF)); conn->get_socket()->set_send_buffer_size(65536); conn->send_all(s->str, s->length); }, [pkt_len, port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); sock.get_socket()->set_recv_buffer_size(65536); ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, pkt_len - 14); }}); } TEST(coroutine_socket, eof_6) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); String s(128 * 1024); s.repeat("A", 1, 128 * 1024 - 16); s.append(SW_STRL(CRLF)); conn->send_all(s.value(), s.get_length()); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); sock.protocol.package_max_length = 1024 * 64; ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, -1); ASSERT_EQ(sock.errCode, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); }}); } static void socket_set_length_protocol_1(Socket &sock) { sock.protocol = {}; sock.protocol.package_length_type = 'n'; sock.protocol.package_length_size = swoole_type_size(sock.protocol.package_length_type); sock.protocol.package_body_offset = 2; sock.protocol.get_package_length = Protocol::default_length_func; sock.protocol.package_max_length = 65535; sock.open_length_check = true; } static void socket_set_length_protocol_2(Socket &sock) { sock.protocol = {}; sock.protocol.package_length_type = 'N'; sock.protocol.package_length_size = swoole_type_size(sock.protocol.package_length_type); sock.protocol.package_body_offset = 4; sock.protocol.get_package_length = Protocol::default_length_func; sock.protocol.package_max_length = 2 * 1024 * 1024; sock.open_length_check = true; } TEST(coroutine_socket, length_1) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; ssize_t l = swoole_random_bytes(buf + 2, sizeof(buf) - 2); *(uint16_t *) buf = htons(l); conn->send(buf, l + 2); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_1(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, 1024); ASSERT_EQ(buf->length, l); ASSERT_EQ(buf->offset, l); }}); } TEST(coroutine_socket, length_2) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; *(uint16_t *) buf = htons(0); conn->send(buf, 2); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_1(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, 2); ASSERT_EQ(buf->length, 2); ASSERT_EQ(buf->offset, 2); }}); } TEST(coroutine_socket, length_3) { const int port = __LINE__ + TEST_PORT; coroutine::run({[](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; memset(buf, 'A', sizeof(buf)); *(uint16_t *) buf = htons(65530); conn->send(buf, sizeof(buf)); }, [](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_1(sock); sock.protocol.package_max_length = 4096; ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, -1); ASSERT_EQ(sock.errCode, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); }}); } static string pkt_1; static string pkt_2; static void length_protocol_server_func(void *arg, int port) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); String strbuf(256 * 1024); uint32_t pack_len; size_t l_1 = swoole_rand(65536, 65536 * 2); pack_len = htonl(l_1); strbuf.append((char *) &pack_len, sizeof(pack_len)); strbuf.append_random_bytes(l_1); pkt_1 = string(strbuf.str, l_1 + 4); size_t l_2 = swoole_rand(65536, 65536 * 2); pack_len = htonl(l_2); strbuf.append((char *) &pack_len, sizeof(pack_len)); strbuf.append_random_bytes(l_2); pkt_2 = string(strbuf.str + pkt_1.length(), l_2 + 4); conn->send_all(strbuf.str, strbuf.length); } TEST(coroutine_socket, length_4) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { length_protocol_server_func(arg, port); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_2(sock); size_t bytes = 0; for (int i = 0; i < 2; i++) { ssize_t l = sock.recv_packet(RECV_TIMEOUT); bytes += l; auto buf = sock.get_read_buffer(); uint32_t unpack_len = ntohl(*(uint32_t *) buf->str); if (i == 0) { ASSERT_EQ(pkt_1, string(buf->str, buf->length)); } else { ASSERT_EQ(pkt_2, string(buf->str, buf->length)); } ASSERT_EQ(unpack_len, l - 4); ASSERT_EQ(buf->length, l); ASSERT_EQ(buf->offset, l); } ASSERT_GE(bytes, 65536 * 2); }}); } TEST(coroutine_socket, length_5) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { length_protocol_server_func(arg, port); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_2(sock); size_t bytes = 0; for (int i = 0; i < 2; i++) { ssize_t l = sock.recv_packet(RECV_TIMEOUT); bytes += l; char *data = sock.pop_packet(); uint32_t unpack_len = ntohl(*(uint32_t *) data); ASSERT_EQ(unpack_len, l - 4); if (i == 0) { ASSERT_EQ(pkt_1, string(data, l)); } else { ASSERT_EQ(pkt_2, string(data, l)); } } ASSERT_GE(bytes, 65536 * 2); }}); } TEST(coroutine_socket, length_7) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); char buf[1024]; *(uint32_t *) buf = htons(0); conn->send(buf, 2); System::sleep(0.01); conn->send(buf + 2, 2); }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_2(sock); ssize_t l = sock.recv_packet(RECV_TIMEOUT); auto buf = sock.get_read_buffer(); ASSERT_EQ(l, 4); ASSERT_EQ(buf->length, 4); ASSERT_EQ(buf->offset, 4); }}); } TEST(coroutine_socket, event_hup) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); System::sleep(0.05); char buf[1024]; auto ret_n = conn->recv(buf, sizeof(buf)); ASSERT_EQ(ret_n, 0); delete conn; }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); auto buf = sock.get_read_buffer(); Coroutine::create([&sock](void *args) { System::sleep(0.01); sock.shutdown(SHUT_RDWR); }); auto n = sock.recv_all(buf->str, buf->size); ASSERT_EQ(sock.get_socket()->event_hup, 1); ASSERT_EQ(n, 0); }}); } TEST(coroutine_socket, recv_line) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); conn->send("hello world\n"); conn->send("\r"); char buf[256]; memset(buf, 'A', 128); memset(buf + 128, 'B', 125); conn->send(buf, 253); delete conn; }, [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); size_t n; auto buf = sock.get_read_buffer(); n = sock.recv_line(buf->str, 128); ASSERT_EQ(n, 12); ASSERT_MEMEQ(buf->str, "hello world\n", 12); n = sock.recv_line(buf->str, 128); ASSERT_EQ(n, 1); ASSERT_MEMEQ(buf->str, "\r", 1); char buf_2[256]; memset(buf_2, 'A', 128); memset(buf_2 + 128, 'B', 125); n = sock.recv_line(buf->str, 128); ASSERT_EQ(n, 128); ASSERT_MEMEQ(buf->str, buf_2, 128); n = sock.recv_line(buf->str, 128); ASSERT_EQ(n, 125); ASSERT_MEMEQ(buf->str, buf_2 + 128, 125); n = sock.recv_line(buf->str, 128); ASSERT_EQ(n, 0); }}); } TEST(coroutine_socket, getsockname) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); ASSERT_TRUE(sock.connect(host, 80)); ASSERT_TRUE(sock.getsockname()); sock.close(); }); } TEST(coroutine_socket, buffer) { Socket sock(SW_SOCK_TCP); auto rbuf = sock.get_read_buffer(); auto wbuf = sock.get_write_buffer(); auto rbuf_pop = sock.pop_read_buffer(); auto wbuf_pop = sock.pop_write_buffer(); ASSERT_EQ(rbuf, rbuf_pop); ASSERT_EQ(wbuf, wbuf_pop); auto rbuf2 = sock.get_read_buffer(); auto wbuf2 = sock.get_write_buffer(); ASSERT_NE(rbuf2, rbuf); ASSERT_NE(wbuf2, wbuf); delete rbuf_pop; delete wbuf_pop; } TEST(coroutine_socket, check_liveness) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.connect(host, 80); ASSERT_EQ(retval, true); bool result = sock.check_liveness(); sock.close(); ASSERT_EQ(result, true); result = sock.check_liveness(); ASSERT_EQ(result, false); }); } TEST(coroutine_socket, write_and_read) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; size_t length = text.length(); Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.write(text.c_str(), length); sock.close(); ASSERT_EQ(result, length); }); char data[128]; Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.read(data, 128); sock.close(); ASSERT_GT(result, 0); data[result] = '\0'; ASSERT_STREQ(text.c_str(), data); }); } TEST(coroutine_socket, write_and_read_2) { // test for Socket::Socket(int _fd, int _domain, int _type, int _protocol) construct function coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; size_t length = text.length(); Coroutine::create([&](void *) { Socket sock(pairs[0], AF_UNIX, SOCK_STREAM, 0); ssize_t result = sock.write(text.c_str(), length); sock.close(); ASSERT_EQ(result, length); }); char data[128]; Socket sock(pairs[1], AF_UNIX, SOCK_STREAM, 0); ssize_t result = sock.read(data, 128); sock.close(); ASSERT_GT(result, 0); data[result] = '\0'; ASSERT_STREQ(text.c_str(), data); }); } TEST(coroutine_socket, writev_and_readv) { coroutine::run([&](void *arg) { int iovcnt = 3; int pairs[2]; std::string text = "Hello World"; size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::unique_ptr iov(new iovec[iovcnt]); for (int i = 0; i < iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), iovcnt); Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.writev(&io_vector); sock.close(); ASSERT_EQ(result, length * 3); }); std::vector results(iovcnt); std::unique_ptr iov(new iovec[iovcnt]); for (int i = 0; i < iovcnt; i++) { iov[i].iov_base = (void *) results[i].c_str(); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), iovcnt); Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.readv(&io_vector); sock.close(); ASSERT_EQ(result, length * 3); for (auto iter = results.begin(); iter != results.end(); iter++) { (*iter)[length] = '\0'; ASSERT_STREQ(text.c_str(), (*iter).c_str()); } }); } TEST(coroutine_socket, send_and_recv_all) { coroutine::run([&](void *arg) { int pairs[2]; String wbuf; wbuf.append_random_bytes(4 * 1024 * 1024, false); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_send_buffer_size(65536); ASSERT_EQ(sock.send_all(wbuf.str, wbuf.length), wbuf.length); System::sleep(0.1); sock.close(); }); Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_recv_buffer_size(65536); String rbuf(wbuf.length); ssize_t result = sock.recv_all(rbuf.str, wbuf.length); ASSERT_EQ(result, wbuf.length); ASSERT_MEMEQ(wbuf.str, rbuf.str, wbuf.length); System::sleep(0.1); sock.close(); }); } TEST(coroutine_socket, writevall_and_readvall) { coroutine::run([&](void *arg) { int write_iovcnt = 4; int pairs[2]; char buf[65536]; swoole_random_bytes(buf, sizeof(buf)); std::string text(buf, sizeof(buf)); size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::unique_ptr iov(new iovec[write_iovcnt]); for (int i = 0; i < write_iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); iov[i].iov_len = length; } Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_send_buffer_size(sizeof(buf)); IOVector io_vector1((struct iovec *) iov.get(), write_iovcnt); ASSERT_EQ(sock.writev_all(&io_vector1), write_iovcnt * sizeof(buf)); System::sleep(0.01); IOVector io_vector2((struct iovec *) iov.get(), write_iovcnt); ASSERT_EQ(sock.writev_all(&io_vector2), write_iovcnt * sizeof(buf)); sock.close(); }); int read_iovcnt = 8; std::unique_ptr iov(new iovec[read_iovcnt]); for (int i = 0; i < read_iovcnt; i++) { iov[i].iov_base = sw_malloc(length); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), read_iovcnt); Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_recv_buffer_size(sizeof(buf)); ssize_t result = sock.readv_all(&io_vector); sock.close(); ASSERT_EQ(result, length * read_iovcnt); for (int i = 0; i < read_iovcnt; i++) { ASSERT_MEMEQ(iov[i].iov_base, buf, sizeof(buf)); sw_free(iov[i].iov_base); } }); } TEST(coroutine_socket, sendfile) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::string file = swoole::test::get_jpg_file(); Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); bool result = sock.sendfile(file.c_str(), 0, 0); sock.close(); ASSERT_TRUE(result); }); char data[250000]; Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.read(data, 250000); data[result] = '\0'; sock.close(); ASSERT_GT(result, 0); }); } static void test_sendto_recvfrom(enum swSocketType sock_type) { coroutine::run([&](void *arg) { std::string server_text = "hello world!!!"; size_t server_length = server_text.length(); std::string client_text = "hello swoole!!!"; size_t client_length = client_text.length(); const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; const char *local = "localhost"; int port = swoole::test::get_random_port(); Socket sock_server(sock_type); Socket sock_client(sock_type); sock_server.bind(ip, port); sock_client.bind(ip, port + 1); ON_SCOPE_EXIT { sock_server.close(); sock_client.close(); }; sock_server.sendto(ip, port + 1, (const void *) server_text.c_str(), server_length); char data_from_server[128] = {}; struct sockaddr_in serveraddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(ip); serveraddr.sin_port = htons(port); socklen_t addr_length = sizeof(serveraddr); // receive data from server ssize_t result = sock_client.recvfrom(data_from_server, server_length, (struct sockaddr *) &serveraddr, &addr_length); data_from_server[result] = '\0'; ASSERT_EQ(result, server_length); ASSERT_STREQ(data_from_server, server_text.c_str()); // receive data from client char data_from_client[128] = {}; sock_client.sendto(local, port, (const void *) client_text.c_str(), client_length); result = sock_server.recvfrom(data_from_client, client_length); data_from_client[client_length] = '\0'; ASSERT_EQ(result, client_length); ASSERT_STREQ(data_from_client, client_text.c_str()); }); } TEST(coroutine_socket, sendto_recvfrom_udp) { test_sendto_recvfrom(SW_SOCK_UDP); test_sendto_recvfrom(SW_SOCK_UDP6); } static void socket_test_request_baidu(Socket &sock) { ASSERT_GT(sock.send(SW_STRL(TEST_REQUEST_BAIDU)), 0); String buf(65536); while (true) { char rbuf[4096]; ssize_t nr = sock.recv(rbuf, sizeof(rbuf)); if (nr <= 0) { break; } buf.append(rbuf, nr); } ASSERT_TRUE(buf.contains("www.baidu.com")); } static void proxy_test(Socket &sock, bool https) { if (https) { sock.enable_ssl_encrypt(); } bool retval = sock.connect(host, https ? 443 : 80); ON_SCOPE_EXIT { sock.close(); }; ASSERT_EQ(retval, true); if (https) { ASSERT_NE(sock.ssl_get_peer_cert(), ""); } socket_test_request_baidu(sock); } static void proxy_set_socks5_proxy(Socket &socket, int port, bool auth) { std::string username, password; if (auth) { username = std::string(TEST_SOCKS5_PROXY_USER); password = std::string(TEST_SOCKS5_PROXY_PASSWORD); } socket.set_socks5_proxy(TEST_SOCKS5_PROXY_HOST, port, username, password); } TEST(coroutine_socket, https_get_with_socks5_proxy) { coroutine::run([](void *arg) { if (swoole::test::is_github_ci()) { Socket sock(SW_SOCK_TCP); proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); proxy_test(sock, true); } // no auth { Socket sock(SW_SOCK_TCP); proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_NO_AUTH_PORT, false); proxy_test(sock, true); } }); } TEST(coroutine_socket, http_get_with_socks5_proxy) { coroutine::run([](void *arg) { if (swoole::test::is_github_ci()) { Socket sock(SW_SOCK_TCP); proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); proxy_test(sock, false); } // no auth { Socket sock(SW_SOCK_TCP); proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_NO_AUTH_PORT, false); proxy_test(sock, false); } }); } static void proxy_set_http_proxy(Socket &socket) { std::string username, password; if (swoole::test::is_github_ci()) { username = std::string(TEST_HTTP_PROXY_USER); password = std::string(TEST_HTTP_PROXY_PASSWORD); } socket.set_http_proxy(TEST_HTTP_PROXY_HOST, TEST_HTTP_PROXY_PORT, username, password); } TEST(coroutine_socket, http_get_with_http_proxy) { coroutine::run([&](void *arg) { Socket sock(SW_SOCK_TCP); proxy_set_http_proxy(sock); proxy_test(sock, false); }); } TEST(coroutine_socket, https_get_with_http_proxy) { coroutine::run([&](void *arg) { Socket sock(SW_SOCK_TCP); proxy_set_http_proxy(sock); proxy_test(sock, true); }); } TEST(coroutine_socket, ssl) { coroutine::run([&](void *arg) { Socket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/client.crt"); sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/client.key"); sock.set_ssl_verify_peer(false); sock.set_ssl_allow_self_signed(true); sock.set_ssl_cafile(swoole::test::get_ssl_dir() + "/ca.crt"); proxy_test(sock, true); }); } TEST(coroutine_socket, ssl_accept) { const int port = __LINE__ + TEST_PORT; auto svr = [port](void *arg) { Socket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); sock.enable_ssl_encrypt(); sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/server.crt"); sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/server.key"); sock.set_ssl_dhparam(swoole::test::get_ssl_dir() + "/dhparams.pem"); sock.set_ssl_ecdh_curve("secp256r1"); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); ASSERT_NE(conn, nullptr); ASSERT_TRUE(conn->ssl_handshake()); conn->send(EOF_PACKET); char rbuf[1024]; auto n = conn->recv(rbuf, sizeof(rbuf)); rbuf[n] = 0; ASSERT_STREQ(rbuf, EOF_PACKET_2); conn->close(); delete conn; }; auto cli = [port](void *arg) { Socket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); char rbuf[1024]; auto n = sock.recv(rbuf, sizeof(rbuf)); rbuf[n] = 0; ASSERT_STREQ(rbuf, EOF_PACKET); sock.send(EOF_PACKET_2); sock.close(); }; coroutine::run({svr, cli}); } TEST(coroutine_socket, peek) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; size_t length = text.length(); Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.write(text.c_str(), length); sock.close(); ASSERT_EQ(result, length); }); char data[128]; Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.peek(data, 5); sock.close(); ASSERT_EQ(result, 5); data[result] = '\0'; ASSERT_STREQ("Hello", data); }); } TEST(coroutine_socket, sendmsg_and_recvmsg) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; const size_t length = text.length(); Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); struct msghdr msg; struct iovec ivec; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = (void *) text.c_str(); ivec.iov_len = length; ssize_t ret = sock.sendmsg(&msg, 0); sock.close(); ASSERT_EQ(ret, length); }); Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); struct msghdr msg; struct iovec ivec; char buf[length + 1]; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = buf; ivec.iov_len = length; ssize_t ret = sock.recvmsg(&msg, 0); buf[ret] = '\0'; sock.close(); ASSERT_STREQ(buf, text.c_str()); }); } std::pair, std::shared_ptr > swoole::test::create_socket_pair() { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); auto sock0 = new Socket(pairs[0], SW_SOCK_UNIX_STREAM); auto sock1 = new Socket(pairs[1], SW_SOCK_UNIX_STREAM); sock0->get_socket()->set_buffer_size(65536); sock1->get_socket()->set_buffer_size(65536); std::pair, std::shared_ptr > result(sock0, sock1); return result; } TEST(coroutine_socket, close) { coroutine::run([&](void *arg) { auto pair = create_socket_pair(); auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); std::map results; auto _sock = pair.first; // write co Coroutine::create([&](void *) { SW_LOOP_N(32) { ssize_t result = _sock->write(buffer->value(), buffer->get_length()); if (result < 0 && _sock->errCode == ECANCELED) { ASSERT_FALSE(_sock->close()); ASSERT_EQ(_sock->errCode, SW_ERROR_CO_SOCKET_CLOSE_WAIT); results["write"] = true; ASSERT_EQ(_sock->write(buffer->value(), buffer->get_length()), -1); ASSERT_EQ(_sock->errCode, EBADF); break; } } }); // read co Coroutine::create([&](void *) { SW_LOOP_N(32) { char buf[4096]; ssize_t result = _sock->read(buf, sizeof(buf)); if (result < 0 && _sock->errCode == ECANCELED) { ASSERT_TRUE(_sock->close()); results["read"] = true; break; } } }); System::sleep(0.1); ASSERT_FALSE(_sock->close()); ASSERT_EQ(_sock->errCode, SW_ERROR_CO_SOCKET_CLOSE_WAIT); ASSERT_TRUE(_sock->is_closed()); ASSERT_TRUE(results["write"]); ASSERT_TRUE(results["read"]); ASSERT_FALSE(_sock->close()); ASSERT_EQ(_sock->errCode, EBADF); }); } TEST(coroutine_socket, cancel) { coroutine::run([&](void *arg) { auto pair = create_socket_pair(); auto buffer = sw_tg_buffer(); buffer->clear(); buffer->append_random_bytes(256 * 1024, false); std::map results; // read co Coroutine::create([&](void *) { SW_LOOP_N(32) { char buf[4096]; ssize_t result = pair.first->read(buf, sizeof(buf)); if (result < 0 && pair.first->errCode == ECANCELED) { results["read"] = true; break; } } }); System::sleep(0.1); pair.first->cancel(SW_EVENT_READ); ASSERT_TRUE(results["read"]); }); } TEST(coroutine_socket, get_event_str) { Socket sock; ASSERT_STREQ(sock.get_event_str(SW_EVENT_READ), "reading"); ASSERT_STREQ(sock.get_event_str(SW_EVENT_WRITE), "writing"); } TEST(coroutine_socket, option) { Socket sock(SW_SOCK_TCP); int optval; ASSERT_TRUE(sock.get_option(SOL_SOCKET, SO_RCVBUF, &optval)); ASSERT_GT(optval, 65536); optval *= 2; ASSERT_TRUE(sock.set_option(SOL_SOCKET, SO_RCVBUF, optval)); } static void test_ssl_verify() { Socket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); sock.set_tls_host_name(TEST_HTTP_DOMAIN); sock.set_ssl_verify_peer(true); ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 443)); ASSERT_TRUE(sock.ssl_verify(false)); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); ASSERT_EQ(sock.send(req.c_str(), req.length()), req.length()); ASSERT_TRUE(sock.check_liveness()); String buf(65536); SW_LOOP { auto n = sock.recv(buf.str + buf.length, buf.size - buf.length); if (n <= 0) { break; } buf.grow(n); } ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); usleep(50000); ASSERT_FALSE(sock.check_liveness()); } TEST(coroutine_socket, ssl_verify) { coroutine::run([](void *arg) { test_ssl_verify(); }); } TEST(coroutine_socket, shutdown) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 80)); ASSERT_TRUE(sock.shutdown(SHUT_RD)); ASSERT_FALSE(sock.shutdown(SHUT_RD)); ASSERT_ERREQ(ENOTCONN); ASSERT_TRUE(sock.shutdown(SHUT_WR)); ASSERT_FALSE(sock.shutdown(SHUT_WR)); ASSERT_ERREQ(ENOTCONN); ASSERT_FALSE(sock.shutdown()); ASSERT_ERREQ(ENOTCONN); }); } TEST(coroutine_socket, recv_packet) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 80)); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); ASSERT_EQ(sock.send(req.c_str(), req.length()), req.length()); ASSERT_TRUE(sock.check_liveness()); auto n = sock.recv_packet(); ASSERT_GT(n, 0); auto buf = sock.get_read_buffer(); ASSERT_TRUE(buf->contains(TEST_HTTP_EXPECT)); }); } TEST(coroutine_socket, set_error) { Socket sock(SW_SOCK_TCP); sock.set_err(1000, std::string(TEST_STR)); ASSERT_EQ(sock.errCode, 1000); ASSERT_STREQ(sock.errMsg, TEST_STR); } TEST(coroutine_socket, reinit) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP6); ASSERT_EQ(sock.get_sock_domain(), AF_INET6); proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); sock.connect("::1", 80); ASSERT_EQ(sock.get_sock_domain(), AF_INET); }); } ================================================ FILE: core-tests/src/coroutine/system.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_pipe.h" using namespace swoole; using namespace swoole::test; using swoole::coroutine::Socket; using swoole::coroutine::System; static const char *test_file = "/tmp/swoole-core-test"; static constexpr int DATA_SIZE = 8 * 1024 * 1024; static constexpr int DATA_SIZE_2 = 64 * 1024; TEST(coroutine_system, file) { test::coroutine::run([](void *arg) { std::shared_ptr buf = std::make_shared(DATA_SIZE); ASSERT_EQ(swoole_random_bytes(buf->str, buf->size - 1), buf->size - 1); buf->str[buf->size - 1] = 0; ASSERT_EQ(System::write_file(test_file, buf->str, buf->size, true, 0), buf->size); auto data = System::read_file(test_file, true); ASSERT_TRUE(data.get()); ASSERT_STREQ(buf->str, data->str); unlink(test_file); }); } TEST(coroutine_system, flock) { std::shared_ptr buf = std::make_shared(65536); ASSERT_EQ(swoole_random_bytes(buf->str, buf->size - 1), buf->size - 1); buf->str[buf->size - 1] = 0; test::coroutine::run([&buf](void *) { int fd = swoole_coroutine_open(test_file, File::WRITE | File::CREATE, 0666); ASSERT_TRUE(fd > 0); swoole_coroutine_flock(fd, LOCK_EX); for (int i = 0; i < 4; i++) { Coroutine::create([&buf](void *) { int fd = swoole_coroutine_open(test_file, File::READ, 0); ASSERT_TRUE(fd > 0); swoole_coroutine_flock(fd, LOCK_SH); String read_buf(DATA_SIZE_2); auto rn = swoole_coroutine_read(fd, read_buf.str, read_buf.size - 1); ASSERT_EQ(rn, read_buf.size - 1); read_buf.str[read_buf.size - 1] = 0; swoole_coroutine_flock(fd, LOCK_UN); EXPECT_STREQ(read_buf.str, buf->str); swoole_coroutine_close(fd); }); } auto wn = swoole_coroutine_write(fd, buf->str, buf->size - 1); ASSERT_EQ(wn, buf->size - 1); swoole_coroutine_flock(fd, LOCK_UN); swoole_coroutine_close(fd); }); unlink(test_file); } TEST(coroutine_system, flock_nb) { swoole::coroutine::run([&](void *arg) { DEBUG() << "[thread-1] open" << std::endl; int fd = swoole_coroutine_open(test_file, File::WRITE | File::CREATE, 0666); DEBUG() << "[thread-1] LOCK_EX | LOCK_NB" << std::endl; ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX | LOCK_NB), 0); std::thread t([]() { int fd = open(test_file, File::WRITE | File::CREATE, 0666); DEBUG() << "[thread-2] LOCK_EX | LOCK_NB" << std::endl; ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX), 0); DEBUG() << "[thread-2] LOCK_UN" << std::endl; ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); DEBUG() << "[thread-2] close" << std::endl; swoole_coroutine_close(fd); unlink(test_file); }); DEBUG() << "[thread-1] LOCK_UN" << std::endl; ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); t.join(); }); } TEST(coroutine_system, cancel_sleep) { test::coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); Coroutine::create([co](void *) { System::sleep(0.002); co->cancel(); }); System::sleep(1000); }); } static void test_getaddrinfo( const std::string &host, int family, int type, int protocol, const char *service, double timeout) { std::vector ip_list = System::getaddrinfo(host, family, type, protocol, service, timeout); ASSERT_GT(ip_list.size(), 0); for (auto &ip : ip_list) { ASSERT_TRUE(swoole::network::Address::verify_ip(family, ip)); network::Client c(family == AF_INET ? SW_SOCK_TCP : SW_SOCK_TCP6, false); if (!test::is_github_ci()) { std::cout << ip.c_str() << "\n"; ASSERT_EQ(c.connect(ip.c_str(), 443), SW_OK); } } } TEST(coroutine_system, getaddrinfo) { test::coroutine::run([](void *arg) { test_getaddrinfo(TEST_HTTP_DOMAIN, AF_INET, SOCK_STREAM, 0, "http", -1); test_getaddrinfo(TEST_HTTP_DOMAIN, AF_INET6, SOCK_STREAM, 0, "http", -1); }); } TEST(coroutine_system, getaddrinfo_fail) { test::coroutine::run([](void *arg) { auto ip_list = System::getaddrinfo("w11.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, "http", -1); ASSERT_EQ(ip_list.size(), 0); ASSERT_ERREQ(EAI_NONAME); }); } TEST(coroutine_system, getaddrinfo_timeout) { test::coroutine::run([](void *arg) { auto ip_list = System::getaddrinfo("w12.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, "http", 0.005); ASSERT_EQ(ip_list.size(), 0); ASSERT_ERREQ(SW_ERROR_CO_TIMEDOUT); }); } TEST(coroutine_system, wait_signal) { test::coroutine::run([](void *arg) { Coroutine::create([](void *) { System::sleep(0.002); kill(getpid(), SIGUSR1); }); ASSERT_EQ(System::wait_signal(SIGUSR1, 1.0), SIGUSR1); ASSERT_EQ(System::wait_signal(SIGUSR2, 0.1), -1); }); } TEST(coroutine_system, wait_signal_invalid_signo) { test::coroutine::run([](void *arg) { ASSERT_EQ(System::wait_signal(SW_SIGNO_MAX), SW_ERR); ASSERT_ERREQ(EINVAL); }); } TEST(coroutine_system, wait_signal_fail) { test::coroutine::run([](void *arg) { SwooleG.signal_listener_num = 1; ASSERT_EQ(System::wait_signal(SIGUSR1, 1.0), SW_ERR); ASSERT_ERREQ(EBUSY); SwooleG.signal_listener_num = 0; }); } static const char *GREETING = "hello world, hello swoole"; TEST(coroutine_system, wait_event_readable) { UnixSocket p(true, SOCK_DGRAM); ASSERT_TRUE(p.ready()); test::coroutine::run([&p](void *arg) { Coroutine::create([&p](void *) { System::sleep(0.002); ASSERT_GT(p.write(GREETING, strlen(GREETING)), 0); }); // bad fd EXPECT_EQ(System::wait_event(9999, SW_EVENT_READ, 1), -1); EXPECT_EQ(errno, EBADF); EXPECT_ERREQ(EBADF); // trigger event char buffer[128]; auto pipe_sock = p.get_socket(false); // readable EXPECT_EQ(System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ, 1), SW_EVENT_READ); // readable + writable EXPECT_EQ(System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ | SW_EVENT_WRITE, 1), SW_EVENT_READ | SW_EVENT_WRITE); ssize_t n = pipe_sock->read(buffer, sizeof(buffer)); buffer[n] = 0; EXPECT_EQ(strlen(GREETING), n); EXPECT_STREQ(GREETING, buffer); // timeout auto pipe_sock_2 = p.get_socket(true); EXPECT_EQ(System::wait_event(pipe_sock_2->get_fd(), SW_EVENT_READ, 0.1), -1); EXPECT_EQ(errno, SW_ERROR_CO_TIMEDOUT); EXPECT_ERREQ(SW_ERROR_CO_TIMEDOUT); }); } TEST(coroutine_system, wait_event_writable) { UnixSocket p(true, SOCK_STREAM); ASSERT_TRUE(p.ready()); p.set_blocking(false); p.set_buffer_size(65536); sw_tg_buffer()->clear(); String str(2 * SW_NUM_MILLION); str.append_random_bytes(str.size - 1, false); str.append('\0'); test::coroutine::run([&](void *arg) { Coroutine::create([&](void *) { System::sleep(0.002); auto pipe_sock = p.get_socket(true); char *ptr = str.value(); size_t len = str.get_length(); while (len > 0) { ssize_t retval = pipe_sock->write(ptr, len > 8192 ? 8192 : len); if (retval > 0) { ptr += retval; len -= retval; } else if (retval == 0 || (retval < 0 && errno != EAGAIN)) { break; } System::wait_event(pipe_sock->get_fd(), SW_EVENT_WRITE, 1); } }); auto pipe_sock = p.get_socket(false); auto tg_buf = sw_tg_buffer(); while (tg_buf->length < str.size - 1) { ssize_t retval = pipe_sock->read(tg_buf->str + tg_buf->length, tg_buf->size - tg_buf->length); if (retval > 0) { tg_buf->grow(retval); continue; } else if (retval == 0 && (retval < 0 && errno != EAGAIN)) { break; } System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ, 1); } tg_buf->append('\0'); EXPECT_STREQ(sw_tg_buffer()->value(), str.value()); }); } TEST(coroutine_system, wait_event_fail) { UnixSocket p(true, SOCK_DGRAM); test::coroutine::run([&](void *arg) { ASSERT_EQ(System::wait_event(9999, 0, 1), SW_ERR); ASSERT_ERREQ(EINVAL); ASSERT_EQ(System::wait_event(p.get_socket(true)->get_fd(), SW_EVENT_READ, 0), SW_ERR); ASSERT_ERREQ(ETIMEDOUT); ASSERT_EQ(System::wait_event(p.get_socket(false)->get_fd(), SW_EVENT_WRITE, 0), SW_EVENT_WRITE); ASSERT_EQ(System::wait_event(9999, SW_EVENT_WRITE, 0), -1); ASSERT_ERREQ(EBADF); ASSERT_EQ(System::wait_event(9999, SW_EVENT_WRITE, 1.0), -1); ASSERT_ERREQ(EBADF); }); } TEST(coroutine_system, swoole_stream_select) { UnixSocket p(true, SOCK_STREAM); std::unordered_map fds; fds.emplace(std::make_pair(p.get_socket(false)->fd, swoole::coroutine::PollSocket(SW_EVENT_READ, nullptr))); test::coroutine::run([&](void *arg) { // try timeout to trigger socket_poll_timeout function ASSERT_FALSE(System::socket_poll(fds, 0.5)); }); // start normal process test::coroutine::run([&](void *arg) { std::string text = "Hello world"; size_t len = text.length(); // child pipe Coroutine::create([&](void *) { System::sleep(0.05); auto pipe_sock = p.get_socket(true); const char *ptr = text.c_str(); ASSERT_EQ(pipe_sock->write(ptr, len), len); }); // master pipe bool result = System::socket_poll(fds, 0.5); ASSERT_TRUE(result); char buffer[128]; auto pipe_sock = p.get_socket(false); ssize_t retval = pipe_sock->read(buffer, sizeof(buffer)); buffer[retval] = '\0'; ASSERT_EQ(retval, len); const char *ptr = text.c_str(); ASSERT_STREQ(ptr, buffer); }); } TEST(coroutine_system, timeout_is_zero) { UnixSocket p(true, SOCK_STREAM); std::unordered_map fds; fds.emplace(std::make_pair(p.get_socket(false)->fd, swoole::coroutine::PollSocket(SW_EVENT_READ, nullptr))); // timeout is 0 test::coroutine::run([&](void *arg) { std::string text = "Hello world"; size_t len = text.length(); // child pipe Coroutine::create([&](void *) { auto pipe_sock = p.get_socket(true); const char *ptr = text.c_str(); ASSERT_EQ(pipe_sock->write(ptr, len), len); }); // master pipe bool result = System::socket_poll(fds, 0); ASSERT_TRUE(result); // child pipe Coroutine::create([&](void *) { auto pipe_sock = p.get_socket(true); const char *ptr = text.c_str(); ASSERT_EQ(pipe_sock->write(ptr, len), len); }); // master pipe auto pipe_sock = p.get_socket(false); result = System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ, 0); ASSERT_TRUE(result); }); } TEST(coroutine_system, exec) { test::coroutine::run([](void *arg) { int status; auto buffer = std::shared_ptr(swoole::make_string(1024)); ASSERT_TRUE(System::exec("ls /", true, buffer, &status)); ASSERT_TRUE(buffer->contains(SW_STRL("tmp"))); }); } TEST(coroutine_system, waitpid) { auto pid = spawn_exec([]() { sleep(2000); }); test::coroutine::run([pid](void *arg) { int status; ASSERT_EQ(System::waitpid(pid, &status, WNOHANG, -1), 0); ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), -1); ASSERT_ERREQ(ETIMEDOUT); kill(pid, SIGKILL); System::sleep(0.3); ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), pid); }); } TEST(coroutine_system, waitpid_any) { auto pid = spawn_exec([]() { sleep(2000); }); test::coroutine::run([pid](void *arg) { int status; ASSERT_EQ(System::waitpid(pid, &status, WNOHANG, -1), 0); ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), -1); ASSERT_ERREQ(ETIMEDOUT); kill(pid, SIGKILL); System::sleep(0.3); ASSERT_EQ(System::waitpid(-1, &status, 0, 0.1), pid); }); } TEST(coroutine_system, read_file_fail) { test::coroutine::run([](void *arg) { ASSERT_EQ(System::read_file("/tmp/not-exists", true), nullptr); ASSERT_EQ(errno, ENOENT); }); } TEST(coroutine_system, write_file_fail) { test::coroutine::run([](void *arg) { ASSERT_EQ(System::write_file("/tmp/not-exists/file.log", SW_STRL(TEST_STR)), -1); ASSERT_EQ(errno, ENOENT); }); } ================================================ FILE: core-tests/src/coroutine/uring_socket.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_uring_socket.h" #include "swoole_util.h" #include "swoole_file.h" #include #include #ifdef SW_USE_IOURING using swoole::Coroutine; using swoole::File; using swoole::Iouring; using swoole::Reactor; using swoole::String; using swoole::coroutine::System; using swoole::coroutine::UringSocket; using swoole::network::IOVector; using swoole::test::coroutine; using swoole::test::create_socket_pair; using swoole::test::get_jpg_file; TEST(uring_socket, connect) { coroutine::run([](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.connect(TEST_HTTP_DOMAIN, 80); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); ssize_t rv; auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); rv = sock.send(req.c_str(), req.length()); ASSERT_EQ(rv, req.length()); char buf[4096]; rv = sock.recv(buf, sizeof(buf)); ASSERT_GT(rv, 100); std::string s{buf}; ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); }); } TEST(uring_socket, ssl_connect) { coroutine::run([](void *arg) { UringSocket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); sock.set_tls_host_name(TEST_HTTP_DOMAIN); sock.set_ssl_verify_peer(true); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); bool retval = sock.connect(TEST_HTTP_DOMAIN, 443); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); auto rv = sock.send(req.c_str(), req.length()); ASSERT_EQ(rv, req.length()); ASSERT_TRUE(sock.check_liveness()); swoole::String buf(1024 * 1024); while (true) { char rbuf[16384]; ssize_t nr = sock.recv(rbuf, sizeof(rbuf)); if (nr <= 0) { break; } buf.append(rbuf, nr); } ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); }); } TEST(uring_socket, accept) { const int port = __LINE__ + TEST_PORT; coroutine::run({[port](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); UringSocket *conn = sock.accept(); ASSERT_NE(conn, nullptr); conn->write(TEST_STR, strlen(TEST_STR)); char buf[128]; auto n = conn->recv(buf, sizeof(buf)); ASSERT_EQ(n, strlen(TEST_STR2)); buf[n] = '\0'; ASSERT_STREQ(buf, TEST_STR2); delete conn; }, [port](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); char buf[128]; auto n = sock.read(buf, sizeof(buf)); ASSERT_EQ(n, strlen(TEST_STR)); buf[n] = '\0'; ASSERT_STREQ(buf, TEST_STR); ASSERT_EQ(sock.send(TEST_STR2, strlen(TEST_STR2)), strlen(TEST_STR2)); sock.close(); }}); } TEST(uring_socket, ssl_accept) { const int port = __LINE__ + TEST_PORT; auto jpg_file = get_jpg_file(); File file(jpg_file, File::READ); auto svr = [port, &file](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); sock.enable_ssl_encrypt(); sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/server.crt"); sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/server.key"); sock.set_ssl_dhparam(swoole::test::get_ssl_dir() + "/dhparams.pem"); sock.set_ssl_ecdh_curve("secp256r1"); ASSERT_EQ(sock.listen(128), true); UringSocket *conn = sock.accept(); ASSERT_NE(conn, nullptr); ASSERT_TRUE(conn->ssl_handshake()); ASSERT_EQ(conn->send(EOF_PACKET, strlen(EOF_PACKET)), strlen(EOF_PACKET)); char rbuf[1024]; auto n = conn->recv(rbuf, sizeof(rbuf)); ASSERT_GT(n, 0); rbuf[n] = 0; ASSERT_STREQ(rbuf, EOF_PACKET_2); size_t fsize = file.get_size(); char *jpg = new char[fsize]; size_t nr = 0; while (nr < fsize) { auto ret = conn->recv(jpg + nr, fsize - nr); ASSERT_GT(ret, 0); nr += ret; } auto content = file.read_content(); ASSERT_MEMEQ(jpg, content.get()->value(), fsize); conn->close(); delete conn; }; auto cli = [port, &file](void *arg) { UringSocket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); char rbuf[1024]; auto n = sock.recv(rbuf, sizeof(rbuf)); ASSERT_GT(n, 0); rbuf[n] = 0; ASSERT_STREQ(rbuf, EOF_PACKET); ASSERT_EQ(sock.send(EOF_PACKET_2, strlen(EOF_PACKET_2)), strlen(EOF_PACKET_2)); ASSERT_TRUE(sock.sendfile(file.get_path().c_str(), 0, file.get_size())); sock.close(); }; coroutine::run({svr, cli}); } static void socket_set_length_protocol_1(UringSocket &sock) { sock.protocol = {}; sock.protocol.package_length_type = 'n'; sock.protocol.package_length_size = swoole_type_size(sock.protocol.package_length_type); sock.protocol.package_body_offset = 2; sock.protocol.get_package_length = swoole::Protocol::default_length_func; sock.protocol.package_max_length = 65535; sock.open_length_check = true; } TEST(uring_socket, length_3) { const int port = __LINE__ + TEST_PORT; coroutine::run({[](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); UringSocket *conn = sock.accept(); char buf[1024]; memset(buf, 'A', sizeof(buf)); *(uint16_t *) buf = htons(65530); conn->send(buf, sizeof(buf)); }, [](void *arg) { UringSocket sock(SW_SOCK_TCP); bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); socket_set_length_protocol_1(sock); sock.protocol.package_max_length = 4096; ssize_t l = sock.recv_packet(RECV_TIMEOUT); ASSERT_EQ(l, -1); ASSERT_EQ(sock.errCode, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); }}); } TEST(uring_socket, sendmsg_and_recvmsg) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; const size_t length = text.length(); Coroutine::create([&](void *) { UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); struct msghdr msg; struct iovec ivec; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = (void *) text.c_str(); ivec.iov_len = length; ssize_t ret = sock.sendmsg(&msg, 0); sock.close(); ASSERT_EQ(ret, length); }); UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); struct msghdr msg; struct iovec ivec; char buf[length + 1]; msg.msg_control = nullptr; msg.msg_controllen = 0; msg.msg_flags = 0; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &ivec; msg.msg_iovlen = 1; ivec.iov_base = buf; ivec.iov_len = length; ssize_t ret = sock.recvmsg(&msg, 0); buf[ret] = '\0'; sock.close(); ASSERT_STREQ(buf, text.c_str()); }); } static void test_sendto_recvfrom(enum swSocketType sock_type) { coroutine::run([&](void *arg) { std::string server_text = "hello world!!!"; size_t server_length = server_text.length(); std::string client_text = "hello swoole!!!"; size_t client_length = client_text.length(); const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; const char *local = "localhost"; int port = swoole::test::get_random_port(); UringSocket sock_server(sock_type); UringSocket sock_client(sock_type); sock_server.bind(ip, port); sock_client.bind(ip, port + 1); ON_SCOPE_EXIT { sock_server.close(); sock_client.close(); }; sock_server.sendto(ip, port + 1, (const void *) server_text.c_str(), server_length); char data_from_server[128] = {}; struct sockaddr_in serveraddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(ip); serveraddr.sin_port = htons(port); socklen_t addr_length = sizeof(serveraddr); // receive data from server ssize_t result = sock_client.recvfrom(data_from_server, server_length, (struct sockaddr *) &serveraddr, &addr_length); data_from_server[result] = '\0'; ASSERT_EQ(result, server_length); ASSERT_STREQ(data_from_server, server_text.c_str()); // receive data from client char data_from_client[128] = {}; sock_client.sendto(local, port, (const void *) client_text.c_str(), client_length); result = sock_server.recvfrom(data_from_client, client_length); data_from_client[client_length] = '\0'; ASSERT_EQ(result, client_length); ASSERT_STREQ(data_from_client, client_text.c_str()); }); } TEST(uring_socket, sendto_recvfrom_udp) { test_sendto_recvfrom(SW_SOCK_UDP); test_sendto_recvfrom(SW_SOCK_UDP6); } TEST(uring_socket, writev_and_readv) { coroutine::run([&](void *arg) { int iovcnt = 3; int pairs[2]; std::string text = "Hello World"; size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::unique_ptr iov(new iovec[iovcnt]); for (int i = 0; i < iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), iovcnt); UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.writev(&io_vector); sock.close(); ASSERT_EQ(result, length * 3); }); std::vector results(iovcnt); std::unique_ptr iov(new iovec[iovcnt]); for (int i = 0; i < iovcnt; i++) { iov[i].iov_base = (void *) results[i].c_str(); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), iovcnt); UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.readv(&io_vector); sock.close(); ASSERT_EQ(result, length * 3); for (auto iter = results.begin(); iter != results.end(); iter++) { (*iter)[length] = '\0'; ASSERT_STREQ(text.c_str(), (*iter).c_str()); } }); } TEST(uring_socket, writevall_and_readvall) { coroutine::run([&](void *arg) { int write_iovcnt = 4; int pairs[2]; char buf[65536]; swoole_random_bytes(buf, sizeof(buf)); std::string text(buf, sizeof(buf)); size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::unique_ptr iov(new iovec[write_iovcnt]); for (int i = 0; i < write_iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); iov[i].iov_len = length; } UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_send_buffer_size(sizeof(buf)); IOVector io_vector1((struct iovec *) iov.get(), write_iovcnt); ASSERT_EQ(sock.writev_all(&io_vector1), write_iovcnt * sizeof(buf)); System::sleep(0.01); IOVector io_vector2((struct iovec *) iov.get(), write_iovcnt); ASSERT_EQ(sock.writev_all(&io_vector2), write_iovcnt * sizeof(buf)); sock.close(); }); int read_iovcnt = 8; std::unique_ptr iov(new iovec[read_iovcnt]); for (int i = 0; i < read_iovcnt; i++) { iov[i].iov_base = sw_malloc(length); iov[i].iov_len = length; } IOVector io_vector((struct iovec *) iov.get(), read_iovcnt); UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_recv_buffer_size(sizeof(buf)); ssize_t result = sock.readv_all(&io_vector); sock.close(); ASSERT_EQ(result, length * read_iovcnt); for (int i = 0; i < read_iovcnt; i++) { ASSERT_MEMEQ(iov[i].iov_base, buf, sizeof(buf)); sw_free(iov[i].iov_base); } }); } TEST(uring_socket, sendfile) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { std::string file = swoole::test::get_jpg_file(); UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); bool result = sock.sendfile(file.c_str(), 0, 0); std::cout << sock.errMsg << "\n"; sock.close(); ASSERT_TRUE(result); }); char data[250000]; UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); ssize_t result = sock.read(data, 250000); data[result] = '\0'; sock.close(); ASSERT_GT(result, 0); }); } TEST(uring_socket, send_and_recv_all) { coroutine::run([&](void *arg) { int pairs[2]; String wbuf; wbuf.append_random_bytes(4 * 1024 * 1024, false); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); Coroutine::create([&](void *) { UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_send_buffer_size(65536); ASSERT_EQ(sock.send_all(wbuf.str, wbuf.length), wbuf.length); System::sleep(0.1); sock.close(); }); UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_recv_buffer_size(65536); String rbuf(wbuf.length); ssize_t result = sock.recv_all(rbuf.str, wbuf.length); ASSERT_EQ(result, wbuf.length); ASSERT_MEMEQ(wbuf.str, rbuf.str, wbuf.length); System::sleep(0.1); sock.close(); }); } TEST(uring_socket, poll) { coroutine::run([&](void *arg) { int pairs[2]; String wbuf; wbuf.append_random_bytes(4 * 1024 * 1024, false); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); sock.get_socket()->set_recv_buffer_size(65536); bool rs; rs = sock.poll(SW_EVENT_READ, 0.01); ASSERT_FALSE(rs); ASSERT_EQ(sock.errCode, ETIMEDOUT); TEST_WRITE(pairs[0], TEST_STR); rs = sock.poll(SW_EVENT_READ, 0.01); ASSERT_TRUE(rs); }); } TEST(uring_socket, ssl_readv) { coroutine::run([&](void *arg) { UringSocket client(SW_SOCK_TCP); client.enable_ssl_encrypt(); client.set_tls_host_name(TEST_HTTP_DOMAIN); ASSERT_TRUE(client.connect(TEST_HTTP_DOMAIN, 443)); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); constexpr off_t offset1 = TEST_WRITEV_OFFSET; iovec wr_iov[2]; wr_iov[0].iov_base = (void *) req.c_str(); wr_iov[0].iov_len = offset1; wr_iov[1].iov_base = (char *) req.c_str() + offset1; wr_iov[1].iov_len = req.length() - offset1; swoole::network::IOVector wr_vec(wr_iov, 2); ASSERT_EQ(client.writev(&wr_vec), req.length()); sw_tg_buffer()->clear(); if (sw_tg_buffer()->size < 1024 * 1024) { sw_tg_buffer()->extend(1024 * 1024); } constexpr off_t offset2 = TEST_READV_OFFSET; iovec rd_iov[2]; rd_iov[0].iov_base = sw_tg_buffer()->str; rd_iov[0].iov_len = offset2; rd_iov[1].iov_base = sw_tg_buffer()->str + offset2; rd_iov[1].iov_len = sw_tg_buffer()->size - offset2; swoole::network::IOVector rd_vec(rd_iov, 2); auto rv = client.readv(&rd_vec); ASSERT_GT(rv, 1024); sw_tg_buffer()->length = rv; sw_tg_buffer()->set_null_terminated(); ASSERT_TRUE(sw_tg_buffer()->contains(TEST_HTTPS_EXPECT)); }); } #endif ================================================ FILE: core-tests/src/lock/lock.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_lock.h" #include "swoole_util.h" #include using swoole::Lock; using swoole::RWLock; using swoole::SpinLock; using swoole::Coroutine; using swoole::CoroutineLock; using swoole::Mutex; using swoole::coroutine::System; using swoole::test::coroutine; static void test_func(Lock &lock) { int count = 0; const int N = 100000; auto fn = [&]() { for (int i = 0; i < N; i++) { ASSERT_EQ(lock.lock(), 0); count++; ASSERT_EQ(lock.unlock(), 0); } }; std::thread t1(fn); std::thread t2(fn); t1.join(); t2.join(); ASSERT_EQ(count, N * 2); } static void test_lock_rd_func(Lock &lock) { std::thread t1([&lock]() { ASSERT_EQ(lock.lock(LOCK_SH), 0); usleep(2000); // wait lock.unlock(); }); std::thread t2([&lock]() { usleep(1000); ASSERT_GE(lock.lock(LOCK_SH | LOCK_NB), 0); }); t1.join(); t2.join(); } static void test_share_lock_fun(Lock &lock) { lock.lock(); const int sleep_us = 10000; int magic_num = swoole_rand(100000, 9999999); int *_num = (int *) sw_mem_pool()->alloc(sizeof(int)); *_num = 0; pid_t pid = fork(); if (pid == 0) { lock.lock(); *_num = magic_num; usleep(1); exit(0); } else { usleep(sleep_us); lock.unlock(); int status; pid_t _pid = waitpid(pid, &status, 0); if (_pid != pid) { swoole_warning("error pid=%d", _pid); } ASSERT_EQ(*_num, magic_num); } } TEST(lock, mutex) { Mutex lock(0); test_func(reinterpret_cast(lock)); } TEST(lock, lockwait) { Mutex lock(0); lock.lock(); std::thread t1([&lock]() { long ms1 = swoole::time(); const int TIMEOUT_1 = 2; ASSERT_EQ(lock.lock(LOCK_EX, TIMEOUT_1), ETIMEDOUT); long ms2 = swoole::time(); ASSERT_GE(ms2 - ms1, TIMEOUT_1); const int TIMEOUT_2 = 10; ASSERT_EQ(lock.lock(LOCK_EX, TIMEOUT_2), 0); long ms3 = swoole::time(); ASSERT_LE(ms3 - ms2, TIMEOUT_2); }); std::this_thread::sleep_for(std::chrono::milliseconds(10)); lock.unlock(); t1.join(); } TEST(lock, shared) { Mutex lock(true); test_share_lock_fun(lock); } TEST(lock, try_rd) { Mutex lock(0); test_lock_rd_func(lock); } TEST(lock, coroutine_lock) { auto *lock = new CoroutineLock(false); ASSERT_EQ(lock->lock(), SW_ERROR_CO_OUT_OF_COROUTINE); ASSERT_EQ(lock->unlock(), SW_ERROR_CO_OUT_OF_COROUTINE); coroutine::run([lock](void *arg) { Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(), 0); ASSERT_EQ(lock->lock(), 0); System::sleep(1); ASSERT_EQ(lock->unlock(), 0); }); Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(), 0); System::sleep(1); ASSERT_EQ(lock->unlock(), 0); // unlock 2, no effect ASSERT_EQ(lock->unlock(), 0); }); Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_NB), EBUSY); }); }); delete lock; } #ifndef HAVE_IOURING_FUTEX TEST(lock, coroutine_lock_cancel) { CoroutineLock lock(true); coroutine::run([&](void *arg) { ASSERT_EQ(lock.lock(), 0); Coroutine::create([&](void *) { auto co = Coroutine::get_current(); swoole_timer_after(20, [co](TIMER_PARAMS) { DEBUG() << "cancel coroutine " << co->get_cid() << "\n"; co->cancel(); }); ASSERT_EQ(lock.lock(), SW_ERROR_CO_CANCELED); }); }); } #endif TEST(lock, coroutine_lock_rd) { auto *lock = new CoroutineLock(false); ASSERT_EQ(lock->lock(LOCK_SH), SW_ERROR_CO_OUT_OF_COROUTINE); coroutine::run([lock](void *arg) { Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_SH), 0); ASSERT_EQ(lock->lock(LOCK_SH), 0); System::sleep(0.3); ASSERT_EQ(lock->unlock(), 0); }); Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_SH), 0); System::sleep(0.3); ASSERT_EQ(lock->unlock(), 0); }); Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_SH | LOCK_NB), EBUSY); }); }); delete lock; } #ifdef HAVE_RWLOCK TEST(lock, rwlock_shared) { RWLock lock(true); test_share_lock_fun(lock); } TEST(lock, rwlock) { RWLock lock(false); test_func(lock); } TEST(lock, rwlock_try_rd) { RWLock lock(false); test_lock_rd_func(lock); } TEST(lock, rw_try_wr) { RWLock lock(false); std::thread t1([&lock]() { ASSERT_EQ(lock.lock(), 0); usleep(2000); lock.unlock(); }); std::thread t2([&lock]() { usleep(1000); ASSERT_GT(lock.lock(LOCK_NB), 0); }); t1.join(); t2.join(); } #endif #ifdef HAVE_SPINLOCK TEST(lock, spinlock_shared) { SpinLock lock(true); test_share_lock_fun(lock); } TEST(lock, spinlock) { SpinLock lock(false); test_func(lock); } TEST(lock, spinlock_try_rd) { SpinLock lock(false); test_lock_rd_func(lock); } #endif ================================================ FILE: core-tests/src/main.cpp ================================================ #include "test_core.h" #include "swoole_memory.h" #include #include using namespace swoole; using namespace std; static string root_path; static int *test_counter; static void init_root_path(const char *); int main(int argc, char **argv) { swoole_init(); SwooleG.max_sockets = 20000; init_root_path(argv[0]); if (getenv("DISPLAY_BACKTRACE") != nullptr) { sw_logger()->display_backtrace(); } #ifdef SW_VERBOSE swoole_set_log_level(SW_LOG_TRACE); swoole_set_trace_flags(SW_TRACE_ALL); #endif if (getenv("VERBOSE") != nullptr && std::string(getenv("VERBOSE")) == "0") { swoole_set_log_level(SW_LOG_INFO); test::debug_output = test::null_stream; } test_counter = static_cast(sw_mem_pool()->alloc(sizeof(int) * TEST_COUNTER_NUM)); ::testing::InitGoogleTest(&argc, argv); int retval = RUN_ALL_TESTS(); swoole_clean(); return retval; } static void init_root_path(const char *_exec_file) { char buf[PATH_MAX]; string file; if (_exec_file[0] == '/') { file = _exec_file; } else { char *dir = getcwd(buf, sizeof(buf)); file = string(dir) + "/" + _exec_file; } string relative_root_path = file.substr(0, file.rfind('/')) + "/../"; char *_realpath = realpath(relative_root_path.c_str(), buf); if (_realpath == nullptr) { root_path = relative_root_path; } else { root_path = string(_realpath); } } namespace swoole::test { NullStream null_stream; std::reference_wrapper debug_output(std::cout); void counter_init() { sw_memset_zero(test_counter, sizeof(int) * TEST_COUNTER_NUM); } int *counter_ptr() { return test_counter; } int counter_incr(int index, int add) { return sw_atomic_add_fetch(&test_counter[index], add); } int counter_get(int index) { return test_counter[index]; } void counter_set(int index, int value) { test_counter[index] = value; } void counter_incr_and_put_log(int index, const char *msg) { DEBUG() << "PID: " << getpid() << ", VALUE: " << counter_incr(index) << "; " << msg << std::endl; } /** * swoole-src root path */ const string &get_root_path() { return root_path; } string get_ssl_dir() { return get_root_path() + "/tests/include/ssl_certs"; } string get_jpg_file() { return root_path + TEST_JPG_FILE; } string http_get_request(const string &domain, const string &path) { return "GET " + path + " HTTP/1.1\r\n" "Host: " + domain + "\r\n" "Connection: close\r\n" "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/51.0.2704.106 Safari/537.36" "\r\n\r\n"; } bool is_github_ci() { return getenv("GITHUB_ACTIONS") != nullptr; } int exec_js_script(const std::string &file, const std::string &args) { std::string command = "bash -c 'node " + get_root_path() + "/core-tests/js/" + file + " " + args + "'"; return std::system(command.c_str()); } int get_random_port() { return TEST_PORT + swoole_random_int() % 10000; } bool is_valid_fd(int fd) { return fcntl(fd, F_GETFD) != -1; } int wait_all_child_processes(bool verbose) { pid_t pid; int status; int count = 0; // 循环等待所有子进程结束 while (true) { // 使用waitpid等待任意子进程,这里会阻塞直到有子进程退出 pid = waitpid(-1, &status, 0); if (pid > 0) { // 成功回收一个子进程 count++; // 输出子进程退出状态(如果启用详细输出) if (verbose) { if (WIFEXITED(status)) { std::cout << "子进程 " << pid << " 正常退出,退出码: " << WEXITSTATUS(status) << std::endl; } else if (WIFSIGNALED(status)) { std::cout << "子进程 " << pid << " 被信号 " << WTERMSIG(status) << " 终止"; if (WCOREDUMP(status)) { std::cout << " (核心已转储)"; } std::cout << std::endl; } } } else if (pid < 0) { if (errno == ECHILD) { // 没有子进程了,完成回收 if (verbose) { std::cout << "所有子进程已回收,共 " << count << " 个" << std::endl; } break; } else { // 其他错误 if (verbose) { perror("waitpid failed"); } return -1; } } } return count; } // 检测子进程 int has_child_processes() { pid_t current_pid = getpid(); DIR *proc_dir; struct dirent *entry; char stat_path[512]; FILE *stat_file; char buffer[1024]; pid_t pid, ppid; // 尝试使用waitpid快速检测 if (waitpid(-1, NULL, WNOHANG) == -1 && errno == ECHILD) { return 0; // 没有子进程 } // 如果waitpid没有明确结果,使用/proc检测 proc_dir = opendir("/proc"); if (!proc_dir) { perror("opendir /proc failed"); return -1; } while ((entry = readdir(proc_dir)) != NULL) { if (entry->d_type == DT_DIR && entry->d_name[0] >= '0' && entry->d_name[0] <= '9') { snprintf(stat_path, sizeof(stat_path), "/proc/%s/stat", entry->d_name); stat_file = fopen(stat_path, "r"); if (stat_file) { if (fgets(buffer, sizeof(buffer), stat_file)) { sscanf(buffer, "%d %*s %*c %d", &pid, &ppid); if (ppid == current_pid) { fclose(stat_file); closedir(proc_dir); return 1; // 找到子进程 } } fclose(stat_file); } } } closedir(proc_dir); return 0; // 没有子进程 } // 检测线程 int has_threads() { FILE *status_file; char path[256]; char line[256]; int thread_count = -1; snprintf(path, sizeof(path), "/proc/%d/status", getpid()); status_file = fopen(path, "r"); if (!status_file) { perror("fopen failed"); return -1; } while (fgets(line, sizeof(line), status_file)) { if (strncmp(line, "Threads:", 8) == 0) { sscanf(line, "Threads: %d", &thread_count); break; } } fclose(status_file); return thread_count; } /** * 检查目录是否为空 * @param path 目录路径 * @return 如果目录为空返回1,否则返回0 */ int is_directory_empty(const char *path) { DIR *dir = opendir(path); if (dir == NULL) { perror("opendir"); return 0; } int is_empty = 1; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // 跳过 "." 和 ".." 目录 if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { is_empty = 0; break; } } closedir(dir); return is_empty; } /** * 检查路径是否为目录 * @param path 路径 * @return 如果是目录返回1,否则返回0 */ int is_directory(const char *path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { return 0; } return S_ISDIR(path_stat.st_mode); } /** * 获取父目录路径 * @param path 当前路径 * @param parent_path 用于存储父目录路径的缓冲区 * @param size 缓冲区大小 * @return 成功返回1,失败返回0 */ int get_parent_directory(const char *path, char *parent_path, size_t size) { auto last_slash = strrchr(path, '/'); if (last_slash == NULL || last_slash == path) { // 没有斜杠或者斜杠是第一个字符(根目录) return 0; } size_t parent_length = last_slash - path; if (parent_length >= size) { return 0; } strncpy(parent_path, path, parent_length); parent_path[parent_length] = '\0'; // 处理路径只有一个斜杠的情况 if (parent_length == 0) { parent_path[0] = '/'; parent_path[1] = '\0'; } return 1; } /** * 递归删除空目录 * @param path 要删除的目录路径 * @return 成功删除的目录数量 */ int recursive_rmdir(const char *path) { // 检查路径是否存在且是目录 if (!is_directory(path)) { return 0; } // 检查目录是否为空 if (!is_directory_empty(path)) { return 0; } int deleted_count = 0; // 删除当前空目录 if (rmdir(path) == 0) { deleted_count++; // 获取父目录 char parent_path[PATH_MAX]; if (get_parent_directory(path, parent_path, PATH_MAX)) { // 如果父目录存在且不是当前目录,则尝试删除父目录 if (strcmp(parent_path, path) != 0) { deleted_count += recursive_rmdir(parent_path); } } } return deleted_count; } pid_t spawn_exec(const std::function &fn) { pid_t child_pid = fork(); if (child_pid == -1) { throw std::system_error{errno, std::generic_category()}; } else if (child_pid == 0) { fn(); exit(0); } return child_pid; } int spawn_exec_and_wait(const std::function &fn) { int status; pid_t pid = spawn_exec(fn); if (swoole_waitpid(pid, &status, 0) == pid) { return status; } else { return -1; } } } // namespace swoole::test ================================================ FILE: core-tests/src/memory/buffer.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_memory.h" #include "swoole_buffer.h" using namespace std; using namespace swoole; TEST(buffer, append_iov) { Buffer buf(1024); Buffer buf_for_offset(1024); constexpr int N = 4; int iovcnt = N; iovec v[N]; size_t total_len = 0; SW_LOOP_N(iovcnt) { v[i].iov_len = swoole_rand(99, 4095); total_len += v[i].iov_len; } unique_ptr s1(new char[v[0].iov_len]); unique_ptr s2(new char[v[1].iov_len]); unique_ptr s3(new char[v[2].iov_len]); unique_ptr s4(new char[v[3].iov_len]); v[0].iov_base = s1.get(); v[1].iov_base = s2.get(); v[2].iov_base = s3.get(); v[3].iov_base = s4.get(); memset(v[0].iov_base, 'A', v[0].iov_len); memset(v[1].iov_base, 'B', v[1].iov_len); memset(v[2].iov_base, 'C', v[2].iov_len); memset(v[3].iov_base, 'D', v[3].iov_len); buf.append(v, iovcnt, 0); ASSERT_EQ(buf.length(), total_len); size_t offset = swoole_rand(v[0].iov_len + 1, total_len - 1); buf_for_offset.append(v, iovcnt, offset); ASSERT_EQ(buf_for_offset.length(), total_len - offset); String str(buf_for_offset.length()); while (!buf_for_offset.empty()) { auto chunk = buf_for_offset.front(); str.append(chunk->value.str, chunk->length); buf_for_offset.pop(); } size_t indent = 0; SW_LOOP_N(iovcnt) { if (offset >= v[i].iov_len) { offset -= v[i].iov_len; continue; } ASSERT_EQ(memcmp(str.str + indent, (char *) v[i].iov_base + offset, v[i].iov_len - offset), 0); indent += v[i].iov_len - offset; offset = 0; } } ================================================ FILE: core-tests/src/memory/fixed_pool.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_memory.h" #include "swoole_util.h" using namespace std; TEST(fixed_pool, alloc) { auto *pool = new swoole::FixedPool(1024, 256, false); list alloc_list; ASSERT_EQ(pool->get_slice_size(), 256); for (int i = 0; i < 1200; i++) { int j = rand(); void *mem; if (j % 4 < 3) { mem = pool->alloc(0); ASSERT_TRUE(mem); alloc_list.push_back(mem); } else if (!alloc_list.empty()) { if (j % 2 == 1) { mem = alloc_list.front(); alloc_list.pop_front(); } else { mem = alloc_list.back(); alloc_list.pop_back(); } pool->free(mem); } } pool->debug(1); delete pool; } TEST(fixed_pool, realloc) { void *memory = sw_shm_malloc(1024); void *new_memory = sw_shm_realloc(memory, 2048); ON_SCOPE_EXIT { sw_shm_free(new_memory); }; ASSERT_NE(new_memory, nullptr); } ================================================ FILE: core-tests/src/memory/global_memory.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_memory.h" TEST(global_memory, alloc) { auto pool = new swoole::GlobalMemory(2 * 1024 * 1024, false); char *ptr1 = (char *) pool->alloc(199); pool->free(ptr1); strcpy(ptr1, "hello, world, #1"); char *ptr2 = (char *) pool->alloc(17); strcpy(ptr2, "hello, world, #2"); pool->free(ptr2); char *ptr3 = (char *) pool->alloc(113); strcpy(ptr3, "hello, world, #3"); ASSERT_TRUE(ptr1); ASSERT_TRUE(ptr2); ASSERT_TRUE(ptr3); ASSERT_GT(pool->capacity(), 2 * 1024 * 1024 - 512); ASSERT_GT(pool->get_memory_size(), 0); ASSERT_STREQ(ptr1, "hello, world, #1"); ASSERT_STREQ(ptr2, "hello, world, #2"); ASSERT_STREQ(ptr3, "hello, world, #3"); delete pool; } ================================================ FILE: core-tests/src/memory/lru_cache.cpp ================================================ #include "test_core.h" #include "swoole_lru_cache.h" using namespace swoole; using namespace std; int dtor_num = 0; class lru_cache_test_class { public: lru_cache_test_class() {} ~lru_cache_test_class() { ++dtor_num; } }; TEST(lru_cache, basic) { LRUCache cache(2); shared_ptr val = make_shared("hello"); shared_ptr val1 = make_shared("hello1"); cache.set("test", val); ASSERT_EQ(cache.get("test").get(), val.get()); cache.set("test", val1); ASSERT_EQ(cache.get("test").get(), val1.get()); cache.del("test"); ASSERT_EQ(cache.get("test"), nullptr); cache.set("test", val); ASSERT_EQ(cache.get("test").get(), val.get()); cache.clear(); ASSERT_EQ(cache.get("test"), nullptr); } TEST(lru_cache, memory_free) { LRUCache cache(2); shared_ptr val = make_shared(); cache.set("test", val); ASSERT_EQ(cache.get("test").get(), val.get()); val.reset(); ASSERT_EQ(dtor_num, 0); cache.clear(); ASSERT_EQ(dtor_num, 1); } TEST(lru_cache, lru_kick) { LRUCache cache(2); dtor_num = 0; shared_ptr val = make_shared(); shared_ptr val1 = make_shared(); shared_ptr val2 = make_shared(); shared_ptr val3 = make_shared(); cache.set("test", val); ASSERT_EQ(cache.get("test").get(), val.get()); val.reset(); ASSERT_EQ(dtor_num, 0); cache.set("test1", val1); ASSERT_EQ(cache.get("test1").get(), val1.get()); val1.reset(); ASSERT_EQ(dtor_num, 0); cache.set("test2", val2); ASSERT_EQ(cache.get("test2").get(), val2.get()); val2.reset(); ASSERT_EQ(dtor_num, 1); ASSERT_EQ(cache.get("test"), nullptr); shared_ptr val4 = make_shared(); cache.set("test1", val4); // update test1 and will del test2 ASSERT_EQ(cache.get("test1").get(), val4.get()); ASSERT_EQ(dtor_num, 2); cache.set("test3", val3); ASSERT_EQ(cache.get("test3").get(), val3.get()); val3.reset(); ASSERT_EQ(dtor_num, 3); ASSERT_EQ(cache.get("test2"), nullptr); cache.clear(); ASSERT_EQ(dtor_num, 4); } ================================================ FILE: core-tests/src/memory/ringbuffer.cpp ================================================ #include "test_core.h" #include "swoole_memory.h" #include "swoole_pipe.h" using namespace swoole; #include #define READ_THREAD_N 4 #define WRITE_N 10000 #define PACKET_LEN 90000 //#define PRINT_SERNUM_N 10 static MemoryPool *pool = NULL; typedef struct { uint32_t id; uint32_t size; uint32_t serial_num; void *ptr; } pkg; typedef struct { std::thread *thread; UnixSocket *pipe; } ThreadObject; static void thread_read(int i); static void thread_write(); static ThreadObject threads[READ_THREAD_N]; static void test_ringbuffer(bool shared) { int i; pool = new RingBuffer(1024 * 1024 * 4, shared); ASSERT_NE(nullptr, pool); for (i = 0; i < READ_THREAD_N; i++) { threads[i].pipe = new UnixSocket(true, SOCK_DGRAM); ASSERT_TRUE(threads[i].pipe->ready()); threads[i].thread = new std::thread(thread_read, i); } sleep(1); srand((unsigned int) time(NULL)); thread_write(); for (i = 0; i < READ_THREAD_N; i++) { threads[i].thread->join(); delete threads[i].pipe; delete threads[i].thread; } delete pool; } TEST(ringbuffer, thread) { test_ringbuffer(true); test_ringbuffer(false); } static void thread_write() { uint32_t size, yield_count = 0, yield_total_count = 0; void *ptr; pkg send_pkg; sw_memset_zero(&send_pkg, sizeof(send_pkg)); int i; for (i = 0; i < WRITE_N; i++) { size = 10000 + rand() % PACKET_LEN; // printf("[ < %d] alloc size=%d\n", i, size); yield_count = 0; do { ptr = pool->alloc(size); if (ptr) { break; } else { yield_count++; yield_total_count++; usleep(10); } } while (yield_count < 100); if (!ptr) { swoole_warning("alloc failed. index=%d, break", i); } ASSERT_NE(ptr, nullptr); send_pkg.id = i; send_pkg.ptr = ptr; send_pkg.size = size; send_pkg.serial_num = rand(); //保存长度值 memcpy(ptr, &size, sizeof(size)); //在指针末尾保存一个串号 memcpy((char *) ptr + size - 4, &(send_pkg.serial_num), sizeof(send_pkg.serial_num)); ASSERT_FALSE(threads[i % READ_THREAD_N].pipe->write(&send_pkg, sizeof(send_pkg)) < 0); } // printf("yield_total_count=%d\n", yield_total_count); } static void thread_read(int i) { pkg recv_pkg; uint32_t tmp; int ret; uint32_t recv_count = 0; int j = 0; UnixSocket *sock = threads[i].pipe; int task_n = WRITE_N / READ_THREAD_N; for (j = 0; j < task_n; j++) { ret = sock->read(&recv_pkg, sizeof(recv_pkg)); ASSERT_FALSE(ret < 0); memcpy(&tmp, recv_pkg.ptr, sizeof(tmp)); ASSERT_EQ(tmp, recv_pkg.size); memcpy(&tmp, (char *) recv_pkg.ptr + recv_pkg.size - 4, sizeof(tmp)); ASSERT_EQ(tmp, recv_pkg.serial_num); #ifdef PRINT_SERNUM_N if (j % PRINT_SERNUM_N == 0) { printf("[ > %d] recv. recv_count=%d, serial_num=%d\n", recv_pkg.id, recv_count, tmp); } #endif pool->free(recv_pkg.ptr); recv_count++; } } ================================================ FILE: core-tests/src/memory/table.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_table.h" using namespace swoole; #include #include struct exception_t : public std::exception { int code; std::string msg; exception_t(std::string _msg, int _code) : std::exception() { msg = _msg; code = _code; } const char *what() const throw() { return msg.c_str(); } }; struct row_t { std::string name; long id; double score; }; class table_t { private: TableColumn *column_id; TableColumn *column_name; TableColumn *column_score; Table *table; public: table_t(uint32_t rows_size, float conflict_proportion = 0.2) { table = Table::make(rows_size, conflict_proportion); if (!table) { throw exception_t("alloc failed", swoole_get_last_error()); } EXPECT_TRUE(table->add_column("id", TableColumn::TYPE_INT, 0)); EXPECT_TRUE(table->add_column("name", TableColumn::TYPE_STRING, 32)); EXPECT_TRUE(table->add_column("score", TableColumn::TYPE_FLOAT, 0)); if (!table->create()) { throw exception_t("create failed", swoole_get_last_error()); } column_id = table->get_column("id"); column_name = table->get_column("name"); column_score = table->get_column("score"); } bool set(const std::string &key, const row_t &value) { TableRow *_rowlock = nullptr; TableRow *row = table->set(key.c_str(), key.length(), &_rowlock, nullptr); if (!row) { _rowlock->unlock(); return false; } row->set_value(column_id, (void *) &value.id, sizeof(value.id)); row->set_value(column_name, (void *) value.name.c_str(), value.name.length()); row->set_value(column_score, (void *) &value.score, sizeof(value.score)); _rowlock->unlock(); return true; } row_t get(const std::string &key) { row_t result; TableRow *_rowlock = nullptr; TableRow *row = table->get(key.c_str(), key.length(), &_rowlock); if (row) { memcpy(&result.id, row->data + column_id->index, sizeof(result.id)); memcpy(&result.score, row->data + column_score->index, sizeof(result.score)); TableStringLength l; memcpy(&l, row->data + column_name->index, sizeof(l)); result.name = std::string(row->data + column_name->index + sizeof(l), l); } _rowlock->unlock(); return result; } bool del(const std::string &key) { return table->del(key.c_str(), key.length()); } bool exists(const std::string &key) { TableRow *_rowlock = nullptr; TableRow *row = table->get(key.c_str(), key.length(), &_rowlock); _rowlock->unlock(); return row != nullptr; } size_t count() { return table->count(); } Table *ptr() { return table; } ~table_t() { if (table) { table->destroy(); } } }; TEST(table, create) { table_t table(1024); auto ptr = table.ptr(); ASSERT_GT(ptr->get_memory_size(), ptr->get_size() * ptr->get_column_size()); ASSERT_FALSE(ptr->create()); // create again should fail ASSERT_TRUE(table.set("php", {"php", 1, 1.245})); ASSERT_TRUE(table.set("java", {"java", 2, 3.1415926})); ASSERT_TRUE(table.set("c++", {"c++", 3, 4.888})); ASSERT_EQ(table.count(), 3); row_t r1 = table.get("java"); ASSERT_EQ(r1.id, 2); ASSERT_EQ(r1.score, 3.1415926); ASSERT_EQ(r1.name, std::string("java")); ASSERT_FALSE(ptr->get_column("not-exists")); ASSERT_TRUE(table.exists("php")); ASSERT_TRUE(table.del("php")); ASSERT_FALSE(table.exists("php")); ASSERT_FALSE(table.del("not-exists")); // Test with a string that is longer than the column size ASSERT_TRUE(table.set("golang", {"golang " TEST_JPG_MD5SUM TEST_JPG_MD5SUM, 3, 4.888})); } void start_iterator(Table *_ptr) { _ptr->rewind(); auto count = 0; while (true) { _ptr->forward(); auto row = _ptr->current(); if (row->key_len == 0) { break; } ASSERT_TRUE(_ptr->exists(row->key, row->key_len)); count++; } ASSERT_EQ(count, _ptr->count()); } TEST(table, iterator) { table_t table(1024); table.set("php", {"php", 1, 1.245}); table.set("java", {"java", 2, 3.1415926}); table.set("c++", {"c++", 3, 4.888}); auto _ptr = table.ptr(); start_iterator(_ptr); } TEST(table, iterator_2) { table_t table(1024); auto _ptr = table.ptr(); _ptr->set_hash_func([](const char *key, size_t len) -> uint64_t { return 1; }); table.set("php", {"php", 1, 1.245}); table.set("java", {"java", 2, 3.1415926}); table.set("c++", {"c++", 3, 4.888}); start_iterator(_ptr); } static int test_table_size = 128; static void create_table(table_t &table) { auto ptr = table.ptr(); ptr->set_hash_func([](const char *key, size_t len) -> uint64_t { return 1; }); ASSERT_TRUE(table.set("php", {"php", 1, 1.245})); ASSERT_TRUE(table.set("java", {"java", 2, 3.1415926})); ASSERT_TRUE(table.set("c++", {"c++", 3, 4.888})); ASSERT_TRUE(table.set("js", {"js", 9, 6565})); ASSERT_TRUE(table.set("golang", {"golang", 4, 9.888})); } TEST(table, conflict1) { table_t table(test_table_size); ASSERT_FALSE(table.exists("swift")); create_table(table); auto ptr = table.ptr(); ASSERT_FALSE(table.exists("kotlin")); ASSERT_TRUE(table.del("php")); ASSERT_FALSE(table.exists("php")); ASSERT_TRUE(table.set("rust", {"rust", 5, 9.888})); ASSERT_TRUE(table.del("golang")); ASSERT_FALSE(table.exists("golang")); ASSERT_TRUE(table.set("erlang", {"erlang", 6, 12.888})); ASSERT_TRUE(table.del("java")); ASSERT_FALSE(table.exists("java")); ASSERT_EQ(ptr->get_total_slice_num() - ptr->get_available_slice_num(), table.count() - 1); } TEST(table, conflict2) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); ASSERT_TRUE(table.del("java")); ASSERT_FALSE(table.exists("java")); ASSERT_TRUE(table.set("rust", {"rust", 5, 9.888})); ASSERT_TRUE(table.del("golang")); ASSERT_FALSE(table.exists("golang")); ASSERT_TRUE(table.set("erlang", {"erlang", 6, 12.888})); ASSERT_EQ(ptr->get_total_slice_num() - ptr->get_available_slice_num(), table.count() - 1); } TEST(table, conflict3) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); ASSERT_TRUE(table.del("golang")); ASSERT_TRUE(table.set("erlang", {"erlang", 6, 12.888})); ASSERT_TRUE(table.del("java")); ASSERT_EQ(ptr->get_total_slice_num() - ptr->get_available_slice_num(), table.count() - 1); } TEST(table, conflict4) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); ASSERT_TRUE(table.del("c++")); ASSERT_TRUE(table.set("rust", {"rust", 5, 9.888})); ASSERT_TRUE(table.del("golang")); ASSERT_TRUE(table.set("erlang", {"erlang", 6, 12.888})); ASSERT_TRUE(table.del("java")); ASSERT_EQ(ptr->get_total_slice_num() - ptr->get_available_slice_num(), table.count() - 1); } TEST(table, get_value) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); std::string key("php"); TableRow *_rowlock = nullptr; TableRow *row = ptr->get(key.c_str(), key.length(), &_rowlock); _rowlock->unlock(); TableColumn *column_id = ptr->get_column("id"); TableColumn *column_name = ptr->get_column("name"); TableColumn *column_score = ptr->get_column("score"); char *str = nullptr; TableStringLength len = 0; row->get_value(column_name, &str, &len); ASSERT_STREQ(str, "php"); double dval = 0; row->get_value(column_score, &dval); ASSERT_EQ(dval, 1.245); long lval = 0; row->get_value(column_id, &lval); ASSERT_EQ(lval, 1); column_id->clear(row); column_name->clear(row); column_score->clear(row); row->get_value(column_name, &str, &len); ASSERT_STREQ(str, "php"); row->get_value(column_score, &dval); ASSERT_EQ(dval, 0); row->get_value(column_id, &lval); ASSERT_EQ(lval, 0); } TEST(table, lock) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); std::string key("php"); TableRow *_rowlock = nullptr; for (int i = 0; i <= 3; i++) { std::thread t([&]() { TableRow *row = ptr->get(key.c_str(), key.length(), &_rowlock); TableColumn *column_name = ptr->get_column("name"); char *str = nullptr; TableStringLength len = 0; row->get_value(column_name, &str, &len); ASSERT_STREQ(str, "php"); }); t.join(); } _rowlock->unlock(); } TEST(table, size_limit) { auto t1 = Table::make(0x90000000, 1.2); ASSERT_EQ(t1->get_size(), SW_TABLE_MAX_ROW_SIZE); ASSERT_EQ(t1->get_conflict_proportion(), 1.0); EXPECT_FALSE(t1->add_column("bad_field", (TableColumn::Type) 8, 0)); auto t2 = Table::make(1024, 0.1); ASSERT_EQ(t2->get_size(), 1024); ASSERT_EQ(t2->get_conflict_proportion(), (float) SW_TABLE_CONFLICT_PROPORTION); } TEST(table, lock_crash) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); auto child = test::spawn_exec([ptr]() { TableRow *_rowlock = nullptr; ptr->get("java", 4, &_rowlock); usleep(5); exit(200); // Simulate a crash in the child process, no release lock }); ASSERT_GT(child, 0); test::wait_all_child_processes(); TableRow *_rowlock = nullptr; ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); _rowlock->unlock(); } TEST(table, lock_race) { table_t table(test_table_size); create_table(table); auto ptr = table.ptr(); auto child = test::spawn_exec([ptr]() { TableRow *_rowlock = nullptr; ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); usleep(5); _rowlock->unlock(); }); ASSERT_GT(child, 0); TableRow *_rowlock = nullptr; ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); _rowlock->unlock(); test::wait_all_child_processes(); } ================================================ FILE: core-tests/src/network/address.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" using swoole::network::Address; TEST(address, basic) { Address address{}; ASSERT_TRUE(address.empty()); ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU, 80, true)); address.set_port(443); ASSERT_EQ(address.get_port(), 443); ASSERT_TRUE(address.assign(SW_SOCK_TCP6, TEST_HTTP_DOMAIN, 80, true)); address.set_port(9501); ASSERT_EQ(address.get_port(), 9501); } TEST(address, dns_fail) { Address address{}; ASSERT_FALSE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU "not-exists", 80, true)); ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); } TEST(address, path_to_long) { Address address{}; swoole::String path; path.repeat("HELLO", 5, 128); ASSERT_FALSE(address.assign(SW_SOCK_UNIX_DGRAM, path.to_std_string())); ASSERT_ERREQ(SW_ERROR_NAME_TOO_LONG); } TEST(address, bad_type) { Address address{}; ASSERT_FALSE(address.assign((swSocketType) (SW_SOCK_RAW6 + 9), TEST_DOMAIN_BAIDU)); ASSERT_ERREQ(SW_ERROR_BAD_SOCKET_TYPE); } TEST(address, type_str) { ASSERT_STREQ(Address::type_str(SW_SOCK_TCP), "IPv4"); ASSERT_STREQ(Address::type_str(SW_SOCK_UNIX_STREAM), "UnixSocket"); ASSERT_STREQ(Address::type_str(SW_SOCK_TCP6), "IPv6"); ASSERT_STREQ(Address::type_str((swSocketType) (SW_SOCK_RAW6 + 9)), "Unknown"); } TEST(address, is_loopback_addr) { Address address{}; ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU, 80, true)); ASSERT_FALSE(address.is_loopback_addr()); ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_HOST, 80, true)); ASSERT_TRUE(address.is_loopback_addr()); ASSERT_TRUE(address.assign(SW_SOCK_TCP6, "::1", 80, true)); ASSERT_TRUE(address.is_loopback_addr()); ASSERT_TRUE(address.assign(SW_SOCK_TCP6, TEST_HTTP_DOMAIN, 443, true)); ASSERT_FALSE(address.is_loopback_addr()); ASSERT_TRUE(address.assign(SW_SOCK_UNIX_DGRAM, TEST_LOG_FILE)); ASSERT_FALSE(address.is_loopback_addr()); } TEST(address, ipv4_addr) { auto sock = swoole::make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); Address addr; ASSERT_TRUE(addr.assign("tcp://127.0.0.1:12345")); ASSERT_EQ(sock->connect(addr), SW_ERR); ASSERT_EQ(errno, ECONNREFUSED); ASSERT_TRUE(addr.assign("tcp://localhost:12345")); ASSERT_EQ(sock->connect(addr), SW_ERR); ASSERT_EQ(errno, ECONNREFUSED); sock->free(); } TEST(address, ipv6_addr) { auto sock = swoole::make_socket(SW_SOCK_TCP6, SW_FD_STREAM, 0); Address addr; ASSERT_TRUE(addr.assign("tcp://[::1]:12345")); ASSERT_EQ(sock->connect(addr), SW_ERR); ASSERT_EQ(errno, ECONNREFUSED); ASSERT_TRUE(addr.assign("tcp://[ip6-localhost]:12345")); ASSERT_EQ(sock->connect(addr), SW_ERR); ASSERT_EQ(errno, ECONNREFUSED); sock->free(); } TEST(address, unix_addr) { auto sock = swoole::make_socket(SW_SOCK_UNIX_STREAM, SW_FD_STREAM, 0); Address addr; ASSERT_TRUE(addr.assign("unix:///tmp/swoole-not-exists.sock")); ASSERT_EQ(sock->connect(addr), SW_ERR); ASSERT_EQ(errno, ENOENT); sock->free(); } TEST(address, bad_addr) { Address addr; ASSERT_FALSE(addr.assign("test://[::1]:12345")); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_BAD_HOST_ADDR); uchar buf[16]; ASSERT_EQ(Address::addr_str(AF_INET6 + 9, buf), nullptr); ASSERT_EQ(errno, EAFNOSUPPORT); } TEST(address, bad_port) { Address addr; ASSERT_FALSE(addr.assign("tcp://[::1]:92345")); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_BAD_PORT); } TEST(address, loopback_addr) { Address addr1; addr1.assign(SW_SOCK_TCP, "127.0.0.1", 0); ASSERT_TRUE(addr1.is_loopback_addr()); Address addr2; addr2.assign(SW_SOCK_TCP6, "::1", 0); ASSERT_TRUE(addr1.is_loopback_addr()); Address addr3; addr3.assign(SW_SOCK_TCP, "192.168.1.2", 0); ASSERT_FALSE(addr3.is_loopback_addr()); Address addr4; addr4.assign(SW_SOCK_TCP6, "192::66::88", 0); ASSERT_FALSE(addr4.is_loopback_addr()); } ================================================ FILE: core-tests/src/network/client.cpp ================================================ #include "test_core.h" #include "test_server.h" #include "test_process.h" #include "core-tests/include/test_core.h" #include #include #include #define GREETER "Hello Swoole" #define GREETER_SIZE sizeof(GREETER) using swoole::HttpProxy; using swoole::Mutex; using swoole::Pipe; using swoole::Socks5Proxy; using swoole::String; using swoole::network::Address; using swoole::network::AsyncClient; using swoole::network::Client; using swoole::network::Socket; using swoole::network::SyncClient; using swoole::test::Process; using swoole::test::Server; TEST(client, tcp) { int ret; char buf[128]; pid_t pid; int port = swoole::test::get_random_port(); Process proc([port](Process *proc) { Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); serv.on("Receive", [](ON_RECEIVE_PARAMS) { SERVER_THIS->send(req->info.fd, req->data, req->info.len); return 0; }); serv.start(); }); pid = proc.start(); usleep(300000); // wait for the test server to start Client cli(SW_SOCK_TCP, false); ASSERT_NE(cli.socket, nullptr); ret = cli.connect(TEST_HOST, port, -1, 0); ASSERT_EQ(ret, 0); ret = cli.send(SW_STRS(GREETER), 0); ASSERT_GT(ret, 0); ret = cli.recv(buf, 128, 0); ASSERT_EQ(ret, GREETER_SIZE); ASSERT_STREQ(GREETER, buf); Address peer_name; ASSERT_EQ(cli.get_peer_name(&peer_name), 0); ASSERT_STREQ(peer_name.get_addr(), "127.0.0.1"); ASSERT_EQ(peer_name.get_port(), port); ASSERT_EQ(cli.close(), SW_OK); ASSERT_EQ(cli.close(), SW_ERR); kill(pid, SIGTERM); int status; wait(&status); } static void test_sync_client_dgram(const char *host, int port, enum swSocketType type) { int ret; char buf[128]; pid_t pid; Mutex *lock = new Mutex(true); lock->lock(); Process proc([&](Process *proc) { Server serv(host, port, swoole::Server::MODE_BASE, type); serv.on("Packet", [](ON_PACKET_PARAMS) -> int { swoole::DgramPacket *packet = (swoole::DgramPacket *) req->data; SERVER_THIS->sendto(packet->socket_addr, packet->data, packet->length, req->info.server_fd); return 0; }); serv.on("Start", [lock](ON_START_PARAMS) { lock->unlock(); }); serv.start(); }); pid = proc.start(); lock->lock(); Client cli(type, false); ASSERT_NE(cli.socket, nullptr); ret = cli.connect(host, port, -1, 0); ASSERT_EQ(ret, 0); ret = cli.send(SW_STRS(GREETER), 0); ASSERT_GT(ret, 0); ret = cli.recv(buf, 128, 0); ASSERT_EQ(ret, GREETER_SIZE); ASSERT_STREQ(GREETER, buf); kill(pid, SIGTERM); int status; wait(&status); } TEST(client, udp) { int port = swoole::test::get_random_port(); test_sync_client_dgram("127.0.0.1", port, SW_SOCK_UDP); } TEST(client, udp6) { int port = swoole::test::get_random_port(); test_sync_client_dgram("::1", port, SW_SOCK_UDP6); } TEST(client, udg) { test_sync_client_dgram("/tmp/swoole_core_tests.sock", 0, SW_SOCK_UNIX_DGRAM); } static void test_async_client_tcp(const char *host, int port, enum swSocketType type) { pid_t pid; Pipe p(true); ASSERT_TRUE(p.ready()); Process proc([&](Process *proc) { Server serv(Socket::is_inet6(type) ? TEST_HOST6 : TEST_HOST, port, swoole::Server::MODE_BASE, type); serv.set_private_data("pipe", &p); serv.on("Receive", [](ON_RECEIVE_PARAMS) { SERVER_THIS->send(req->info.fd, req->data, req->info.len); return 0; }); serv.on("WorkerStart", [](ON_WORKER_START_PARAMS) { Pipe *p = (Pipe *) SERVER_THIS->get_private_data("pipe"); int64_t value = 1; p->write(&value, sizeof(value)); }); serv.start(); }); pid = proc.start(); int64_t value; p.set_timeout(10); p.read(&value, sizeof(value)); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); AsyncClient ac(type); ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); ac.on_close([](AsyncClient *ac) {}); ac.on_error([](AsyncClient *ac) {}); ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }); bool retval = ac.connect(host, port, 1.0); EXPECT_TRUE(retval); swoole_event_wait(); kill(pid, SIGTERM); int status; wait(&status); } TEST(client, async_tcp) { test_async_client_tcp(TEST_HOST, swoole::test::get_random_port(), SW_SOCK_TCP); } TEST(client, async_tcp_dns) { test_async_client_tcp("localhost", swoole::test::get_random_port(), SW_SOCK_TCP); } TEST(client, async_tcp6) { test_async_client_tcp("::1", swoole::test::get_random_port(), SW_SOCK_TCP6); } TEST(client, async_tcp6_dns) { test_async_client_tcp("localhost", swoole::test::get_random_port(), SW_SOCK_TCP6); } TEST(client, async_tcp_dns_fail) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client ac(SW_SOCK_TCP, true); ASSERT_EQ(ac.connect(TEST_HOST, 9999), SW_ERR); bool success = true; ac.onConnect = [&success](Client *ac) { ac->send(SW_STRS(GREETER)); success = true; }; ac.onClose = [](Client *ac) {}; ac.onError = [&success](Client *ac) { DEBUG() << "connect failed, ERROR: " << errno << "\n"; ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); success = false; }; ac.onReceive = [](Client *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }; ASSERT_EQ(ac.connect("www.baidu.com-not-found", 80, 1.0), SW_OK); swoole_event_wait(); ASSERT_FALSE(success); } TEST(client, async_tcp_ssl_handshake_fail) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client ac(SW_SOCK_TCP, true); bool success = true; ac.onConnect = [&success](Client *ac) { ac->send(SW_STRS(GREETER)); success = true; }; ac.onClose = [](Client *ac) {}; ac.onError = [&success](Client *ac) { DEBUG() << "connect failed, ERROR: " << errno << "\n"; ASSERT_ERREQ(SW_ERROR_SSL_HANDSHAKE_FAILED); success = false; }; ac.onReceive = [](Client *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }; ac.enable_ssl_encrypt(); ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); swoole_event_wait(); ASSERT_FALSE(success); } TEST(client, async_tcp_http_proxy_handshake_fail) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client ac(SW_SOCK_TCP, true); bool success = true; ac.onConnect = [&success](Client *ac) { ac->send(SW_STRS(GREETER)); success = true; }; ac.onClose = [](Client *ac) {}; ac.onError = [&success](Client *ac) { DEBUG() << "connect failed, ERROR: " << errno << "\n"; ASSERT_ERREQ(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); success = false; }; ac.onReceive = [](Client *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }; ac.set_http_proxy("www.baidu.com", 80); ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); swoole_event_wait(); } TEST(client, async_tcp_socks5_proxy_handshake_fail) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client ac(SW_SOCK_TCP, true); bool success = true; ac.onConnect = [&success](Client *ac) { ac->send(SW_STRS(GREETER)); success = true; }; ac.onClose = [](Client *ac) {}; ac.onError = [&success](Client *ac) { DEBUG() << "connect failed, ERROR: " << errno << "\n"; ASSERT_ERREQ(ETIMEDOUT); success = false; }; ac.onReceive = [](Client *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }; ac.set_socks5_proxy("www.baidu.com", 80); ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); swoole_event_wait(); } TEST(client, sleep) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); String buf(65536); auto domain = TEST_HTTP_DOMAIN; Client client(SW_SOCK_TCP, true); client.onConnect = [&domain](Client *cli) { cli->sleep(); swoole_timer_after(200, [cli, &domain](auto _1, auto _2) { auto req = swoole::test::http_get_request(domain, "/"); cli->send(req.c_str(), req.length(), 0); cli->wakeup(); }); }; client.onError = [](Client *cli) {}; client.onClose = [](Client *cli) {}; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; ASSERT_EQ(client.connect(domain, 80, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(buf.contains(TEST_HTTP_EXPECT)); } TEST(client, sleep_2) { auto port = __LINE__ + TEST_PORT; auto server_pid = swoole::test::spawn_exec([port]() { Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); serv.on("Receive", [](ON_RECEIVE_PARAMS) { usleep(10000); return SW_OK; }); serv.on("workerStart", [](ON_WORKER_START_PARAMS) { DEBUG() << "Worker started, PID: " << getpid() << "\n"; }); serv.start(); }); ASSERT_GT(server_pid, 0); swoole::test::counter_init(); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); String buf(65536); String wbuf(8 * 1024 * 1024); wbuf.append_random_bytes(wbuf.size); Client client(SW_SOCK_TCP, true); client.buffer_high_watermark = 1024 * 1024; client.buffer_low_watermark = 32 * 1024; client.onBufferFull = [](Client *cli) { DEBUG() << "Buffer is full, waiting for data to be sent...\n"; swoole::test::counter_incr(0); }; client.onBufferEmpty = [server_pid](Client *cli) { DEBUG() << "Buffer is empty, ready to send more data...\n"; swoole::test::counter_incr(1); swoole_timer_after(200, [cli, server_pid](auto _1, auto _2) { cli->close(); DEBUG() << "Client closed, terminating server...\n"; kill(server_pid, SIGTERM); }); }; client.onConnect = [&wbuf, server_pid](Client *cli) { DEBUG() << "Client connected, sending data...\n"; EXPECT_EQ(cli->send(wbuf.str, wbuf.length), wbuf.length); EXPECT_EQ(cli->send(wbuf.str, wbuf.length), -1); ASSERT_ERREQ(SW_ERROR_OUTPUT_BUFFER_OVERFLOW); swoole_timer_after(10, [cli, server_pid](auto _1, auto _2) { cli->sleep(); DEBUG() << "Client is sleeping...\n"; swoole_timer_after(15, [cli](auto _1, auto _2) { cli->wakeup(); DEBUG() << "Client woke up, closing connection...\n"; }); }); }; client.onError = [](Client *cli) { DEBUG() << "Client error occurred, ERROR: " << swoole_get_last_error() << "\n"; }; client.onClose = [](Client *cli) { DEBUG() << "Client connection closed.\n"; }; client.onReceive = [](Client *cli, const char *data, size_t length) { DEBUG() << "Client received data, length: " << length << "\n"; }; ASSERT_EQ(client.connect(TEST_HOST, port, -1, 0), 0); swoole_event_wait(); swoole::test::wait_all_child_processes(); ASSERT_GE(swoole::test::counter_get(0), 1); ASSERT_GE(swoole::test::counter_get(1), 1); } TEST(client, connect_refuse) { int ret; Client cli(SW_SOCK_TCP, false); ret = cli.connect(TEST_HOST, swoole::test::get_random_port(), -1, 0); ASSERT_EQ(ret, -1); ASSERT_EQ(swoole_get_last_error(), ECONNREFUSED); } TEST(client, bind) { Client cli(SW_SOCK_TCP, false); ASSERT_EQ(cli.bind("127.0.0.1", 9999), SW_OK); ASSERT_EQ(cli.bind("192.0.0.1", 9999), SW_ERR); ASSERT_ERREQ(EADDRNOTAVAIL); ASSERT_EQ(cli.bind("127.0.0.1", 80), SW_ERR); if (swoole::test::is_github_ci()) { ASSERT_ERREQ(EINVAL); } else { ASSERT_ERREQ(EACCES); } } // DNS 报文头部结构 struct DNSHeader { uint16_t id; // 标识符 uint16_t flags; // 各种标志 uint16_t qdcount; // 问题数量 uint16_t ancount; // 回答数量 uint16_t nscount; // 授权记录数量 uint16_t arcount; // 附加记录数量 }; // 将域名转换为 DNS 格式 std::vector encodeDomainName(const std::string &domain) { std::vector result; std::string label; for (char c : domain) { if (c == '.') { result.push_back(static_cast(label.length())); for (char lc : label) { result.push_back(static_cast(lc)); } label.clear(); } else { label += c; } } // 处理最后一个标签 if (!label.empty()) { result.push_back(static_cast(label.length())); for (char lc : label) { result.push_back(static_cast(lc)); } } // 添加结束符 result.push_back(0); return result; } // 构建 DNS 查询报文 std::vector buildDNSQuery(const std::string &domain, uint16_t recordType = 1) { std::vector query; // 生成随机 ID std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution dist(0, 65535); uint16_t transactionId = dist(gen); // 构建 DNS 头部 DNSHeader header; header.id = htons(transactionId); // 网络字节序 header.flags = htons(0x0100); // RD=1, 其余为0 header.qdcount = htons(1); // 1个问题 header.ancount = htons(0); // 0个回答 header.nscount = htons(0); // 0个授权记录 header.arcount = htons(0); // 0个附加记录 // 将头部添加到查询报文 uint8_t *headerPtr = reinterpret_cast(&header); query.insert(query.end(), headerPtr, headerPtr + sizeof(DNSHeader)); // 添加问题部分 - 域名 std::vector qname = encodeDomainName(domain); query.insert(query.end(), qname.begin(), qname.end()); // 添加问题部分 - 查询类型和查询类 uint16_t qtype = htons(recordType); // 查询类型(如A记录=1) uint16_t qclass = htons(1); // 查询类(IN=1) uint8_t *qtypePtr = reinterpret_cast(&qtype); uint8_t *qclassPtr = reinterpret_cast(&qclass); query.insert(query.end(), qtypePtr, qtypePtr + sizeof(uint16_t)); query.insert(query.end(), qclassPtr, qclassPtr + sizeof(uint16_t)); return query; } // 将二进制数据转换为十六进制字符串 std::string bytesToHexString(const std::vector &data) { std::stringstream ss; for (size_t i = 0; i < data.size(); ++i) { ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i]); if (i < data.size() - 1) { ss << " "; } } return ss.str(); } TEST(client, sendto) { Client cli(SW_SOCK_TCP, false); ASSERT_EQ(cli.sendto("127.0.0.1", 9999, SW_STRL(TEST_STR)), SW_ERR); ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); auto dns_server = swoole_get_dns_server(); Client dsock(SW_SOCK_UDP, false); auto dnsQuery = buildDNSQuery("www.baidu.com"); ASSERT_EQ(dsock.sendto(dns_server.host, dns_server.port, (const char *) dnsQuery.data(), dnsQuery.size()), SW_OK); ASSERT_GT(dsock.recv(sw_tg_buffer()->str, sw_tg_buffer()->size), 0); Address ra; ASSERT_EQ(dsock.get_peer_name(&ra), SW_OK); ASSERT_STREQ(ra.get_addr(), dns_server.host.c_str()); ASSERT_EQ(ra.get_port(), dns_server.port); Client cli2(SW_SOCK_UDP, false); ASSERT_EQ(cli2.sendto("www.baidu.com-not-exists", 9999, SW_STRL(TEST_STR)), SW_ERR); ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); Client cli3(SW_SOCK_UNIX_DGRAM, false); ASSERT_EQ(cli3.sendto("/tmp/swoole.sock", 0, SW_STRL(TEST_STR)), SW_ERR); ASSERT_ERREQ(ENOENT); } TEST(client, async_unix_connect_refuse) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); std::unordered_map flags; AsyncClient ac(SW_SOCK_UNIX_DGRAM); ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); ac.on_close([](AsyncClient *ac) {}); ac.on_error([&](AsyncClient *ac) { flags["onError"] = true; }); ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }); bool retval = ac.connect("/tmp/swoole-not-exists.sock", 0); ASSERT_EQ(retval, false); ASSERT_TRUE(flags["onError"]); ASSERT_EQ(errno, ENOENT); swoole_event_wait(); } TEST(client, async_connect_timeout) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); std::unordered_map flags; AsyncClient ac(SW_SOCK_TCP); ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); ac.on_close([](AsyncClient *ac) {}); ac.on_error([&](AsyncClient *ac) { flags["onError"] = true; ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); }); ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }); ASSERT_TRUE(ac.connect("192.168.1.199", 19999, 0.2)); swoole_event_wait(); ASSERT_TRUE(flags["onError"]); } static void test_async_client_dgram(const char *host, int port, enum swSocketType type) { pid_t pid; Mutex *lock = new Mutex(true); lock->lock(); Process proc([&](Process *proc) { Server serv(host, port, swoole::Server::MODE_BASE, type); serv.on("Packet", [](ON_PACKET_PARAMS) -> int { swoole::DgramPacket *packet = (swoole::DgramPacket *) req->data; SERVER_THIS->sendto(packet->socket_addr, packet->data, packet->length, req->info.server_fd); return 0; }); serv.on("Start", [lock](ON_START_PARAMS) { lock->unlock(); }); serv.start(); }); pid = proc.start(); lock->lock(); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); std::unordered_map flags; AsyncClient ac(type); ac.on_connect([&](AsyncClient *ac) { flags["onConnect"] = true; ac->send(SW_STRS(GREETER)); }); ac.on_close([&](AsyncClient *ac) { flags["onClose"] = true; }); ac.on_error([&](AsyncClient *ac) { flags["onError"] = true; ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); }); ac.on_receive([&](AsyncClient *ac, const char *data, size_t len) { flags["onReceive"] = true; ASSERT_EQ(len, GREETER_SIZE); ASSERT_STREQ(GREETER, data); ac->close(); }); ASSERT_TRUE(ac.connect(host, port, 0.2)); swoole_event_wait(); kill(pid, SIGTERM); int status; wait(&status); ASSERT_TRUE(flags["onConnect"]); ASSERT_TRUE(flags["onReceive"]); ASSERT_TRUE(flags["onClose"]); ASSERT_FALSE(flags["onError"]); } TEST(client, async_udp) { test_async_client_dgram(TEST_HOST, swoole::test::get_random_port(), SW_SOCK_UDP); } TEST(client, async_udp_dns) { test_async_client_dgram("localhost", swoole::test::get_random_port(), SW_SOCK_UDP); } TEST(client, async_udp6) { test_async_client_dgram("::1", swoole::test::get_random_port(), SW_SOCK_UDP6); } TEST(client, connect_timeout) { int ret; Client cli(SW_SOCK_TCP, false); ret = cli.connect("19.168.0.99", swoole::test::get_random_port(), 0.2, 0); ASSERT_EQ(ret, -1); ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); } TEST(client, shutdown_write) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); ASSERT_EQ(cli.shutdown(SHUT_WR), 0); ASSERT_EQ(cli.shutdown(SHUT_WR), SW_ERR); // already shutdown ssize_t retval = cli.send(SW_STRL("hello world"), 0); ASSERT_EQ(retval, -1); ASSERT_EQ(swoole_get_last_error(), EPIPE); } TEST(client, shutdown_read) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); ASSERT_EQ(cli.shutdown(SHUT_RD), SW_OK); ASSERT_EQ(cli.shutdown(SHUT_RD), SW_ERR); // already shutdown ssize_t retval = cli.send(SW_STRL("hello world\r\n\r\n"), 0); ASSERT_GT(retval, 0); char buf[1024]; retval = cli.recv(buf, sizeof(buf), 0); ASSERT_EQ(retval, 0); } TEST(client, shutdown_all) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); ASSERT_EQ(cli.shutdown(SHUT_RDWR), SW_OK); ASSERT_EQ(cli.shutdown(SHUT_RDWR + 99), SW_ERR); ASSERT_ERREQ(EINVAL); ssize_t retval = cli.send(SW_STRL("hello world\r\n\r\n"), 0); ASSERT_EQ(retval, -1); ASSERT_EQ(swoole_get_last_error(), EPIPE); char buf[1024]; retval = cli.recv(buf, sizeof(buf), 0); ASSERT_EQ(retval, 0); } static void test_ssl_http_get() { bool connected = false; bool closed = false; String buf(65536); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client client(SW_SOCK_TCP, true); ASSERT_EQ(client.enable_ssl_encrypt(), SW_OK); ASSERT_EQ(client.enable_ssl_encrypt(), SW_ERR); // already enabled client.onConnect = [&connected](Client *cli) { connected = true; auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); cli->send(req.c_str(), req.length(), 0); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; ASSERT_EQ(client.connect(TEST_HTTP_DOMAIN, 443, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(connected); ASSERT_TRUE(closed); ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); } TEST(client, ssl_1) { test_ssl_http_get(); } TEST(client, ssl_sendfile) { bool connected = false; bool closed = false; String buf(65536); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); auto file = swoole::make_tmpfile(); file.write(SW_STRL(TEST_REQUEST_BAIDU)); Client client(SW_SOCK_TCP, true); client.enable_ssl_encrypt(); client.onConnect = [&connected, &file](Client *cli) { connected = true; cli->sendfile(file.get_path().c_str(), 0, file.get_size()); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, 443, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(connected); ASSERT_TRUE(closed); ASSERT_TRUE(buf.contains("Baidu")); } TEST(client, sync_ssl_sendfile) { auto file = swoole::make_tmpfile(); file.write(SW_STRL(TEST_REQUEST_BAIDU)); SyncClient client(SW_SOCK_TCP); ASSERT_TRUE(client.connect(TEST_DOMAIN_BAIDU, 443, -1)); ASSERT_TRUE(client.enable_ssl_encrypt()); ASSERT_TRUE(client.sendfile(file.get_path().c_str())); String buf(65536); while (true) { ssize_t nr = client.recv(buf.str, buf.size - buf.length); if (nr <= 0) { break; } buf.grow(nr); } client.close(); ASSERT_TRUE(buf.contains("baidu.com")); unlink(file.get_path().c_str()); } static void proxy_async_test(Client &client, bool https) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); bool connected = false; bool closed = false; String buf(65536); if (https) { client.enable_ssl_encrypt(); } client.onConnect = [&connected](Client *cli) { connected = true; cli->send(SW_STRL(TEST_REQUEST_BAIDU), 0); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, https ? 443 : 80, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(connected); ASSERT_TRUE(closed); ASSERT_TRUE(buf.contains("www.baidu.com")); } static void proxy_sync_test(Client &client, bool https) { String buf(65536); if (https) { client.enable_ssl_encrypt(); } std::string host = TEST_DOMAIN_BAIDU; if (client.socks5_proxy && !client.socks5_proxy->dns_tunnel) { host = swoole::network::gethostbyname(AF_INET, host); DEBUG() << "Resolved domain " << TEST_DOMAIN_BAIDU << " to " << host << "\n"; } ASSERT_EQ(client.connect(host.c_str(), https ? 443 : 80, -1, 0), 0); ASSERT_GT(client.send(SW_STRL(TEST_REQUEST_BAIDU), 0), 0); while (true) { char rbuf[4096]; auto nr = client.recv(rbuf, sizeof(rbuf), 0); if (nr <= 0) { break; } buf.append(rbuf, nr); } ASSERT_TRUE(buf.contains("www.baidu.com")); } static void proxy_set_socks5_proxy(Client &client) { std::string username = std::string(TEST_SOCKS5_PROXY_USER); std::string password = std::string(TEST_SOCKS5_PROXY_PASSWORD); client.set_socks5_proxy(TEST_SOCKS5_PROXY_HOST, TEST_SOCKS5_PROXY_PORT, username, password); } static void proxy_set_http_proxy(Client &client) { std::string username, password; if (swoole::test::is_github_ci()) { username = std::string(TEST_HTTP_PROXY_USER); password = std::string(TEST_HTTP_PROXY_PASSWORD); } client.set_http_proxy(TEST_HTTP_PROXY_HOST, TEST_HTTP_PROXY_PORT, username, password); } TEST(client, https_get_async_with_http_proxy) { Client client(SW_SOCK_TCP, true); proxy_set_http_proxy(client); proxy_async_test(client, true); } TEST(client, https_get_async_with_socks5_proxy) { Client client(SW_SOCK_TCP, true); proxy_set_socks5_proxy(client); proxy_async_test(client, true); } TEST(client, https_get_sync_with_http_proxy) { Client client(SW_SOCK_TCP, false); proxy_set_http_proxy(client); proxy_sync_test(client, true); } TEST(client, https_get_sync_with_socks5_proxy) { Client client(SW_SOCK_TCP, false); proxy_set_socks5_proxy(client); proxy_sync_test(client, true); } TEST(client, http_get_sync_with_socks5_proxy_no_dns_tunnel) { Client client(SW_SOCK_TCP, false); proxy_set_socks5_proxy(client); client.socks5_proxy->dns_tunnel = 0; proxy_sync_test(client, false); } TEST(client, http_get_async_with_http_proxy) { Client client(SW_SOCK_TCP, true); proxy_set_http_proxy(client); proxy_async_test(client, false); } TEST(client, http_get_async_with_socks5_proxy) { Client client(SW_SOCK_TCP, true); proxy_set_socks5_proxy(client); proxy_async_test(client, false); } TEST(client, http_get_sync_with_http_proxy) { Client client(SW_SOCK_TCP, false); proxy_set_http_proxy(client); proxy_sync_test(client, false); } TEST(client, http_get_sync_with_socks5_proxy) { Client client(SW_SOCK_TCP, false); proxy_set_socks5_proxy(client); proxy_sync_test(client, false); } TEST(client, ssl) { Client client(SW_SOCK_TCP, false); client.enable_ssl_encrypt(); client.set_tls_host_name(TEST_HTTP_DOMAIN); ASSERT_EQ(client.connect(TEST_HTTP_DOMAIN, 443, -1, 0), SW_OK); auto sock = client.socket; ASSERT_TRUE(sock->ssl_get_peer_certificate(sw_tg_buffer())); auto ls = sock->ssl_get_peer_cert_chain(10); ASSERT_FALSE(ls.empty()); swoole::test::dump_cert_info(sw_tg_buffer()->str, sw_tg_buffer()->length); ASSERT_EQ(client.ssl_verify(false), SW_OK); auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); constexpr off_t offset1 = TEST_WRITEV_OFFSET; iovec wr_iov[2]; wr_iov[0].iov_base = (void *) req.c_str(); wr_iov[0].iov_len = offset1; wr_iov[1].iov_base = (char *) req.c_str() + offset1; wr_iov[1].iov_len = req.length() - offset1; swoole::network::IOVector wr_vec(wr_iov, 2); ASSERT_EQ(sock->ssl_writev(&wr_vec), req.length()); sw_tg_buffer()->clear(); if (sw_tg_buffer()->size < 1024 * 1024) { sw_tg_buffer()->extend(1024 * 1024); } constexpr off_t offset2 = TEST_READV_OFFSET; iovec rd_iov[2]; rd_iov[0].iov_base = sw_tg_buffer()->str; rd_iov[0].iov_len = offset2; rd_iov[1].iov_base = sw_tg_buffer()->str + offset2; rd_iov[1].iov_len = sw_tg_buffer()->size - offset2; swoole::network::IOVector rd_vec(rd_iov, 2); auto rv = sock->ssl_readv(&rd_vec); ASSERT_GT(rv, 1024); sw_tg_buffer()->length = rv; sw_tg_buffer()->set_null_terminated(); ASSERT_TRUE(sw_tg_buffer()->contains(TEST_HTTPS_EXPECT)); } TEST(client, fail) { Client c(static_cast(SW_SOCK_RAW6 + 1), false); ASSERT_FALSE(c.ready()); ASSERT_ERREQ(ESOCKTNOSUPPORT); } static void test_recv_timeout(Client &c) { std::thread t([]() { SW_LOOP_N(20) { usleep(50000); kill(getpid(), SIGIO); } }); swoole_signal_set( SIGIO, [](int) { swoole::test::counter_incr(0); }, 0, 1); auto buf = sw_tg_buffer(); while (true) { auto rv = c.recv(buf->str, buf->size); DEBUG() << "rv: " << rv << ", error=" << errno << "\n"; if (c.has_timedout()) { break; } } t.join(); } TEST(client, recv_timeout) { Client c(SW_SOCK_TCP, false); ASSERT_TRUE(c.ready()); ASSERT_EQ(c.connect(TEST_HTTP_DOMAIN, 80, 1.0), SW_OK); test_recv_timeout(c); } TEST(client, ssl_recv_timeout) { Client c(SW_SOCK_TCP, false); ASSERT_TRUE(c.ready()); c.enable_ssl_encrypt(); ASSERT_EQ(c.connect(TEST_HTTP_DOMAIN, 443, 1.0), SW_OK); test_recv_timeout(c); } TEST(client, sync_nonblock) { Client cli(SW_SOCK_TCP, false); ASSERT_NE(cli.socket, nullptr); auto rv = cli.connect(TEST_HTTP_DOMAIN, 80, -1, 1); ASSERT_EQ(rv, -1); ASSERT_EQ(errno, EINPROGRESS); ASSERT_TRUE(cli.async_connect); } ================================================ FILE: core-tests/src/network/dns.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_coroutine.h" #include "swoole_socket.h" #include "swoole_util.h" using namespace swoole; using swoole::coroutine::Socket; using swoole::coroutine::System; using namespace swoole::test; TEST(dns, lookup1) { test::coroutine::run([](void *arg) { auto list = swoole::coroutine::dns_lookup("www.baidu.com", AF_INET, 10); ASSERT_GE(list.size(), 1); }); } TEST(dns, lookup_ipv6) { test::coroutine::run([](void *arg) { auto list = swoole::coroutine::dns_lookup("www.google.com", AF_INET6, 2); ASSERT_GE(list.size(), 1); }); } TEST(dns, domain_not_found) { test::coroutine::run([](void *arg) { auto list = swoole::coroutine::dns_lookup("www.baidu.com-not-found", AF_INET, 2); ASSERT_EQ(list.size(), 0); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); }); } TEST(dns, bad_family) { test::coroutine::run([](void *arg) { auto list = swoole::coroutine::dns_lookup("www.google.com", 9999, 2); ASSERT_GE(list.size(), 1); }); } TEST(dns, cancel) { // swoole_set_trace_flags(SW_TRACE_CARES); // swoole_set_log_level(SW_LOG_TRACE); test::coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); Coroutine::create([co](void *) { System::sleep(0.001); co->cancel(); }); auto list1 = swoole::coroutine::dns_lookup("www.baidu-not-found-for-cancel.com", AF_INET, 2); ASSERT_EQ(list1.size(), 0); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_CO_CANCELED); }); } TEST(dns, gethostbyname) { GethostbynameRequest req1(TEST_HTTP_DOMAIN, AF_INET); ASSERT_EQ(network::gethostbyname(&req1), 0); ASSERT_TRUE(network::Address::verify_ip(AF_INET, req1.addr)); GethostbynameRequest req2(TEST_HTTP_DOMAIN, AF_INET6); ASSERT_EQ(network::gethostbyname(&req2), 0); ASSERT_TRUE(network::Address::verify_ip(AF_INET6, req2.addr)); } TEST(dns, getaddrinfo) { GetaddrinfoRequest req("www.baidu.com", AF_INET, SOCK_STREAM, 0, ""); ASSERT_EQ(network::getaddrinfo(&req), 0); ASSERT_GT(req.count, 0); std::vector ip_list; req.parse_result(ip_list); for (auto &ip : ip_list) { ASSERT_TRUE(network::Address::verify_ip(AF_INET, ip)); } } TEST(dns, getaddrinfo_fail) { GetaddrinfoRequest req("www.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, ""); ASSERT_EQ(network::getaddrinfo(&req), -1); ASSERT_EQ(req.error, EAI_NONAME); } TEST(dns, getaddrinfo_ipv6) { GetaddrinfoRequest req(TEST_HTTP_DOMAIN, AF_INET6, SOCK_STREAM, 0, ""); ASSERT_EQ(network::getaddrinfo(&req), 0); ASSERT_GT(req.count, 0); DEBUG() << "result count: " << req.count << std::endl; std::vector ip_list; req.parse_result(ip_list); for (auto &ip : ip_list) { ASSERT_TRUE(network::Address::verify_ip(AF_INET6, ip)); } } TEST(dns, load_resolv_conf) { int port = get_random_port(); auto ori_dns_server = swoole_get_dns_server(); // with port std::string test_server = "127.0.0.1:" + std::to_string(port); // fake dns server swoole_set_dns_server(test_server); auto dns_server = swoole_get_dns_server(); ASSERT_STREQ(dns_server.host.c_str(), "127.0.0.1"); ASSERT_EQ(dns_server.port, port); // invalid port test_server = "127.0.0.1:808088"; swoole_set_dns_server(test_server); dns_server = swoole_get_dns_server(); ASSERT_EQ(dns_server.port, SW_DNS_SERVER_PORT); ASSERT_TRUE(swoole_load_resolv_conf()); dns_server = swoole_get_dns_server(); ASSERT_EQ(dns_server.host, ori_dns_server.host); ASSERT_EQ(dns_server.port, ori_dns_server.port); } TEST(dns, gethosts) { char hosts_file[] = "/tmp/swoole_hosts"; std::ofstream file(hosts_file); if (!file.is_open()) { std::cout << std::string("file open failed: ") + std::string(strerror(errno)) << std::endl; throw strerror(errno); } ON_SCOPE_EXIT { unlink(hosts_file); }; file << "\n"; file << "127.0.0.1\n"; file << "127.0.0.1 localhost\n"; file << "# 127.0.0.1 aaa.com\n"; file << " 127.0.0.1 bbb.com ccc.com #ddd.com\n"; file.close(); swoole_set_hosts_path(hosts_file); std::string ip = swoole::coroutine::get_ip_by_hosts("localhost"); ASSERT_EQ(ip, "127.0.0.1"); ip = swoole::coroutine::get_ip_by_hosts("aaa.com"); ASSERT_EQ(ip, ""); ip = swoole::coroutine::get_ip_by_hosts("bbb.com"); ASSERT_EQ(ip, "127.0.0.1"); ip = swoole::coroutine::get_ip_by_hosts("ccc.com"); ASSERT_EQ(ip, "127.0.0.1"); ip = swoole::coroutine::get_ip_by_hosts("ddd.com"); ASSERT_EQ(ip, ""); ip = swoole::coroutine::get_ip_by_hosts("non.exist.com"); ASSERT_EQ(ip, ""); } void name_resolver_test_fn_1() { NameResolver::Context ctx{}; ctx.type = AF_INET; ctx.timeout = 1; ASSERT_EQ("127.0.0.1", swoole_name_resolver_lookup("localhost", &ctx)); } void name_resolver_test_fn_2() { NameResolver::Context ctx; std::string domain = "non.exist.com"; NameResolver nr{[](const std::string &domain, NameResolver::Context *ctx, void *) -> std::string { if (domain == "name1") { return "127.0.0.2"; } else if (domain == "www.baidu.com") { ctx->final_ = true; return ""; } return ""; }, nullptr, NameResolver::TYPE_USER}; swoole_name_resolver_add(nr); ctx = {AF_INET}; ASSERT_EQ("127.0.0.2", swoole_name_resolver_lookup("name1", &ctx)); ctx = {AF_INET}; ASSERT_EQ("", swoole_name_resolver_lookup("www.baidu.com", &ctx)); ctx = {AF_INET}; ASSERT_EQ("127.0.0.1", swoole_name_resolver_lookup("localhost", &ctx)); swoole_name_resolver_each([](const std::list::iterator &iter) -> swTraverseOperation { if (iter->type == NameResolver::TYPE_USER) { return SW_TRAVERSE_REMOVE; } else { return SW_TRAVERSE_KEEP; } }); ctx = {AF_INET}; auto ip = swoole_name_resolver_lookup("www.baidu.com", &ctx); ASSERT_TRUE(swoole::network::Address::verify_ip(AF_INET, ip)); } TEST(dns, name_resolve_1) { name_resolver_test_fn_1(); test::coroutine::run([](void *arg) { name_resolver_test_fn_1(); }); } TEST(dns, name_resolve_2) { name_resolver_test_fn_2(); test::coroutine::run([](void *arg) { name_resolver_test_fn_2(); }); } TEST(dns, name_resolve_fail) { NameResolver::Context ctx; ctx = {AF_INET}; auto ip = swoole_name_resolver_lookup("www.baidu.com-not-exists", &ctx); ASSERT_TRUE(ip.empty()); ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); } ================================================ FILE: core-tests/src/network/socket.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_file.h" using namespace std; using namespace swoole; const char test_data[] = "hello swoole, hello world, php is best"; TEST(socket, connect_sync) { network::Address sa; network::Socket *sock; sock = make_socket(SW_SOCK_UNIX_STREAM, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); sa.assign(SW_SOCK_UNIX_STREAM, "/tmp/swole-not-exists.sock"); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_ERR); ASSERT_EQ(swoole_get_last_error(), ENOENT); sock->free(); sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); sa.assign(SW_SOCK_TCP, "192.168.199.199", 80); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_ERR); ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); sock->free(); sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); sa.assign(SW_SOCK_TCP, "127.0.0.1", 59999); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_ERR); ASSERT_EQ(swoole_get_last_error(), ECONNREFUSED); sock->free(); sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); sa.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_OK); sock->free(); sock = make_socket(SW_SOCK_UDP, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); sa.assign(SW_SOCK_UDP, "127.0.0.1", 9900); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_OK); sock->free(); } TEST(socket, fail) { auto *sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); ASSERT_NE(sock, nullptr); network::Address sa; sa.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); ASSERT_EQ(sock->connect_sync(sa), SW_OK); close(sock->get_fd()); ASSERT_EQ(sock->get_name(), -1); ASSERT_EQ(errno, EBADF); network::Address peer; ASSERT_EQ(sock->get_peer_name(&peer), -1); ASSERT_EQ(errno, EBADF); ASSERT_EQ(sock->set_tcp_nopush(1), -1); ASSERT_EQ(sock->listen(1), -1); ASSERT_FALSE(sock->set_buffer_size(1)); ASSERT_FALSE(sock->set_recv_buffer_size(1)); ASSERT_FALSE(sock->set_send_buffer_size(1)); ASSERT_FALSE(sock->set_tcp_nodelay()); ASSERT_FALSE(sock->cork()); ASSERT_FALSE(sock->uncork()); ASSERT_FALSE(sock->set_kernel_read_timeout(0.1)); ASSERT_FALSE(sock->set_kernel_write_timeout(0.1)); sock->move_fd(); sock->free(); } TEST(socket, ssl_fail) { sysv_signal(SIGPIPE, SIG_IGN); network::Client client(SW_SOCK_TCP, false); client.enable_ssl_encrypt(); ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, 443, -1, 0), 0); ASSERT_EQ(client.shutdown(SHUT_WR), 0); ASSERT_EQ(client.get_socket()->ssl_send(SW_STRL(TEST_STR)), SW_ERR); ASSERT_EQ(errno, SW_ERROR_SSL_RESET); ASSERT_EQ(client.shutdown(SHUT_RD), 0); char buf[1024]; errno = 0; ASSERT_EQ(client.get_socket()->ssl_recv(SW_STRL(buf)), 0); ASSERT_EQ(errno, 0); ASSERT_EQ(close(client.get_socket()->get_fd()), 0); client.get_socket()->move_fd(); ASSERT_EQ(client.get_socket()->ssl_recv(SW_STRL(buf)), 0); } TEST(socket, sendto) { char sock1_path[] = "/tmp/udp_unix1.sock"; char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock1_path); unlink(sock2_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock1->bind(sock1_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock2->bind(sock2_path); ASSERT_GT(sock1->sendto(sock2_path, 0, test_data, strlen(test_data)), 0); char buf[1024] = {}; network::Address sa; sa.type = SW_SOCK_UNIX_DGRAM; ASSERT_GT(sock2->recvfrom(buf, sizeof(buf), 0, &sa), 0); ASSERT_STREQ(test_data, buf); ASSERT_STREQ(sa.get_addr(), sock1_path); sock1->free(); sock2->free(); unlink(sock1_path); unlink(sock2_path); } static void test_sendto(enum swSocketType sock_type) { const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; auto sock1 = make_socket(sock_type, SW_FD_DGRAM_SERVER, 0); ASSERT_EQ(sock1->bind(ip, 0), SW_OK); ASSERT_EQ(sock1->get_name(), SW_OK); auto sock2 = make_socket(sock_type, SW_FD_DGRAM_SERVER, 0); ASSERT_EQ(sock2->bind(ip, 0), SW_OK); ASSERT_EQ(sock2->get_name(), SW_OK); ASSERT_GT(sock1->sendto(ip, sock2->get_port(), test_data, strlen(test_data)), 0); char buf[1024] = {}; network::Address sa; sa.type = sock_type; ASSERT_GT(sock2->recvfrom(buf, sizeof(buf), 0, &sa), 0); ASSERT_STREQ(test_data, buf); ASSERT_EQ(sa.get_port(), sock1->get_port()); ASSERT_STREQ(sa.get_addr(), ip); sock1->free(); sock2->free(); } TEST(socket, sendto_ipv4) { test_sendto(SW_SOCK_UDP); } TEST(socket, sendto_ipv6) { test_sendto(SW_SOCK_UDP6); } TEST(socket, recv) { mutex m; m.lock(); int port = swoole::test::get_random_port(); thread t1([&m, port]() { auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, port); char buf[1024] = {}; svr->set_block(); m.unlock(); auto client_sock = svr->accept(); client_sock->recv(buf, sizeof(buf), 0); ASSERT_STREQ(test_data, buf); svr->free(); }); thread t2([&m, port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); ASSERT_EQ(cli->connect(TEST_HOST, port), SW_OK); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cli->send(test_data, sizeof(test_data), 0); cli->free(); }); t1.join(); t2.join(); } TEST(socket, recvfrom_sync) { mutex m; m.lock(); int port = swoole::test::get_random_port(); thread t1([&m, port]() { auto svr = make_server_socket(SW_SOCK_UDP, TEST_HOST, port); network::Address addr; char buf[1024] = {}; svr->set_nonblock(); m.unlock(); svr->recvfrom_sync(buf, sizeof(buf), 0, &addr); ASSERT_STREQ(test_data, buf); svr->free(); }); thread t2([&m, port]() { m.lock(); auto cli = make_socket(SW_SOCK_UDP, SW_FD_STREAM_CLIENT, 0); network::Address addr; addr.assign(SW_SOCK_TCP, TEST_HOST, port); ASSERT_EQ(cli->connect(addr), SW_OK); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cli->send(test_data, sizeof(test_data), 0); cli->free(); }); t1.join(); t2.join(); } TEST(socket, send_async_1) { auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); ASSERT_TRUE(sock->set_block()); ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), SW_OK); auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); ASSERT_EQ(sock->send_async(req.c_str(), req.length()), req.length()); auto buf = sw_tg_buffer(); auto n = sock->recv_sync(buf->str, buf->size, 0); ASSERT_GT(n, 0); buf->length = n; ASSERT_TRUE(buf->contains(SW_STRL(TEST_HTTP_EXPECT))); sock->free(); } TEST(socket, send_async_2) { auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); ASSERT_TRUE(sock->set_block()); ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), SW_OK); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); ASSERT_EQ(sock->send_async(req.c_str(), req.length()), req.length()); swoole_event_set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_READ, [](Reactor *reactor, Event *event) { auto buf = sw_tg_buffer(); auto n = event->socket->recv_sync(buf->str, buf->size, 0); EXPECT_GT(n, 0); buf->length = n; EXPECT_TRUE(buf->contains(SW_STRL(TEST_HTTP_EXPECT))); return 0; }); swoole_event_add(sock, SW_EVENT_READ | SW_EVENT_ONCE); swoole_event_wait(); sock->free(); } TEST(socket, sendfile_sync) { string file = test::get_root_path() + "/examples/test.jpg"; mutex m; int port = swoole::test::get_random_port(); m.lock(); auto str = file_get_contents(file); thread t1([&m, &str, port]() { auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, port); m.unlock(); auto cli = svr->accept(); int len; cli->recv_sync(&len, sizeof(len), MSG_WAITALL); int _len = ntohl(len); ASSERT_EQ(_len, str->get_length()); ASSERT_LT(_len, 1024 * 1024); std::unique_ptr data(new char[_len]); cli->recv_sync(data.get(), _len, MSG_WAITALL); ASSERT_STREQ(data.get(), str->value()); cli->free(); svr->free(); }); thread t2([&m, &file, &str, port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); network::Address addr; addr.assign(SW_SOCK_TCP, TEST_HOST, port); ASSERT_EQ(cli->connect(addr), SW_OK); int len = htonl(str->get_length()); cli->send(&len, sizeof(len), 0); ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_OK); cli->free(); }); t1.join(); t2.join(); } TEST(socket, sendfile) { string file = "/tmp/swoole-file-not-exists"; auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); network::Address addr; addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); ASSERT_EQ(cli->connect(addr), SW_OK); ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_ERR); ASSERT_EQ(errno, ENOENT); File fp(file, File::WRITE | File::CREATE); ASSERT_TRUE(fp.ready()); ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_ERR); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_FILE_EMPTY); fp.write(SW_STRL(TEST_STR)); fp.close(); ASSERT_EQ(cli->sendfile_sync(file.c_str(), 10, 100), SW_ERR); ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); ASSERT_TRUE(fp.open(file, File::WRITE | File::APPEND)); auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); fp.write(req); fp.close(); ASSERT_EQ(cli->sendfile_sync(file.c_str(), strlen(TEST_STR), 0), SW_OK); char rbuf[4096]; auto n = cli->recv_sync(rbuf, sizeof(rbuf), 0); ASSERT_GT(n, 0); String resp(rbuf, n); ASSERT_TRUE(resp.contains(SW_STRL(TEST_HTTP_EXPECT))); cli->free(); ASSERT_TRUE(File::remove(file)); } TEST(socket, peek) { char sock1_path[] = "/tmp/udp_unix1.sock"; char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock1_path); unlink(sock2_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock1->bind(sock1_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock2->bind(sock2_path); ASSERT_GT(sock1->sendto(sock2_path, 0, test_data, strlen(test_data)), 0); char buf[1024] = {}; ASSERT_GT(sock2->peek(buf, sizeof(buf), 0), 0); ASSERT_STREQ(test_data, buf); sw_memset_zero(buf, sizeof(buf)); ASSERT_GT(sock2->recv(buf, sizeof(buf), 0), 0); ASSERT_STREQ(test_data, buf); sock1->free(); sock2->free(); unlink(sock1_path); unlink(sock2_path); } TEST(socket, sendto_sync) { char sock1_path[] = "/tmp/udp_unix1.sock"; unlink(sock1_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock1->bind(sock1_path); char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock2_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock2->bind(sock2_path); char sendbuf[65536] = {}; swoole_random_string(sendbuf, sizeof(sendbuf) - 1); thread t1([sock2, sendbuf]() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); char recvbuf[65536] = {}; while (1) { auto retval = sock2->recv(recvbuf, sizeof(recvbuf) - 1, 0); recvbuf[retval] = 0; if (retval == 3) { ASSERT_STREQ(recvbuf, "end"); break; } else { ASSERT_STREQ(sendbuf, recvbuf); } } }); network::Address sock2_addr; ASSERT_TRUE(sock2_addr.assign(SW_SOCK_UNIX_DGRAM, sock2_path)); for (int i = 0; i < 10; i++) { ASSERT_GT(sock1->sendto_sync(sock2_addr, sendbuf, strlen(sendbuf)), 0); } ASSERT_GT(sock1->sendto_sync(sock2_addr, "end", 3), 0); t1.join(); sock1->free(); sock2->free(); unlink(sock1_path); unlink(sock2_path); } TEST(socket, clean) { char sock1_path[] = "/tmp/udp_unix1.sock"; unlink(sock1_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock1->bind(sock1_path); char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock2_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); sock2->bind(sock2_path); char sendbuf[65536] = {}; swoole_random_string(sendbuf, sizeof(sendbuf) - 1); network::Address sock2_addr; ASSERT_TRUE(sock2_addr.assign(SW_SOCK_UNIX_DGRAM, sock2_path)); for (int i = 0; i < 3; i++) { ASSERT_GT(sock1->sendto(sock2_addr, sendbuf, strlen(sendbuf)), 0); } sock2->clean(); char recvbuf[1024]; auto retval = sock2->peek(recvbuf, sizeof(recvbuf), MSG_DONTWAIT); ASSERT_EQ(retval, -1); sock1->free(); sock2->free(); unlink(sock1_path); unlink(sock2_path); } TEST(socket, check_liveness) { mutex m; int svr_port = TEST_PORT + __LINE__; m.lock(); thread t1([&m, svr_port]() { auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, svr_port); m.unlock(); auto cli = svr->accept(); ASSERT_TRUE(cli); char buf[1024] = {}; cli->recv(buf, sizeof(buf), 0); ASSERT_STREQ(test_data, buf); ssize_t n = cli->recv(buf, sizeof(buf), 0); buf[n] = 0; ASSERT_STREQ("close", buf); cli->shutdown(SHUT_RDWR); cli->free(); svr->free(); }); thread t2([&m, svr_port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); ASSERT_EQ(cli->connect(TEST_HOST, svr_port), SW_OK); cli->send(test_data, sizeof(test_data), 0); std::this_thread::sleep_for(std::chrono::milliseconds(1)); ASSERT_TRUE(cli->check_liveness()); cli->send(SW_STRL("close"), 0); std::this_thread::sleep_for(std::chrono::milliseconds(5)); ASSERT_FALSE(cli->check_liveness()); cli->free(); }); t1.join(); t2.join(); } #define CRLF "\r\n" static void test_socket_sync(network::Socket *sock, bool connect = true) { if (connect) { network::Address addr; ASSERT_TRUE(addr.assign("tcp://" TEST_HTTP_DOMAIN ":80")); ASSERT_EQ(sock->connect(addr), 0); } auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/get"); ASSERT_EQ(sock->write_sync(req.c_str(), req.length()), req.length()); ASSERT_TRUE(sock->check_liveness()); string resp; SW_LOOP { char buf[1024]; auto n = sock->read_sync(buf, sizeof(buf)); if (n == 0) { break; } ASSERT_GT(n, 0); resp.append(buf, n); } ASSERT_TRUE(resp.find(TEST_HTTP_EXPECT) != resp.npos); usleep(50000); ASSERT_FALSE(sock->check_liveness()); sock->free(); } TEST(socket, sync) { auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); test_socket_sync(sock); } TEST(socket, dup) { auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); network::Address addr; ASSERT_TRUE(addr.assign("tcp://" TEST_HTTP_DOMAIN ":80")); ASSERT_EQ(sock->connect(addr), 0); auto sock_2 = sock->dup(); sock->free(); test_socket_sync(sock_2, false); } TEST(socket, convert_to_type) { ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_STREAM), SW_SOCK_TCP); ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_STREAM), SW_SOCK_TCP6); ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_DGRAM), SW_SOCK_UDP); ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_DGRAM), SW_SOCK_UDP6); ASSERT_EQ(network::Socket::convert_to_type(AF_LOCAL, SOCK_STREAM), SW_SOCK_UNIX_STREAM); ASSERT_EQ(network::Socket::convert_to_type(AF_LOCAL, SOCK_DGRAM), SW_SOCK_UNIX_DGRAM); ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_RAW), SW_SOCK_RAW); ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_RAW), SW_SOCK_RAW6); std::string s1("unix:///tmp/swoole.sock"); ASSERT_EQ(network::Socket::convert_to_type(s1), SW_SOCK_UNIX_STREAM); ASSERT_EQ(s1, "/tmp/swoole.sock"); std::string s2("127.0.0.1"); ASSERT_EQ(network::Socket::convert_to_type(s2), SW_SOCK_TCP); std::string s3("::1"); ASSERT_EQ(network::Socket::convert_to_type(s3), SW_SOCK_TCP6); std::string s4("unix:/tmp/swoole.sock"); ASSERT_EQ(network::Socket::convert_to_type(s4), SW_SOCK_UNIX_STREAM); ASSERT_EQ(s4, "/tmp/swoole.sock"); } static void test_sock_type(SocketType type, int expect_sock_domain, int expect_sock_type) { int sock_domain, sock_type; ASSERT_EQ(network::Socket::get_domain_and_type(type, &sock_domain, &sock_type), SW_OK); ASSERT_EQ(sock_domain, expect_sock_domain); ASSERT_EQ(sock_type, expect_sock_type); } TEST(socket, get_domain_and_type) { test_sock_type(SW_SOCK_TCP, AF_INET, SOCK_STREAM); test_sock_type(SW_SOCK_TCP6, AF_INET6, SOCK_STREAM); test_sock_type(SW_SOCK_UDP, AF_INET, SOCK_DGRAM); test_sock_type(SW_SOCK_UDP6, AF_INET6, SOCK_DGRAM); test_sock_type(SW_SOCK_UNIX_STREAM, AF_LOCAL, SOCK_STREAM); test_sock_type(SW_SOCK_UNIX_DGRAM, AF_LOCAL, SOCK_DGRAM); test_sock_type(SW_SOCK_RAW, AF_INET, SOCK_RAW); test_sock_type(SW_SOCK_RAW6, AF_INET6, SOCK_RAW); ASSERT_TRUE(network::Socket::is_dgram(SW_SOCK_UDP6)); ASSERT_TRUE(network::Socket::is_stream(SW_SOCK_TCP)); int sock_domain, sock_type; ASSERT_EQ( network::Socket::get_domain_and_type(static_cast(SW_SOCK_RAW6 + 1), &sock_domain, &sock_type), SW_ERR); } TEST(socket, make_socket) { network::Socket *sock; sock = make_socket(SW_SOCK_RAW, SW_FD_STREAM, 0); ASSERT_EQ(sock, nullptr); ASSERT_EQ(errno, EPROTONOSUPPORT); ASSERT_EQ(swoole_get_last_error(), EPROTONOSUPPORT); sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, AF_INET6, SOCK_RDM, 999, 0); ASSERT_EQ(sock, nullptr); ASSERT_EQ(errno, EINVAL); ASSERT_EQ(swoole_get_last_error(), EINVAL); } TEST(socket, make_server_socket) { network::Socket *sock; auto bad_addr = "199.199.0.0"; sock = make_server_socket(SW_SOCK_RAW, bad_addr); ASSERT_EQ(sock, nullptr); if (geteuid() == 0) { // root ASSERT_EQ(errno, EPROTONOSUPPORT); ASSERT_EQ(swoole_get_last_error(), EPROTONOSUPPORT); } else { ASSERT_EQ(errno, ESOCKTNOSUPPORT); ASSERT_EQ(swoole_get_last_error(), ESOCKTNOSUPPORT); } sock = make_server_socket(SW_SOCK_TCP, bad_addr); ASSERT_EQ(sock, nullptr); ASSERT_EQ(errno, EADDRNOTAVAIL); sock = make_server_socket(SW_SOCK_TCP, TEST_HOST, 0, -1); ASSERT_NE(sock, nullptr); sock->free(); } TEST(socket, ssl_get_error_reason) { swoole_ssl_init(); { int reason = -1; const char *error_str = network::Socket::ssl_get_error_reason(&reason); EXPECT_EQ(error_str, nullptr); EXPECT_EQ(reason, 0); } // 测试单个错误的情况 { // 生成一个 OpenSSL 错误 ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED, __FILE__, __LINE__); int reason = -1; const char *error_str = network::Socket::ssl_get_error_reason(&reason); // 验证错误原因代码 EXPECT_EQ(reason, SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED); // 验证错误字符串 EXPECT_NE(error_str, nullptr); EXPECT_TRUE(strstr(error_str, "certificate expired") != nullptr || strstr(error_str, "CERTIFICATE_EXPIRED") != nullptr); // 验证错误队列现在应该为空(因为 ERR_get_error 会移除错误) EXPECT_EQ(ERR_peek_error(), 0); } // 测试多个错误的情况(只返回第一个) { // 生成多个 OpenSSL 错误 ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_SSLV3_ALERT_BAD_CERTIFICATE, __FILE__, __LINE__); ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SHUTDOWN, SSL_R_PROTOCOL_IS_SHUTDOWN, __FILE__, __LINE__); int reason = -1; const char *error_str = network::Socket::ssl_get_error_reason(&reason); // 验证返回的是第一个错误的原因代码 EXPECT_EQ(reason, SSL_R_SSLV3_ALERT_BAD_CERTIFICATE); // 验证错误字符串 EXPECT_NE(error_str, nullptr); EXPECT_TRUE(strstr(error_str, "bad certificate") != nullptr || strstr(error_str, "BAD_CERTIFICATE") != nullptr); // 验证错误队列中还有一个错误 EXPECT_NE(ERR_peek_error(), 0); ERR_get_error(); } // 测试不同库的错误 { // 生成一个 BIO 库错误 ERR_put_error(ERR_LIB_BIO, BIO_F_BIO_WRITE, BIO_R_BROKEN_PIPE, __FILE__, __LINE__); int reason = -1; const char *error_str = network::Socket::ssl_get_error_reason(&reason); // 验证错误原因代码 EXPECT_EQ(reason, BIO_R_BROKEN_PIPE); // 验证错误字符串 EXPECT_NE(error_str, nullptr); EXPECT_TRUE(strstr(error_str, "broken pipe") != nullptr || strstr(error_str, "BROKEN_PIPE") != nullptr); } // 测试 reason 参数为 nullptr 的情况(如果函数支持) { // 生成一个 OpenSSL 错误 ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_READ, SSL_R_SSL_HANDSHAKE_FAILURE, __FILE__, __LINE__); // 调用函数,传入 nullptr 作为 reason 参数 // 注意:如果函数不支持 nullptr 参数,这个测试会导致段错误 // 在这种情况下,应该跳过这个测试或修改函数以支持 nullptr const char *error_str = network::Socket::ssl_get_error_reason(nullptr); // 验证错误字符串 EXPECT_NE(error_str, nullptr); EXPECT_TRUE(strstr(error_str, "handshake failure") != nullptr || strstr(error_str, "HANDSHAKE_FAILURE") != nullptr); } // 测试错误队列中有错误但 ERR_reason_error_string 返回 nullptr 的情况 { // 使用一个不常见的错误代码,可能没有对应的错误字符串 // 注意:这个测试可能不稳定,因为 OpenSSL 可能为所有错误代码都提供字符串 ERR_put_error(ERR_LIB_USER, 0, 12345, __FILE__, __LINE__); int reason = -1; const char *error_str = network::Socket::ssl_get_error_reason(&reason); // 验证错误原因代码 EXPECT_EQ(reason, 12345); // 错误字符串可能为 nullptr 或包含通用错误信息 // 这个验证可能需要根据实际情况调整 if (error_str != nullptr) { EXPECT_TRUE(true); // 如果有字符串,测试通过 } else { EXPECT_EQ(error_str, nullptr); // 如果没有字符串,也测试通过 } } // 测试函数在多次调用后的行为 { // 生成一个 OpenSSL 错误 ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_CTX_NEW, SSL_R_LIBRARY_HAS_NO_CIPHERS, __FILE__, __LINE__); // 第一次调用 int reason1 = -1; const char *error_str1 = network::Socket::ssl_get_error_reason(&reason1); // 验证第一次调用的结果 EXPECT_EQ(reason1, SSL_R_LIBRARY_HAS_NO_CIPHERS); EXPECT_NE(error_str1, nullptr); // 第二次调用,应该没有错误了 int reason2 = -1; const char *error_str2 = network::Socket::ssl_get_error_reason(&reason2); // 验证第二次调用的结果 EXPECT_EQ(reason2, 0); EXPECT_EQ(error_str2, nullptr); } } TEST(socket, catch_error) { network::Socket fake_sock; ASSERT_EQ(fake_sock.catch_write_pipe_error(ENOBUFS), SW_REDUCE_SIZE); ASSERT_EQ(fake_sock.catch_write_pipe_error(EMSGSIZE), SW_REDUCE_SIZE); ASSERT_EQ(fake_sock.catch_write_pipe_error(EAGAIN), SW_WAIT); ASSERT_EQ(fake_sock.catch_write_error(ENOBUFS), SW_WAIT); } TEST(socket, misc) { auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); ASSERT_TRUE(sock->set_nonblock()); ASSERT_EQ(sock->get_out_buffer_length(), 0); ASSERT_TRUE(sock->set_block()); ASSERT_GT(sock->get_fd(), 0); ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), 0); ASSERT_EQ(sock->get_name(), 0); ASSERT_GT(sock->get_port(), 1024); auto fd = sock->get_fd(); auto sock_fd = sock->move_fd(); ASSERT_EQ(fd, sock_fd); ASSERT_EQ(sock->get_fd(), -1); ASSERT_EQ(sock->catch_write_pipe_error(EMSGSIZE), SW_REDUCE_SIZE); ASSERT_FALSE(sock->kernel_nobufs); ASSERT_EQ(sock->catch_write_pipe_error(ENOBUFS), SW_REDUCE_SIZE); ASSERT_TRUE(sock->kernel_nobufs); ASSERT_EQ(sock->catch_write_error(EAGAIN), sock->catch_write_pipe_error(EAGAIN)); delete sock; ASSERT_TRUE(network::Socket::is_dgram(SW_SOCK_UDP)); ASSERT_FALSE(network::Socket::is_dgram(SW_SOCK_TCP6)); ASSERT_TRUE(network::Socket::is_stream(SW_SOCK_TCP)); ASSERT_FALSE(network::Socket::is_stream(SW_SOCK_UDP)); ASSERT_TRUE(network::Socket::is_inet6(SW_SOCK_UDP6)); ASSERT_FALSE(network::Socket::is_inet6(SW_SOCK_UDP)); int fds[2]; ASSERT_EQ(pipe(fds), 0); network::Socket sock1{}; sock1.fd = fds[1]; network::Socket sock2{}; sock2.fd = fds[0]; ASSERT_EQ(sock1.write(test_data, strlen(test_data)), strlen(test_data)); char rbuf[128]; ASSERT_EQ(sock2.read(rbuf, sizeof(rbuf)), strlen(test_data)); ASSERT_MEMEQ(test_data, rbuf, strlen(test_data)); } ================================================ FILE: core-tests/src/network/stream.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_server.h" using namespace std; using namespace swoole; using namespace swoole::network; TEST(stream, send) { Server serv(Server::MODE_BASE); serv.worker_num = 1; int svr_port = swoole::test::get_random_port(); int ori_log_level = sw_logger()->get_level(); sw_logger()->set_level(SW_LOG_ERROR); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, svr_port); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); } port->open_length_check = true; Stream::set_protocol(&port->protocol); mutex lock; lock.lock(); char buf[65536]; ASSERT_EQ(swoole_random_bytes(buf, sizeof(buf)), sizeof(buf)); ASSERT_EQ(serv.create(), SW_OK); std::thread t1([&]() { swoole_signal_block_all(); lock.lock(); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); // bad request auto stream0 = Stream::create(TEST_TMP_FILE, 0, SW_SOCK_UNIX_STREAM); ASSERT_EQ(stream0, nullptr); // bad request auto stream1 = Stream::create(TEST_HOST, 39999, SW_SOCK_TCP); ASSERT_TRUE(stream1); stream1->response = [](Stream *stream, const char *data, uint32_t length) { EXPECT_EQ(data, nullptr); EXPECT_EQ(stream->errCode, ECONNREFUSED); }; ASSERT_EQ(stream1->send(buf, sizeof(buf)), SW_OK); // success requset auto stream2 = Stream::create(TEST_HOST, svr_port, SW_SOCK_TCP); ASSERT_TRUE(stream2); stream2->private_data = new string(buf, sizeof(buf)); stream2->set_max_length(8 * 1024 * 1024); stream2->response = [](Stream *stream, const char *data, uint32_t length) { string *buf = (string *) stream->private_data; string pkt = string("Server: ") + *buf; EXPECT_EQ(string(data, length), pkt); delete buf; }; ASSERT_EQ(stream2->send(buf, sizeof(buf)), SW_OK); swoole_event_wait(); kill(getpid(), SIGTERM); }); serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; serv.onReceive = [&buf](Server *serv, RecvData *req) -> int { string req_body(req->data + 4, req->info.len - 4); EXPECT_EQ(string(buf, sizeof(buf)), req_body); string pkt = string("Server: ") + req_body; int packed_len = htonl(pkt.length()); EXPECT_TRUE(serv->send(req->info.fd, &packed_len, sizeof(packed_len))); EXPECT_TRUE(serv->send(req->info.fd, pkt.c_str(), pkt.length())); // end stream packed_len = htonl(0); EXPECT_TRUE(serv->send(req->info.fd, &packed_len, sizeof(packed_len))); return SW_OK; }; serv.start(); t1.join(); sw_logger()->set_level(ori_log_level); } ================================================ FILE: core-tests/src/os/async.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_socket.h" #include "swoole_async.h" #include using namespace swoole; static int callback_count; TEST(async, dispatch) { int count = 1000; callback_count = 0; std::atomic handle_count(0); AsyncEvent event = {}; event.object = &handle_count; event.callback = [](AsyncEvent *event) { callback_count++; }; event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); for (int i = 0; i < count; ++i) { auto ret = swoole::async::dispatch(&event); EXPECT_EQ(ret->object, event.object); } swoole_event_wait(); ASSERT_EQ(handle_count, count); ASSERT_EQ(callback_count, count); } TEST(async, schedule) { callback_count = 0; std::atomic handle_count(0); int N = 1000; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); AsyncEvent event{}; event.object = &handle_count; event.callback = [](AsyncEvent *event) { callback_count++; }; event.handler = [](AsyncEvent *event) { usleep(swoole_rand(50000, 100000)); ++(*static_cast *>(event->object)); }; SwooleG.aio_core_worker_num = 4; SwooleG.aio_worker_num = 128; SwooleG.aio_max_wait_time = 0.05; SwooleG.aio_max_idle_time = 0.5; int count = N; swoole_timer_tick(2, [&count, &event, N](Timer *, TimerNode *timer) { SW_LOOP_N(swoole_rand(5, 15)) { auto ret = swoole::async::dispatch(&event); EXPECT_EQ(ret->object, event.object); count--; if (count == 0) { swoole_timer_del(timer); ASSERT_GT(sw_async_threads()->get_worker_num(), 16); ASSERT_GT(sw_async_threads()->get_queue_size(), 100); ASSERT_GT(sw_async_threads()->get_task_num(), 100); break; } else if (count == N - 1) { ASSERT_EQ(sw_async_threads()->get_worker_num(), 4); ASSERT_LE(sw_async_threads()->get_queue_size(), 1); ASSERT_EQ(sw_async_threads()->get_task_num(), 1); } else if (count < N / 2) { ASSERT_GT(sw_async_threads()->get_worker_num(), 4); } } if (count % 50 == 0) { DEBUG() << "async worker thread num=" << sw_async_threads()->get_worker_num() << "\n"; } }); swoole_timer_tick(2000, [](TIMER_PARAMS) { DEBUG() << "async worker thread num=" << sw_async_threads()->get_worker_num() << "\n"; if (sw_async_threads()->get_worker_num() < 16) { swoole_timer_del(tnode); } }); swoole_event_wait(); ASSERT_EQ(handle_count, N); ASSERT_EQ(callback_count, N); } TEST(async, misc) { callback_count = 0; std::atomic handle_count(0); AsyncEvent event = {}; AsyncEvent *rv; event.object = &handle_count; event.callback = [](AsyncEvent *event) { callback_count++; }; event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); auto ret = swoole::async::dispatch(&event); EXPECT_EQ(ret->object, event.object); sw_async_threads()->notify_one(); AsyncEvent event2 = {}; event2.callback = [](AsyncEvent *event) { ASSERT_EQ(event->retval, -1); ASSERT_EQ(event->error, SW_ERROR_AIO_BAD_REQUEST); callback_count++; }; rv = swoole::async::dispatch(&event2); EXPECT_NE(rv, nullptr); swoole_event_wait(); ASSERT_EQ(handle_count, 1); ASSERT_EQ(callback_count, 2); } ================================================ FILE: core-tests/src/os/file.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_file.h" #include "swoole_pipe.h" using namespace swoole; TEST(file, read_line) { std::string filename = test::get_root_path() + "/tests/include/bootstrap.php"; File file(filename, File::READ); FILE *stdc_file = fopen(filename.c_str(), "r"); ASSERT_NE(stdc_file, nullptr); char buf1[1024]; char buf2[1024]; size_t size = file.get_size(); size_t total = 0; while (true) { auto retval = file.read_line(buf1, sizeof(buf1)); if (retval == 0) { break; } total += retval; ASSERT_NE(fgets(buf2, sizeof(buf2), stdc_file), nullptr); ASSERT_STREQ(buf1, buf2); } ASSERT_EQ(total, size); } TEST(file, read_line_no_crlf) { String buf(1024); swoole_random_string(buf.str, buf.size - 1); buf.str[buf.size - 1] = '\0'; std::string filename = "/tmp/swoole_file_read_line_no_crlf.txt"; ASSERT_TRUE(file_put_contents(filename, buf.str, buf.size - 1)); File file(filename, File::READ); char rbuf[1024]; ASSERT_EQ(file.read_line(rbuf, sizeof(rbuf)), sizeof(rbuf) - 1); ASSERT_EQ(rbuf[sizeof(rbuf) - 1], '\0'); remove(filename.c_str()); } TEST(file, file_put_contents) { std::string filename = "/tmp/not-exists-dir/test.txt"; ASSERT_FALSE(file_put_contents(filename, TEST_STR, 0)); ASSERT_ERREQ(SW_ERROR_FILE_EMPTY); ASSERT_FALSE(file_put_contents(filename, TEST_STR, SwooleG.max_file_content + 1)); ASSERT_ERREQ(SW_ERROR_FILE_TOO_LARGE); ASSERT_FALSE(file_put_contents(filename, SW_STRL(TEST_STR))); ASSERT_ERREQ(ENOENT); } TEST(file, file_get_contents) { std::string filename = "/tmp/not-exists-dir/test.txt"; ASSERT_EQ(file_get_contents(filename), nullptr); ASSERT_ERREQ(ENOENT); ASSERT_EQ(file_get_contents("/tmp"), nullptr); ASSERT_ERREQ(EISDIR); auto empty_file = "/tmp/empty-file.txt"; int fd = open(empty_file, O_CREAT | O_RDWR, 0644); close(fd); ASSERT_EQ(file_get_contents(empty_file), nullptr); ASSERT_ERREQ(SW_ERROR_FILE_EMPTY); remove(empty_file); auto large_file = test::get_root_path() + "/bin/core-tests"; SwooleG.max_file_content = 1024 * 1024; ASSERT_EQ(file_get_contents(large_file), nullptr); ASSERT_ERREQ(SW_ERROR_FILE_TOO_LARGE); SwooleG.max_file_content = SW_MAX_FILE_CONTENT; } TEST(file, file_get_size) { ASSERT_EQ(file_get_size("/tmp/not-exists-file.txt"), -1); ASSERT_ERREQ(ENOENT); ASSERT_EQ(file_get_size(9999), -1); ASSERT_ERREQ(EBADF); int fd = open("/tmp", O_RDONLY); ASSERT_EQ(file_get_size(fd), -1); ASSERT_ERREQ(EISDIR); } TEST(file, open_twice) { auto fname = "/tmp/swoole_file_open_twice.txt"; File file1(fname, File::WRITE | File::CREATE); ASSERT_TRUE(file1.ready()); file1.open(fname, File::READ); ASSERT_TRUE(file1.ready()); ASSERT_TRUE(file1.close()); ASSERT_FALSE(file1.close()); remove(fname); } TEST(file, error) { Pipe p(true); auto buf = sw_tg_buffer(); File fp(p.get_socket(true)->get_fd()); ASSERT_EQ(fp.read_all(buf->str, buf->size), 0); ASSERT_ERREQ(ESPIPE); ASSERT_EQ(fp.write_all(SW_STRL(TEST_STR)), 0); ASSERT_ERREQ(ESPIPE); p.close(); FileStatus stat; ASSERT_FALSE(fp.stat(&stat)); ASSERT_ERREQ(EBADF); fp.release(); } TEST(file, tmp_file) { char buf[128] = "/tmp/not-exists-dir/test.XXXXXX"; ASSERT_EQ(swoole_tmpfile(buf), -1); ASSERT_ERREQ(ENOENT); auto ori_tmp_dir = swoole_get_task_tmpdir(); // 这里不能使用 swoole_set_task_tmpdir() ,它会递归创建目录 SwooleG.task_tmpfile = buf; auto fp = make_tmpfile(); ASSERT_FALSE(fp.ready()); SwooleG.task_tmpfile = ori_tmp_dir; } TEST(file, empty_file) { auto fname = "/tmp/swoole_empty_file.txt"; File fp(fname, File::WRITE | File::CREATE); fp.open(fname, File::READ); char buf[128]; ASSERT_EQ(fp.read_all(buf, sizeof(buf)), 0); swoole_clear_last_error(); errno = 0; ASSERT_ERREQ(0); ASSERT_EQ(errno, 0); fp.close(); remove(fname); } ================================================ FILE: core-tests/src/os/msg_queue.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_msg_queue.h" using swoole::MsgQueue; using swoole::QueueNode; TEST(msg_queue, rbac) { MsgQueue q(0x950001); ASSERT_TRUE(q.ready()); ASSERT_GE(q.get_id(), 0); QueueNode in; in.mtype = 999; strcpy(in.mdata, "hello world"); ASSERT_TRUE(q.set_capacity(8192)); // input data ASSERT_TRUE(q.push(&in, strlen(in.mdata))); size_t queue_num, queue_bytes; ASSERT_TRUE(q.stat(&queue_num, &queue_bytes)); ASSERT_EQ(queue_num, 1); ASSERT_GT(queue_bytes, 10); // output data QueueNode out{}; ASSERT_GT(q.pop(&out, sizeof(out.mdata)), 1); ASSERT_TRUE(q.stat(&queue_num, &queue_bytes)); ASSERT_EQ(queue_num, 0); ASSERT_EQ(queue_bytes, 0); ASSERT_EQ(out.mtype, in.mtype); ASSERT_STREQ(out.mdata, in.mdata); ASSERT_TRUE(q.destroy()); ASSERT_FALSE(q.destroy()); ASSERT_ERREQ(EINVAL); q.set_blocking(false); ASSERT_EQ(q.pop(&out, sizeof(out.mdata)), -1); ASSERT_ERREQ(EINVAL); ASSERT_FALSE(q.push(&in, strlen(in.mdata))); ASSERT_ERREQ(EINVAL); ASSERT_FALSE(q.stat(&queue_num, &queue_bytes)); ASSERT_ERREQ(EINVAL); ASSERT_FALSE(q.set_capacity(8192)); ASSERT_ERREQ(EINVAL); } ================================================ FILE: core-tests/src/os/os.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_file.h" #include "swoole_thread.h" using namespace swoole; TEST(os, daemon) { auto sid = getsid(getpid()); int status; swoole_waitpid(test::spawn_exec([sid]() { ASSERT_EQ(sid, getsid(getpid())); ASSERT_TRUE(isatty(STDIN_FILENO)); ASSERT_EQ(swoole_daemon(0, 0), 0); ASSERT_NE(sid, getsid(getpid())); ASSERT_FALSE(isatty(STDIN_FILENO)); }), &status, 0); } TEST(os, cpu_affinity) { cpu_set_t ori_affinity, affinity; ASSERT_EQ(swoole_get_cpu_affinity(&affinity), 0); ori_affinity = affinity; CPU_ZERO(&affinity); CPU_SET(1, &affinity); ASSERT_EQ(swoole_set_cpu_affinity(&affinity), 0); ASSERT_EQ(swoole_get_cpu_affinity(&affinity), 0); auto cpu_n = SW_CPU_NUM; SW_LOOP_N(cpu_n) { if (i == 1) { ASSERT_TRUE(CPU_ISSET(i, &affinity)); } else { ASSERT_FALSE(CPU_ISSET(i, &affinity)); } } ASSERT_EQ(swoole_set_cpu_affinity(&ori_affinity), 0); } TEST(os, thread_name) { std::thread t([]() { char new_name[512]; auto thread_name = "sw-core-tests"; ASSERT_TRUE(swoole_thread_set_name(thread_name)); ASSERT_TRUE(swoole_thread_get_name(new_name, sizeof(new_name))); ASSERT_STREQ(thread_name, new_name); ASSERT_FALSE(swoole_thread_set_name("swoole-core-tests-max-size-is-16")); ASSERT_EQ(swoole_get_last_error(), ERANGE); }); t.join(); } TEST(os, thread_id) { auto tid = swoole_thread_id_to_str(std::this_thread::get_id()); DEBUG() << "current thread id: " << tid << "\n"; ASSERT_FALSE(tid.empty()); } TEST(os, set_isolation) { swoole_set_isolation("not-exists-group", "not-exists-user", "/tmp/not-exists-dir"); } ================================================ FILE: core-tests/src/os/pipe.cpp ================================================ #include "test_core.h" #include "swoole_pipe.h" using namespace swoole; TEST(pipe, unixsock) { UnixSocket p(true, SOCK_DGRAM); ASSERT_TRUE(p.ready()); char buf[1024]; int ret = p.write((void *) SW_STRS("hello world1")); ASSERT_GT(ret, 0); ret = p.write((void *) SW_STRS("hello world2")); ASSERT_GT(ret, 0); ret = p.write((void *) SW_STRS("hello world3")); ASSERT_GT(ret, 0); // 1 ret = p.read(buf, sizeof(buf)); if (ret < 0) { swoole_sys_warning("read() failed."); } ASSERT_GT(ret, 0); ASSERT_EQ(strcmp("hello world1", buf), 0); // 2 ret = p.read(buf, sizeof(buf)); ASSERT_GT(ret, 0); ASSERT_EQ(strcmp("hello world2", buf), 0); // 3 ret = p.read(buf, sizeof(buf)); ASSERT_GT(ret, 0); ASSERT_EQ(strcmp("hello world3", buf), 0); } TEST(pipe, base) { int ret; char data[256]; Pipe p(true); ASSERT_TRUE(p.ready()); ret = p.write((void *) SW_STRL("hello world\n")); ASSERT_GT(ret, 0); ret = p.write((void *) SW_STRL("你好中国。\n")); ASSERT_GT(ret, 0); sw_memset_zero(data, 256); ret = p.read(data, 255); ASSERT_GT(ret, 0); ASSERT_EQ(strcmp("hello world\n你好中国。\n", data), 0); } ================================================ FILE: core-tests/src/os/process_pool.cpp ================================================ #include "test_core.h" #include "swoole_process_pool.h" #include #ifdef __MACH__ #define sysv_signal signal #endif #include "swoole_signal.h" #include #include using namespace swoole; constexpr int magic_number = 99900011; static ProcessPool *current_pool = nullptr; static Worker *current_worker = nullptr; static void test_func(ProcessPool &pool) { EventData data{}; size_t size = swoole_system_random(1024, 4096); String rmem(size); rmem.append_random_bytes(size - 1); rmem.append("\0"); data.info.len = size; memcpy(data.data, rmem.value(), size); DEBUG() << "dispatch: " << size << " bytes\n"; int worker_id = -1; ASSERT_EQ(pool.dispatch_sync(&data, &worker_id), SW_OK); pool.running = true; pool.ptr = &rmem; if (pool.onWorkerStart) { pool.onWorkerStart(&pool, pool.get_worker(0)); } pool.main_loop(&pool, pool.get_worker(0)); pool.destroy(); } static void test_func_task_protocol(ProcessPool &pool) { pool.set_protocol(SW_PROTOCOL_TASK); pool.onTask = [](ProcessPool *pool, Worker *worker, EventData *task) -> int { pool->running = false; auto *_data = (String *) pool->ptr; usleep(10000); EXPECT_MEMEQ(_data->str, task->data, task->len()); return 0; }; test_func(pool); } static void test_func_message_protocol(ProcessPool &pool) { pool.set_protocol(SW_PROTOCOL_MESSAGE); pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { pool->running = false; String *_data = static_cast(pool->ptr); usleep(10000); DEBUG() << "received: " << rdata->info.len << " bytes\n"; EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); }; test_func(pool); } static void test_func_stream_protocol(ProcessPool &pool) { pool.set_protocol(SW_PROTOCOL_STREAM); pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { pool->running = false; String *_data = (String *) pool->ptr; EventData *msg = (EventData *) rdata->data; usleep(10000); DEBUG() << "received: " << rdata->info.len << " bytes\n"; EXPECT_MEMEQ(_data->str, msg->data, msg->len()); }; test_func(pool); } TEST(process_pool, tcp) { ProcessPool pool{}; int svr_port = TEST_PORT + __LINE__; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); ASSERT_EQ(pool.listen(TEST_HOST, svr_port, 128), SW_OK); test_func_task_protocol(pool); } TEST(process_pool, unix_sock) { ProcessPool pool{}; signal(SIGPIPE, SIG_IGN); ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); ASSERT_EQ(pool.listen(TEST_HOST, TEST_PORT, 128), SW_ERR); ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); ASSERT_EQ(pool.listen(TEST_SOCK_FILE, 128), SW_ERR); ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); test_func_task_protocol(pool); } TEST(process_pool, tcp_raw) { ProcessPool pool{}; constexpr int size = 2 * 1024 * 1024; int svr_port = TEST_PORT + __LINE__; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); ASSERT_EQ(pool.listen(TEST_HOST, svr_port, 128), SW_OK); pool.set_max_packet_size(size); pool.set_protocol(SW_PROTOCOL_STREAM); String data(size); data.append_random_bytes(size - 1); data.append("\0"); ASSERT_EQ(pool.dispatch_sync(data.str, data.length), SW_OK); pool.running = true; pool.ptr = &data; pool.onMessage = [](ProcessPool *pool, RecvData *rdata) -> void { pool->running = false; String *_data = (String *) pool->ptr; EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); }; pool.main_loop(&pool, pool.get_worker(0)); pool.destroy(); } #ifdef HAVE_MSGQUEUE TEST(process_pool, msgqueue) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); test_func_task_protocol(pool); } TEST(process_pool, msgqueue_2) { auto key = 0x9501 + __LINE__; auto msg_id_ = msgget(key, IPC_CREAT); ASSERT_GE(msg_id_, 0); test::spawn_exec_and_wait([key]() { ProcessPool pool{}; swoole_set_isolation("", "nobody", ""); ASSERT_EQ(pool.create(1, key, SW_IPC_MSGQUEUE), SW_ERR); ASSERT_ERREQ(EACCES); }); } #endif TEST(process_pool, message_protocol) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); test_func_message_protocol(pool); } TEST(process_pool, message_protocol_with_timer) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); pool.set_protocol(SW_PROTOCOL_MESSAGE); swoole_signal_set(SIGTERM, [](int) { DEBUG() << "received SIGTERM signal\n"; current_pool->running = false; }); pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { DEBUG() << "onStart\n"; current_pool = pool; swoole_timer_after(50, [pool](TIMER_PARAMS) { DEBUG() << "kill master\n"; kill(getpid(), SIGTERM); }); }; pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { auto *_data = static_cast(pool->ptr); usleep(10000); DEBUG() << "received: " << rdata->info.len << " bytes\n"; EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); }; test_func(pool); } TEST(process_pool, stream_protocol) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); test_func_stream_protocol(pool); } TEST(process_pool, stream_protocol_with_msgq) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); test_func_stream_protocol(pool); } TEST(process_pool, shutdown) { ProcessPool pool{}; int *shm_value = (int *) sw_mem_pool()->alloc(sizeof(int)); ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); // init pool.set_max_packet_size(8192); pool.set_protocol(SW_PROTOCOL_TASK); pool.ptr = shm_value; pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { int *shm_value = (int *) pool->ptr; *shm_value = magic_number; usleep(1); }; pool.onTask = [](ProcessPool *pool, Worker *worker, EventData *task) -> int { usleep(1000); kill(pool->master_pid, SIGTERM); return 0; }; pool.onStart = [](ProcessPool *pool) { EventData msg{}; msg.info.len = 128; swoole_random_string(msg.data, msg.info.len); int worker_id = -1; pool->dispatch_sync(&msg, &worker_id); }; current_pool = &pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); // start ASSERT_EQ(pool.start(), SW_OK); // wait ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); ASSERT_EQ(*shm_value, magic_number); sysv_signal(SIGTERM, SIG_DFL); } TEST(process_pool, reload) { ProcessPool pool{}; test::counter_init(); ASSERT_EQ(pool.create(2), SW_OK); // init pool.set_max_packet_size(8192); pool.max_wait_time = 1; pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { test::counter_incr(0); current_pool = pool; DEBUG() << "onWorkerStart " << worker->id << "\n"; sysv_signal(SIGTERM, SIG_IGN); sysv_signal(SIGRTMIN, [](int) { current_pool->reopen_logger(); }); sysv_signal(SIGWINCH, [](int) { current_pool->reopen_logger(); }); while (true) { sleep(10000); } }; pool.onStart = [](ProcessPool *pool) { pool->reopen_logger(); swoole_timer_after(50, [pool](TIMER_PARAMS) { kill(pool->get_worker(0)->pid, SIGRTMIN); }); swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->reload(); }); }; pool.onBeforeReload = [](ProcessPool *pool) { DEBUG() << "onBeforeReload\n"; }; pool.onAfterReload = [](ProcessPool *pool) { DEBUG() << "onAfterReload\n"; swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->shutdown(); }); }; pid_t other_child_pid = test::spawn_exec([]() { usleep(10000); exit(123); }); test::counter_set(20, other_child_pid); pool.onWorkerError = [](ProcessPool *pool, Worker *worker, const ExitStatus &exit_status) { DEBUG() << "onWorkerError " << exit_status.get_pid() << "\n"; ASSERT_EQ(exit_status.get_signal(), SIGKILL); }; pool.onWorkerMessage = [](ProcessPool *pool, EventData *msg) { DEBUG() << "onWorkerMessage: type " << msg->info.type << ", content=" << std::string(msg->data, msg->info.len); EXPECT_EQ(msg->info.type, SW_WORKER_MESSAGE_STOP + 1); EXPECT_MEMEQ(msg->data, TEST_STR, msg->info.len); }; pool.onWorkerNotFound = [](ProcessPool *pool, const ExitStatus &exit_status) -> int { DEBUG() << "onWorkerNotFound " << exit_status.get_pid() << "\n"; EXPECT_EQ(exit_status.get_pid(), test::counter_get(20)); EXPECT_EQ(exit_status.get_code(), 123); EXPECT_EQ(pool->push_message(SW_WORKER_MESSAGE_STOP + 1, SW_STRL(TEST_STR)), SW_OK); return SW_OK; }; current_pool = &pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); sysv_signal(SIGIO, [](int sig) { current_pool->read_message = true; }); ASSERT_EQ(pool.start(), SW_OK); ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); ASSERT_EQ(test::counter_get(0), 4); sysv_signal(SIGTERM, SIG_DFL); } static void test_async_pool() { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); // init pool.set_max_packet_size(8192); pool.set_protocol(SW_PROTOCOL_TASK); pool.async = true; test::counter_init(); pool.onStart = [](ProcessPool *pool) { current_pool = pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); }; pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { test::counter_set(0, magic_number); current_worker = worker; current_pool = pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); swoole_signal_set(SIGTERM, [](int sig) { DEBUG() << "value: " << test::counter_incr(0) << "; " << "SIGTERM, stop worker\n"; current_pool->stop(current_worker); }); usleep(10); }; pool.onMessage = [](ProcessPool *pool, RecvData *msg) { DEBUG() << "value: " << test::counter_incr(0) << "; " << "onMessage, kill\n"; kill(pool->master_pid, SIGTERM); }; // start ASSERT_EQ(pool.start(), SW_OK); EventData msg{}; msg.info.len = 128; swoole_random_string(msg.data, msg.info.len); int worker_id = -1; pool.dispatch_sync(&msg, &worker_id); // wait ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); ASSERT_EQ(test::counter_get(0), magic_number + 2); swoole_signal_clear(); sysv_signal(SIGTERM, SIG_DFL); } TEST(process_pool, async) { test_async_pool(); // ASSERT_EQ(test::spawn_exec_and_wait([]() { test_async_pool(); }), 0); } static void test_async_pool_with_mb() { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); ASSERT_EQ(pool.create_message_bus(), SW_OK); if (swoole_timer_is_available()) { swoole_timer_free(); } swoole_signal_clear(); // init pool.set_max_packet_size(8192); pool.set_protocol(SW_PROTOCOL_TASK); test::counter_init(); pool.async = true; pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { current_worker = worker; current_pool = pool; test::counter_incr_and_put_log(0, "onWorkerStart"); swoole_signal_set(SIGTERM, [](int sig) { test::counter_incr_and_put_log(0, "SIGTERM, stop worker"); current_pool->stop(sw_worker()); }); usleep(10); }; pool.onWorkerStop = [](ProcessPool *pool, Worker *worker) { current_worker = worker; current_pool = pool; test::counter_incr_and_put_log(0, "onWorkerStop"); }; pool.onWorkerExit = [](ProcessPool *pool, Worker *worker) { test::counter_incr_and_put_log(0, "onWorkerExit"); }; pool.onStart = [](ProcessPool *pool) { current_pool = pool; swoole_signal_set(SIGTERM, [](int sig) { current_pool->running = false; }); swoole_signal_set(SIGIO, [](int sig) { current_pool->read_message = true; }); test::counter_incr_and_put_log(0, "onStart"); swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->send_message(0, SW_STRL("detach")); swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->send_message(0, SW_STRL("shutdown")); }); }); }; pool.onShutdown = [](ProcessPool *pool) { test::counter_incr_and_put_log(0, "onShutdown"); }; pool.onMessage = [](ProcessPool *pool, RecvData *msg) { auto req = std::string(msg->data, msg->info.len); if (req == "detach") { test::counter_incr_and_put_log(0, "onMessage, detach"); ASSERT_TRUE(pool->detach()); } else if ((req == "shutdown")) { test::counter_incr_and_put_log(0, "onMessage, shutdown"); pool->shutdown(); } }; // start ASSERT_EQ(pool.start(), SW_OK); // wait ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); ASSERT_GE(test::counter_get(0), 8); swoole_signal_clear(); sysv_signal(SIGTERM, SIG_DFL); sysv_signal(SIGIO, SIG_DFL); } TEST(process_pool, async_mb) { test_async_pool_with_mb(); } TEST(process_pool, mb1) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_NONE), SW_OK); ASSERT_EQ(pool.create_message_bus(), SW_ERR); ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); pool.destroy(); } TEST(process_pool, mb2) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); ASSERT_EQ(pool.create_message_bus(), SW_OK); ASSERT_EQ(pool.create_message_bus(), SW_ERR); ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); pool.destroy(); } TEST(process_pool, socket) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); ASSERT_EQ(pool.start(), SW_ERR); ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); pool.destroy(); } TEST(process_pool, listen) { ProcessPool pool{}; auto port = TEST_PORT + __LINE__; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); ASSERT_EQ(pool.listen("127.0.0.1", port, 128), SW_OK); pool.set_protocol(SW_PROTOCOL_STREAM); size_t size = 2048; String rmem(size); rmem.append_random_bytes(size - 1); rmem.append('\0'); String wmem(size); wmem.append_random_bytes(size - 1); wmem.append('\0'); pool.ptr = &wmem; pool.onMessage = [](ProcessPool *pool, RecvData *msg) { String *wmem = (String *) pool->ptr; ASSERT_EQ(pool->response(wmem->str, wmem->length), SW_OK); ASSERT_EQ(pool->response(nullptr, 999), SW_ERR); ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); ASSERT_EQ(pool->response(wmem->str, 0), SW_ERR); ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); }; current_pool = &pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); ASSERT_EQ(pool.start(), SW_OK); std::thread t1([&]() { swoole_signal_block_all(); network::SyncClient c(SW_SOCK_TCP); c.connect("127.0.0.1", port); uint32_t pkt_len = htonl(rmem.length); c.send((char *) &pkt_len, sizeof(pkt_len)); c.send(rmem.str, rmem.length); char buf[4096]; EXPECT_EQ(c.recv((char *) &pkt_len, sizeof(pkt_len)), 4); c.recv(buf, ntohl(pkt_len)); EXPECT_MEMEQ(buf, wmem.str, wmem.length); ASSERT_EQ(pool.response(wmem.str, wmem.length), SW_ERR); ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); c.close(); kill(getpid(), SIGTERM); }); ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); sysv_signal(SIGTERM, SIG_DFL); t1.join(); } const char *test_sock = "/tmp/swoole_process_pool.sock"; TEST(process_pool, listen_unixsock) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); ASSERT_EQ(pool.listen(test_sock, 128), SW_OK); pool.set_protocol(SW_PROTOCOL_STREAM); size_t size = 2048; String rmem(size); rmem.append_random_bytes(size - 1); rmem.append('\0'); String wmem(size); wmem.append_random_bytes(size - 1); wmem.append('\0'); pool.ptr = &wmem; pool.onMessage = [](ProcessPool *pool, RecvData *msg) { String *wmem = (String *) pool->ptr; pool->response(wmem->str, wmem->length); }; current_pool = &pool; sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); ASSERT_EQ(pool.start(), SW_OK); std::thread t1([&]() { swoole_signal_block_all(); network::SyncClient c(SW_SOCK_UNIX_STREAM); c.connect(test_sock, 0); uint32_t pkt_len = htonl(rmem.length); c.send((char *) &pkt_len, sizeof(pkt_len)); c.send(rmem.str, rmem.length); char buf[4096]; EXPECT_EQ(c.recv((char *) &pkt_len, sizeof(pkt_len)), 4); c.recv(buf, ntohl(pkt_len)); EXPECT_MEMEQ(buf, wmem.str, wmem.length); c.close(); kill(getpid(), SIGTERM); }); ASSERT_EQ(pool.wait(), SW_OK); pool.destroy(); sysv_signal(SIGTERM, SIG_DFL); t1.join(); } TEST(process_pool, worker) { Worker worker{}; worker.init(); ASSERT_TRUE(worker.is_running()); ASSERT_GT(worker.start_time, 0); worker.set_max_request(1000, 200); ASSERT_GT(SwooleWG.max_request, 1000); ASSERT_LE(SwooleWG.max_request, 1200); worker.shutdown(); ASSERT_TRUE(worker.is_shutdown()); swoole_set_worker_type(SW_USER_WORKER); ASSERT_EQ(swoole_get_worker_symbol(), '@'); swoole_set_worker_type(SW_TASK_WORKER); ASSERT_EQ(swoole_get_worker_symbol(), '^'); swoole_set_worker_type(SW_WORKER); ASSERT_EQ(swoole_get_worker_symbol(), '*'); swoole_set_worker_type(SW_MASTER); ASSERT_EQ(swoole_get_worker_symbol(), '#'); swoole_set_worker_type(SW_MANAGER); ASSERT_EQ(swoole_get_worker_symbol(), '$'); worker.set_status_to_idle(); ASSERT_TRUE(worker.is_idle()); ASSERT_FALSE(worker.is_busy()); worker.set_status_to_busy(); ASSERT_FALSE(worker.is_idle()); ASSERT_TRUE(worker.is_busy()); worker.set_status(SW_WORKER_EXIT); ASSERT_FALSE(worker.is_idle()); ASSERT_FALSE(worker.is_busy()); } TEST(process_pool, add_worker) { Worker worker{}; worker.pid = getpid(); ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); pool.add_worker(&worker); auto *worker2 = pool.get_worker_by_pid(getpid()); ASSERT_EQ(&worker, worker2); ASSERT_TRUE(pool.del_worker(worker2)); } ================================================ FILE: core-tests/src/os/signal.cpp ================================================ #include "test_core.h" #include "swoole_process_pool.h" #include "swoole_signal.h" #ifdef HAVE_SIGNALFD static void sig_usr1(int signo) {} TEST(os_signal, signalfd) { int ret; sigset_t curset; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); sigemptyset(&curset); sigprocmask(SIG_BLOCK, NULL, &curset); ret = sigismember(&curset, SIGUSR1); ASSERT_EQ(ret, 0); swoole_signalfd_init(); swoole_signal_set(SIGUSR1, sig_usr1); sigemptyset(&curset); sigprocmask(SIG_BLOCK, NULL, &curset); ret = sigismember(&curset, SIGUSR1); ASSERT_EQ(ret, 1); swoole_signal_set(SIGUSR1, NULL); swoole_signal_clear(); sigemptyset(&curset); sigprocmask(SIG_BLOCK, NULL, &curset); ret = sigismember(&curset, SIGUSR1); ASSERT_EQ(ret, 0); swoole_event_wait(); } #endif TEST(os_signal, block) { ASSERT_EQ(swoole::test::spawn_exec_and_wait([]() { sysv_signal(SIGIO, [](int signo) { exit(255); }); std::thread t([] { swoole_signal_block_all(); pthread_kill(pthread_self(), SIGIO); }); t.join(); }), 0); } TEST(os_signal, unblock) { auto status = swoole::test::spawn_exec_and_wait([]() { sysv_signal(SIGIO, [](int signo) { exit(255); }); std::thread t([] { swoole_signal_block_all(); pthread_kill(pthread_self(), SIGIO); swoole_signal_unblock_all(); }); t.join(); }); auto exit_status = swoole::ExitStatus(getpid(), status); ASSERT_EQ(exit_status.get_code(), 255); } TEST(os_signal, signal_to_str) { ASSERT_STREQ(swoole_signal_to_str(SIGTERM), "Terminated: 15"); ASSERT_STREQ(swoole_signal_to_str(SIGIO), "I/O possible: 29"); ASSERT_STREQ(swoole_signal_to_str(SIGRTMIN), "Real-time signal 0: 34"); ASSERT_STREQ(swoole_signal_to_str(99999), "Unknown signal 99999: 99999"); } TEST(os_signal, set) { swoole_signal_set(SIGIO, [](int signo) { exit(255); }); ASSERT_TRUE(swoole_signal_isset(SIGIO)); ASSERT_FALSE(swoole_signal_isset(SIGTERM)); swoole_signal_set(SIGIO, nullptr); ASSERT_FALSE(swoole_signal_isset(SIGIO)); } static int trigger_signal = 0; TEST(os_signal, dispatch) { trigger_signal = 0; swoole_signal_set( SIGIO, [](int signo) { trigger_signal = signo; }, true); swoole_kill(getpid(), SIGIO); ASSERT_EQ(trigger_signal, 0); ASSERT_EQ(swoole_signal_get_handler(SIGTERM), nullptr); ASSERT_NE(swoole_signal_get_handler(SIGIO), nullptr); swoole_signal_dispatch(); ASSERT_EQ(trigger_signal, SIGIO); trigger_signal = 0; swoole_signal_dispatch(); ASSERT_EQ(trigger_signal, 0); ASSERT_EQ(swoole_signal_get_listener_num(), 0); swoole_signal_clear(); } TEST(os_signal, error) { swoole_signal_set(SIGIO, nullptr, 0, 0); swoole_signal_block_all(); swoole_signal_block_all(); // no effect swoole_signal_unblock_all(); swoole_signal_unblock_all(); // no effect swoole_signal_callback(SW_SIGNO_MAX); // no effect swoole_clear_last_error(); swoole_signal_callback(SIGIO); ASSERT_ERREQ(SW_ERROR_UNREGISTERED_SIGNAL); ASSERT_EQ(swoole_signal_get_handler(SW_SIGNO_MAX), nullptr); } ================================================ FILE: core-tests/src/os/timer.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_signal.h" #include "swoole_util.h" #include "swoole_timer.h" using swoole::Timer; using swoole::TimerNode; TEST(timer, sys) { swoole::test::counter_init(); auto counter = swoole::test::counter_ptr(); uint64_t ms1 = swoole::time(); ASSERT_TRUE(swoole_timer_add( 20L, false, [&](TIMER_PARAMS) { counter[0]++; }, nullptr)); swoole_clear_last_error(); ASSERT_EQ(swoole_timer_add(-1L, false, [&](TIMER_PARAMS) {}), nullptr); ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); swoole_clear_last_error(); ASSERT_EQ(swoole_timer_add(0L, false, [&](TIMER_PARAMS) {}), nullptr); ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); // dtor test auto timer = swoole_timer_add(20L, false, [&](TIMER_PARAMS) { counter[2]++; }); ASSERT_TRUE(timer); timer->destructor = [&](TimerNode *tnode) { counter[3] = 9999; }; swoole_timer_add( 100L, true, [&](Timer *, TimerNode *tnode) { counter[1]++; if (counter[1] == 5) { swoole_timer_del(tnode); } }, nullptr); while (sw_timer()->count() > 0) { sleep(10); swoole_signal_dispatch(); if (SwooleG.signal_alarm) { swoole_timer_select(); } } uint64_t ms2 = swoole::time(); swoole_timer_free(); ASSERT_LE(ms2 - ms1, 510); ASSERT_EQ(counter[0], 1); ASSERT_EQ(counter[1], 5); ASSERT_EQ(counter[2], 1); ASSERT_EQ(counter[3], 9999); } TEST(timer, async) { int timer1_count = 0; int timer2_count = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); uint64_t ms1 = swoole::time(); swoole_timer_after( 20, [&](Timer *, TimerNode *) { timer1_count++; }, nullptr); swoole_timer_tick( 100, [&](Timer *, TimerNode *tnode) { timer2_count++; if (timer2_count == 5) { swoole_timer_del(tnode); } }, nullptr); swoole_event_wait(); uint64_t ms2 = swoole::time(); ASSERT_LE(ms2 - ms1, 510); ASSERT_EQ(timer1_count, 1); ASSERT_EQ(timer2_count, 5); } TEST(timer, exists) { long timer_id = swoole_timer_tick( 100, [&](Timer *, TimerNode *tnode) {}, nullptr); ASSERT_TRUE(swoole_timer_exists(timer_id)); } TEST(timer, clear) { long timer_id = swoole_timer_tick( 100, [&](Timer *, TimerNode *tnode) {}, nullptr); swoole_timer_clear(timer_id); ASSERT_FALSE(swoole_timer_exists(timer_id)); } TEST(timer, get) { long timer_id = swoole_timer_tick( 100, [&](Timer *, TimerNode *tnode) {}, nullptr); TimerNode *timerNode = swoole_timer_get(timer_id); ASSERT_EQ(timerNode->id, timer_id); swoole_timer_free(); } TEST(timer, delay) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); uint64_t ms1 = swoole::time(); uint64_t ms2 = 0; long timer_id = swoole_timer_after( 100, [&](Timer *, TimerNode *tnode) { ms2 = swoole::time(); }, nullptr); TimerNode *timerNode = swoole_timer_get(timer_id); swoole_timer_delay(timerNode, 100); swoole_event_wait(); ASSERT_GE(ms2 - ms1, 100); swoole_timer_del(timerNode); swoole_timer_free(); } TEST(timer, error) { Timer *tmp = SwooleTG.timer; SwooleTG.timer = nullptr; swoole_timer_free(); swoole_timer_select(); // no effect ASSERT_EQ(swoole_timer_get(1), nullptr); ASSERT_FALSE(swoole_timer_clear(1)); ASSERT_FALSE(swoole_timer_exists(1)); long timer_id = swoole_timer_tick( 0, [&](Timer *, TimerNode *tnode) {}, nullptr); ASSERT_EQ(timer_id, SW_ERR); timer_id = swoole_timer_after( 0, [&](Timer *, TimerNode *tnode) {}, nullptr); ASSERT_EQ(timer_id, SW_ERR); swoole_timer_delay(nullptr, 100); ASSERT_FALSE(swoole_timer_del(nullptr)); SwooleTG.timer = tmp; swoole_timer_free(); } TEST(timer, reinit) { int timer1_count = 0; int timer2_count = 0; swoole_timer_after( 20, [&](Timer *, TimerNode *) { timer1_count++; DEBUG() << "timer2_count" << timer2_count << "\n"; }, nullptr); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); sw_timer()->reinit(); uint64_t ms1 = swoole::time(); swoole_timer_tick( 100, [&](Timer *, TimerNode *tnode) { timer2_count++; DEBUG() << "timer2_count" << timer2_count << "\n"; if (timer2_count == 5) { swoole_timer_del(tnode); } }, nullptr); swoole_event_wait(); uint64_t ms2 = swoole::time(); ASSERT_LE(ms2 - ms1, 510); ASSERT_EQ(timer1_count, 1); ASSERT_EQ(timer2_count, 5); } TEST(timer, realtime_add) { timespec ts; ts.tv_sec = 1; ts.tv_nsec = 900L * SW_NUM_MILLION; swoole::realtime_add(&ts, 1905); ASSERT_EQ(ts.tv_sec, 3); ASSERT_EQ(ts.tv_nsec, 805L * SW_NUM_MILLION); } ================================================ FILE: core-tests/src/os/wait.cpp ================================================ #include "test_coroutine.h" using namespace swoole; using namespace swoole::test; using swoole::coroutine::System; static pid_t fork_child() { pid_t pid = fork(); EXPECT_NE(pid, -1); if (pid == 0) { usleep(100000); exit(0); } return pid; } static pid_t fork_child2() { pid_t pid = fork(); EXPECT_NE(pid, -1); if (pid == 0) { exit(0); } usleep(100000); return pid; } TEST(os_wait, waitpid_before_child_exit) { test::coroutine::run([](void *arg) { auto pid = fork_child(); int status = -1; pid_t pid2 = swoole_coroutine_waitpid(pid, &status, 0); ASSERT_EQ(status, 0); ASSERT_EQ(pid, pid2); }); } TEST(os_wait, waitpid_after_child_exit) { test::coroutine::run([](void *arg) { pid_t pid = fork_child2(); int status = -1; pid_t pid2 = swoole_coroutine_waitpid(pid, &status, 0); ASSERT_EQ(status, 0); ASSERT_EQ(pid, pid2); }); } TEST(os_wait, wait_before_child_exit) { test::coroutine::run([](void *arg) { pid_t pid = fork_child(); int status = -1; pid_t pid2 = -1; for (;;) { pid2 = swoole_coroutine_wait(&status); if (pid2 == pid) { break; } } ASSERT_EQ(WEXITSTATUS(status), 0); }); } TEST(os_wait, wait_after_child_exit) { test::coroutine::run([](void *arg) { pid_t pid = fork_child2(); int status = -1; pid_t pid2 = -1; for (;;) { pid2 = swoole_coroutine_wait(&status); if (pid2 == pid) { break; } } ASSERT_EQ(WEXITSTATUS(status), 0); }); } TEST(os_wait, waitpid_safe) { test::coroutine::run([](void *arg) { pid_t pid = fork_child(); int status = -1; pid_t pid2 = System::waitpid_safe(pid, &status, 0); ASSERT_EQ(pid2, pid); ASSERT_EQ(WEXITSTATUS(status), 0); }); } ================================================ FILE: core-tests/src/protocol/base.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "test_coroutine.h" #include "redis_client.h" #include "swoole_redis.h" using namespace swoole; using namespace std; constexpr int PKG_N = 128; constexpr int MAX_SIZE = 1 * 1024 * 1024 + 65536; constexpr int MIN_SIZE = 512; static void test_protocol(Server &serv, ListenPort *port, String *pkgs) { mutex lock; lock.lock(); serv.create(); String wbuf; for (int i = 0; i < PKG_N; i++) { wbuf.append(pkgs[i]); } DEBUG() << "data total length: " << wbuf.length << "\n"; thread t1([&]() { swoole_signal_block_all(); lock.lock(); network::Client cli(SW_SOCK_TCP, false); EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); off_t offset = 0; while (offset < (off_t) wbuf.length) { auto n = wbuf.length - offset > 65536 ? swoole_random_int() % 65536 + 1 : wbuf.length - offset; ASSERT_EQ(cli.send(wbuf.str + offset, n), n); offset += n; } usleep(100000); }); serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; int recv_count = 0; serv.onReceive = [&](Server *serv, RecvData *req) -> int { EXPECT_EQ(memcmp(req->data, pkgs[recv_count].str, req->info.len), 0); recv_count++; if (recv_count == PKG_N) { usleep(100000); serv->shutdown(); } return SW_OK; }; serv.start(); t1.join(); } TEST(protocol, length) { Server serv(Server::MODE_BASE); serv.worker_num = 1; String pkgs[PKG_N]; for (int i = 0; i < PKG_N; i++) { auto pkt_len = swoole_rand(MIN_SIZE, MAX_SIZE); auto pkt_len_net = htonl(pkt_len); pkgs[i].append((char *) &pkt_len_net, sizeof(pkt_len_net)); pkgs[i].append_random_bytes(pkt_len, false); } sw_logger()->set_level(SW_LOG_WARNING); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); port->set_stream_protocol(); test_protocol(serv, port, pkgs); } TEST(protocol, length_2) { Server serv(Server::MODE_BASE); serv.worker_num = 1; ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); port->set_stream_protocol(); mutex lock; lock.lock(); serv.create(); thread t1([&]() { swoole_signal_block_all(); lock.lock(); char rbuf[32]; usleep(50000); // 测试分多次发送长度 { network::Client cli(SW_SOCK_TCP, false); EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); String wbuf; auto pkt_len = swoole_rand(MIN_SIZE, MAX_SIZE); auto pkt_len_net = htonl(pkt_len); wbuf.append((char *) &pkt_len_net, sizeof(pkt_len_net)); wbuf.append_random_bytes(pkt_len, false); ASSERT_EQ(cli.send(wbuf.str, 2), 2); usleep(10); ASSERT_EQ(cli.send(wbuf.str + 2, 4), 4); usleep(10); ASSERT_EQ(cli.send(wbuf.str + 2, wbuf.length - 6), wbuf.length - 6); ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 3); ASSERT_STREQ(rbuf, "OK"); } // 发送 0 长度的包 { network::Client cli(SW_SOCK_TCP, false); EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); auto pkt_len = 0; auto pkt_len_net = htonl(pkt_len); ASSERT_EQ(cli.send((char *) &pkt_len_net, sizeof(pkt_len)), sizeof(pkt_len)); ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 3); ASSERT_STREQ(rbuf, "OK"); } // 发送 INT_MAX 长度的包 { network::Client cli(SW_SOCK_TCP, false); EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); auto pkt_len = INT_MAX; auto pkt_len_net = htonl(pkt_len); ASSERT_EQ(cli.send((char *) &pkt_len_net, sizeof(pkt_len)), sizeof(pkt_len)); ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 0); } usleep(50000); serv.shutdown(); }); serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; serv.onReceive = [&](Server *serv, RecvData *req) -> int { serv->send(req->session_id(), SW_STRL("OK\0")); return SW_OK; }; serv.start(); t1.join(); } TEST(protocol, length_3) { Server serv(Server::MODE_BASE); serv.worker_num = 1; ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); port->set_length_protocol(0, 'l', 4); mutex lock; lock.lock(); serv.create(); thread t1([&]() { swoole_signal_block_all(); lock.lock(); char rbuf[32]; usleep(50000); network::Client cli(SW_SOCK_TCP, false); EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); auto pkt_len = -1; ASSERT_EQ(cli.send((char *) &pkt_len, sizeof(pkt_len)), sizeof(pkt_len)); ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 0); usleep(50000); serv.shutdown(); }); serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; serv.onReceive = [&](Server *serv, RecvData *req) -> int { serv->send(req->session_id(), SW_STRL("OK\0")); return SW_OK; }; serv.start(); t1.join(); } TEST(protocol, eof) { Server serv(Server::MODE_BASE); serv.worker_num = 1; String pkgs[PKG_N]; for (int i = 0; i < PKG_N; i++) { pkgs[i].append_random_bytes(swoole_rand(MIN_SIZE, MAX_SIZE), true); pkgs[i].append("\r\n"); } sw_logger()->set_level(SW_LOG_WARNING); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); port->set_eof_protocol("\r\n", false); test_protocol(serv, port, pkgs); } TEST(protocol, socks5_strerror) { SW_LOOP_N(16) { auto error = Socks5Proxy::strerror(i); if (i > 0x08) { ASSERT_STREQ("Unknown error", error); } else { ASSERT_GT(strlen(error), 5); } } } TEST(protocol, swap_byte_order) { { EXPECT_EQ(swoole_swap_endian16(0x1234), 0x3412); EXPECT_EQ(swoole_swap_endian16(0x0001), 0x0100); EXPECT_EQ(swoole_swap_endian16(0x00FF), 0xFF00); EXPECT_EQ(swoole_swap_endian16(0xFF00), 0x00FF); EXPECT_EQ(swoole_swap_endian16(0xFFFF), 0xFFFF); } { EXPECT_EQ(swoole_swap_endian32(0x12345678), 0x78563412); EXPECT_EQ(swoole_swap_endian32(0x00000001), 0x01000000); EXPECT_EQ(swoole_swap_endian32(0x0000FF00), 0x00FF0000); EXPECT_EQ(swoole_swap_endian32(0xFF000000), 0x000000FF); EXPECT_EQ(swoole_swap_endian32(0xFFFFFFFF), 0xFFFFFFFF); } { uint16_t v = 0xABCD; EXPECT_EQ(swoole_swap_endian16(swoole_swap_endian16(v)), v); } { uint32_t v = 0xABCDEF01; EXPECT_EQ(swoole_swap_endian32(swoole_swap_endian32(v)), v); } { uint64_t val = 0x1122334455667788ULL; auto converted = swoole_swap_endian64(val); auto str = (uchar *) &converted; EXPECT_EQ(str[0], 0x11); EXPECT_EQ(str[1], 0x22); EXPECT_EQ(str[2], 0x33); EXPECT_EQ(str[3], 0x44); EXPECT_EQ(str[4], 0x55); EXPECT_EQ(str[5], 0x66); EXPECT_EQ(str[6], 0x77); EXPECT_EQ(str[7], 0x88); } } // Helper function to create binary data for testing template void createBinaryData(T value, char *buffer) { memcpy(buffer, &value, sizeof(T)); } TEST(protocol, unpack) { // Tests for 8-bit integer formats { char buffer[8]; // Test signed char ('c') int8_t c_val = -42; createBinaryData(c_val, buffer); EXPECT_EQ(swoole_unpack('c', buffer), -42); // Test unsigned char ('C') uint8_t C_val = 200; createBinaryData(C_val, buffer); EXPECT_EQ(swoole_unpack('C', buffer), 200); // Test extreme values createBinaryData(INT8_MIN, buffer); EXPECT_EQ(swoole_unpack('c', buffer), INT8_MIN); createBinaryData(INT8_MAX, buffer); EXPECT_EQ(swoole_unpack('c', buffer), INT8_MAX); createBinaryData(UINT8_MAX, buffer); EXPECT_EQ(swoole_unpack('C', buffer), UINT8_MAX); } // Tests for 16-bit integer formats { char buffer[8]; // Test signed short ('s') int16_t s_val = -12345; createBinaryData(s_val, buffer); EXPECT_EQ(swoole_unpack('s', buffer), -12345); // Test unsigned short ('S') uint16_t S_val = 54321; createBinaryData(S_val, buffer); EXPECT_EQ(swoole_unpack('S', buffer), 54321); // Test big-endian unsigned short ('n') uint16_t n_val = 0x1234; uint16_t n_be = (n_val >> 8) | (n_val << 8); // Convert to big-endian createBinaryData(n_be, buffer); EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); // Test little-endian unsigned short ('v') uint16_t v_val = 0x1234; createBinaryData(v_val, buffer); EXPECT_EQ(swoole_unpack('v', buffer), 0x1234); // Test extreme values createBinaryData(INT16_MIN, buffer); EXPECT_EQ(swoole_unpack('s', buffer), INT16_MIN); createBinaryData(INT16_MAX, buffer); EXPECT_EQ(swoole_unpack('s', buffer), INT16_MAX); createBinaryData(UINT16_MAX, buffer); EXPECT_EQ(swoole_unpack('S', buffer), UINT16_MAX); } // Tests for 32-bit integer formats { char buffer[8]; // Test signed long ('l') int32_t l_val = -123456789; createBinaryData(l_val, buffer); EXPECT_EQ(swoole_unpack('l', buffer), -123456789); // Test unsigned long ('L') uint32_t L_val = 3000000000; createBinaryData(L_val, buffer); EXPECT_EQ(swoole_unpack('L', buffer), 3000000000); // Test big-endian unsigned long ('N') uint32_t N_val = 0x12345678; uint32_t N_be = ((N_val & 0xFF) << 24) | ((N_val & 0xFF00) << 8) | ((N_val & 0xFF0000) >> 8) | ((N_val & 0xFF000000) >> 24); createBinaryData(N_be, buffer); EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); // Test little-endian unsigned long ('V') uint32_t V_val = 0x12345678; createBinaryData(V_val, buffer); EXPECT_EQ(swoole_unpack('V', buffer), 0x12345678); // Test extreme values createBinaryData(INT32_MIN, buffer); EXPECT_EQ(swoole_unpack('l', buffer), INT32_MIN); createBinaryData(INT32_MAX, buffer); EXPECT_EQ(swoole_unpack('l', buffer), INT32_MAX); createBinaryData(UINT32_MAX, buffer); EXPECT_EQ(swoole_unpack('L', buffer), UINT32_MAX); } // Tests for 64-bit integer formats { char buffer[8]; // Test signed long long ('q') int64_t q_val = -1234567890123456789LL; createBinaryData(q_val, buffer); EXPECT_EQ(swoole_unpack('q', buffer), -1234567890123456789LL); // Test unsigned long long ('Q') uint64_t Q_val = 10234567890123456789ULL; createBinaryData(Q_val, buffer); EXPECT_EQ(swoole_unpack('Q', buffer), 10234567890123456789ULL); // Test big-endian unsigned long long ('J') uint64_t J_val = 0x123456789ABCDEF0ULL; uint64_t J_be = swoole_swap_endian64(J_val); // Use our swap function for test createBinaryData(J_be, buffer); EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); // Test little-endian unsigned long long ('P') uint64_t P_val = 0x123456789ABCDEF0ULL; createBinaryData(P_val, buffer); EXPECT_EQ(swoole_unpack('P', buffer), 0x123456789ABCDEF0ULL); // Test extreme values (be careful with signed min/max due to two's complement) createBinaryData(INT64_MIN, buffer); EXPECT_EQ(swoole_unpack('q', buffer), INT64_MIN); createBinaryData(INT64_MAX, buffer); EXPECT_EQ(swoole_unpack('q', buffer), INT64_MAX); // For UINT64_MAX, be aware that the return type is int64_t, so this might not work as expected // This test might fail due to the limitation of the return type createBinaryData(UINT64_MAX, buffer); EXPECT_EQ(swoole_unpack('Q', buffer), (int64_t) UINT64_MAX); } // Tests for machine-dependent integer formats { char buffer[8]; // Test signed integer ('i') int i_val = -987654321; createBinaryData(i_val, buffer); EXPECT_EQ(swoole_unpack('i', buffer), -987654321); // Test unsigned integer ('I') unsigned int I_val = 3000000000; createBinaryData(I_val, buffer); EXPECT_EQ(swoole_unpack('I', buffer), 3000000000); // Test extreme values createBinaryData(INT_MIN, buffer); EXPECT_EQ(swoole_unpack('i', buffer), INT_MIN); createBinaryData(INT_MAX, buffer); EXPECT_EQ(swoole_unpack('i', buffer), INT_MAX); createBinaryData(UINT_MAX, buffer); EXPECT_EQ(swoole_unpack('I', buffer), (int64_t) UINT_MAX); } // Test for invalid format specifier { char buffer[8] = {0}; // Test invalid format specifier EXPECT_EQ(swoole_unpack('x', buffer), -1); EXPECT_EQ(swoole_unpack('?', buffer), -1); EXPECT_EQ(swoole_unpack('Z', buffer), -1); } // Test for endianness-specific behavior { char buffer[8]; // Test that 'n' and 'v' formats handle endianness correctly buffer[0] = 0x12; buffer[1] = 0x34; EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); buffer[0] = 0x34; buffer[1] = 0x12; EXPECT_EQ(swoole_unpack('v', buffer), 0x1234); // Test that 'N' and 'V' formats handle endianness correctly buffer[0] = 0x12; buffer[1] = 0x34; buffer[2] = 0x56; buffer[3] = 0x78; EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); buffer[0] = 0x78; buffer[1] = 0x56; buffer[2] = 0x34; buffer[3] = 0x12; EXPECT_EQ(swoole_unpack('V', buffer), 0x12345678); // Test that 'J' and 'P' formats handle endianness correctly buffer[0] = 0x12; buffer[1] = 0x34; buffer[2] = 0x56; buffer[3] = 0x78; buffer[4] = 0x9A; buffer[5] = 0xBC; buffer[6] = 0xDE; buffer[7] = 0xF0; EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); buffer[0] = 0xF0; buffer[1] = 0xDE; buffer[2] = 0xBC; buffer[3] = 0x9A; buffer[4] = 0x78; buffer[5] = 0x56; buffer[6] = 0x34; buffer[7] = 0x12; EXPECT_EQ(swoole_unpack('P', buffer), 0x123456789ABCDEF0ULL); } { char buffer[8]; // Test that 'n' format uses ntohs() correctly uint16_t test16 = 0x1234; uint16_t be16 = htons(test16); // Convert to network byte order createBinaryData(be16, buffer); EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); // Test that 'N' format uses ntohl() correctly uint32_t test32 = 0x12345678; uint32_t be32 = htonl(test32); // Convert to network byte order createBinaryData(be32, buffer); EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); // Test that 'J' format uses swoole_ntoh64() correctly uint64_t test64 = 0x123456789ABCDEF0ULL; uint64_t be64 = swoole_hton64(test64); // Convert to network byte order createBinaryData(be64, buffer); EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); } } TEST(protocol, hton64) { { uint64_t val = 0x1122334455667788ULL; uint64_t converted = swoole_hton64(val); auto str = (uchar *) &converted; EXPECT_EQ(str[0], 0x11); EXPECT_EQ(str[1], 0x22); EXPECT_EQ(str[2], 0x33); EXPECT_EQ(str[3], 0x44); EXPECT_EQ(str[4], 0x55); EXPECT_EQ(str[5], 0x66); EXPECT_EQ(str[6], 0x77); EXPECT_EQ(str[7], 0x88); uint64_t reversed = swoole_ntoh64(converted); EXPECT_EQ(reversed, val); } { uint64_t min_val = 0ULL; uint64_t min_converted = swoole_hton64(min_val); auto min_str = (unsigned char *) &min_converted; for (int i = 0; i < 8; i++) { EXPECT_EQ(min_str[i], 0x00) << "Byte " << i << " should be 0x00"; } EXPECT_EQ(swoole_ntoh64(min_converted), min_val); // 测试最大值 uint64_t max_val = UINT64_MAX; uint64_t max_converted = swoole_hton64(max_val); auto max_str = (unsigned char *) &max_converted; for (int i = 0; i < 8; i++) { EXPECT_EQ(max_str[i], 0xFF) << "Byte " << i << " should be 0xFF"; } EXPECT_EQ(swoole_ntoh64(max_converted), max_val); } { uint64_t alt_pattern = 0xAAAAAAAAAAAAAAAAULL; uint64_t alt_converted = swoole_hton64(alt_pattern); EXPECT_EQ(swoole_ntoh64(alt_converted), alt_pattern); uint64_t alt_pattern2 = 0x5555555555555555ULL; uint64_t alt_converted2 = swoole_hton64(alt_pattern2); EXPECT_EQ(swoole_ntoh64(alt_converted2), alt_pattern2); // 测试单字节模式 for (int i = 0; i < 8; i++) { uint64_t single_byte = 0xFFULL << (i * 8); uint64_t converted = swoole_hton64(single_byte); EXPECT_EQ(swoole_ntoh64(converted), single_byte) << "Failed for byte position " << i; } } { for (int i = 0; i < 100; i++) { uint64_t random_val = swoole_random_int(); uint64_t converted = swoole_hton64(random_val); uint64_t reversed = swoole_ntoh64(converted); EXPECT_EQ(reversed, random_val) << "Failed for random value: 0x" << std::hex << random_val; } } { uint64_t test_val = 0x0102030405060708ULL; uint64_t converted = swoole_hton64(test_val); auto bytes = (unsigned char *) &converted; EXPECT_EQ(bytes[0], 0x01); EXPECT_EQ(bytes[1], 0x02); EXPECT_EQ(bytes[2], 0x03); EXPECT_EQ(bytes[3], 0x04); EXPECT_EQ(bytes[4], 0x05); EXPECT_EQ(bytes[5], 0x06); EXPECT_EQ(bytes[6], 0x07); EXPECT_EQ(bytes[7], 0x08); } { for (int i = 0; i < 100; i++) { uint64_t val = swoole_random_int(); EXPECT_EQ(swoole_ntoh64(swoole_hton64(val)), val) << "hton64->ntoh64 failed for 0x" << std::hex << val; EXPECT_EQ(swoole_hton64(swoole_ntoh64(val)), val) << "ntoh64->hton64 failed for 0x" << std::hex << val; } } } ================================================ FILE: core-tests/src/protocol/base64.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "swoole_base64.h" TEST(base64, encode) { char inbuf[1024]; char outbuf[2048]; auto n = swoole_random_bytes(inbuf, sizeof(inbuf) - 1); auto n2 = swoole::base64_encode((uchar *) inbuf, n, outbuf); ASSERT_GT(n2, n); } TEST(base64, decode) { const char *inbuf = "aGVsbG8gd29ybGQ="; char outbuf[2048]; auto n2 = swoole::base64_decode(inbuf, strlen(inbuf), outbuf); ASSERT_EQ(std::string(outbuf, n2), "hello world"); } ================================================ FILE: core-tests/src/protocol/http2.cpp ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "test_core.h" #include "test_coroutine.h" #include "redis_client.h" #include "swoole_server.h" #include "swoole_http.h" #include "swoole_http2.h" #include #include using namespace swoole; using namespace std; using http_server::Context; using network::Client; using network::SyncClient; using swoole::network::AsyncClient; const std::string REDIS_TEST_KEY = "key-swoole"; const std::string REDIS_TEST_VALUE = "value-swoole"; TEST(http2, default_settings) { ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE), SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH), SW_HTTP2_DEFAULT_ENABLE_PUSH); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE), SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE), SW_HTTP2_DEFAULT_MAX_FRAME_SIZE); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE), SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); http2::Settings _settings = { (uint32_t) swoole_rand(1, 100000), (uint32_t) swoole_rand(1, 100000), (uint32_t) swoole_rand(1, 100000), (uint32_t) swoole_rand(1, 100000), (uint32_t) swoole_rand(1, 100000), (uint32_t) swoole_rand(1, 100000), }; http2::put_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE, _settings.header_table_size); http2::put_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH, _settings.enable_push); http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, _settings.max_concurrent_streams); http2::put_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, _settings.init_window_size); http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, _settings.max_frame_size); http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, _settings.max_header_list_size); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE), _settings.header_table_size); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH), _settings.enable_push); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), _settings.max_concurrent_streams); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE), _settings.init_window_size); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE), _settings.max_frame_size); ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE), _settings.max_header_list_size); } TEST(http2, pack_setting_frame) { char frame[SW_HTTP2_SETTING_FRAME_SIZE + SW_HTTP2_FRAME_HEADER_SIZE]; http2::Settings settings_1{}; http2::init_settings(&settings_1); size_t n = http2::pack_setting_frame(frame, settings_1, false); ASSERT_GT(n, 16); http2::Settings settings_2{}; http2::unpack_setting_data( frame + SW_HTTP2_FRAME_HEADER_SIZE, n, [&settings_2](uint16_t id, uint32_t value) -> ReturnCode { switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: settings_2.header_table_size = value; break; case SW_HTTP2_SETTINGS_ENABLE_PUSH: settings_2.enable_push = value; break; case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: settings_2.max_concurrent_streams = value; break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: settings_2.init_window_size = value; break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: settings_2.max_frame_size = value; break; case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: settings_2.max_header_list_size = value; break; default: return SW_ERROR; } return SW_SUCCESS; }); ASSERT_MEMEQ(&settings_1, &settings_2, sizeof(settings_2)); } #define HTTP2_GET_TYPE_TEST(t) ASSERT_STREQ(http2::get_type(SW_HTTP2_TYPE_##t), #t) TEST(http2, get_type) { HTTP2_GET_TYPE_TEST(DATA); HTTP2_GET_TYPE_TEST(HEADERS); HTTP2_GET_TYPE_TEST(PRIORITY); HTTP2_GET_TYPE_TEST(RST_STREAM); HTTP2_GET_TYPE_TEST(SETTINGS); HTTP2_GET_TYPE_TEST(PUSH_PROMISE); HTTP2_GET_TYPE_TEST(PING); HTTP2_GET_TYPE_TEST(GOAWAY); HTTP2_GET_TYPE_TEST(WINDOW_UPDATE); HTTP2_GET_TYPE_TEST(CONTINUATION); } TEST(http2, get_type_color) { SW_LOOP_N(SW_HTTP2_TYPE_GOAWAY + 2) { ASSERT_GE(http2::get_type_color(i), 0); } } struct Http2Session { SessionId fd; nghttp2_session *session; Server *server; std::unordered_map stream_paths; std::unordered_map stream_data; Http2Session(SessionId _fd, Server *_serv) : fd(_fd), session(nullptr), server(_serv) {} ~Http2Session() { if (session) { nghttp2_session_del(session); session = nullptr; } } }; #define CHECK_NGHTTP2(expr, error_msg) \ do { \ int rv = (expr); \ if (rv != 0) { \ swoole_error_log(SW_LOG_ERROR, "%s: %s", error_msg, nghttp2_strerror(rv)); \ return -1; \ } \ } while (0) std::unordered_map> sessions; static nghttp2_settings_entry default_settings[] = { { NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE, }, { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, }, { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE, }, { NGHTTP2_SETTINGS_MAX_FRAME_SIZE, SW_HTTP2_DEFAULT_MAX_FRAME_SIZE, }, { NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE, }, }; static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) { auto http2_session = static_cast(user_data); Server *server = static_cast(http2_session->server); bool ret = server->send(http2_session->fd, reinterpret_cast(data), length); if (!ret) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return length; } static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { return 0; } // 处理头部回调 static int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data) { if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { return 0; } DEBUG() << "Header: " << std::string(reinterpret_cast(name), namelen) << ": " << std::string(reinterpret_cast(value), valuelen) << std::endl; return 0; } // 处理请求开始回调 static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { return 0; } DEBUG() << "New request started on stream ID: " << frame->hd.stream_id << std::endl; return 0; } static void handle_request(nghttp2_session *session, int32_t stream_id, Http2Session *http2_session); static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { auto http2_session = static_cast(user_data); switch (frame->hd.type) { case NGHTTP2_HEADERS: if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { swoole_trace_log(SW_TRACE_HTTP2, "Received HEADERS frame for stream %d", frame->hd.stream_id); if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { handle_request(session, frame->hd.stream_id, http2_session); } } break; case NGHTTP2_DATA: swoole_trace_log(SW_TRACE_HTTP2, "Received DATA frame for stream %d", frame->hd.stream_id); if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { handle_request(session, frame->hd.stream_id, http2_session); } break; } return 0; } static int on_data_chunk_recv_callback( nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { auto http2_session = static_cast(user_data); // 将数据块添加到对应流的数据中 http2_session->stream_data[stream_id].append(reinterpret_cast(data), len); swoole_trace_log(SW_TRACE_HTTP2, "Received %zu bytes of DATA for stream %d", len, stream_id); return 0; } static int on_frame_not_send_callback(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data) { // 处理帧发送失败 std::cerr << "Failed to send frame type: " << frame->hd.type << std::endl; return 0; } static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { if (frame->hd.type == NGHTTP2_WINDOW_UPDATE) { DEBUG() << "Window update sent: stream=" << frame->hd.stream_id << ", increment=" << frame->window_update.window_size_increment << std::endl; } return 0; } static ssize_t string_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { const char *data = static_cast(source->ptr); size_t datalen = strlen(data); if (datalen <= length) { memcpy(buf, data, datalen); *data_flags |= NGHTTP2_DATA_FLAG_EOF; return datalen; } else { memcpy(buf, data, length); return length; } } static void handle_request(nghttp2_session *session, int32_t stream_id, Http2Session *http2_session) { // 获取路径 std::string path = "/"; auto path_it = http2_session->stream_paths.find(stream_id); if (path_it != http2_session->stream_paths.end()) { path = path_it->second; } // 获取请求体 std::string request_body; auto body_it = http2_session->stream_data.find(stream_id); if (body_it != http2_session->stream_data.end()) { request_body = body_it->second; } swoole_trace_log(SW_TRACE_HTTP2, "Request fully received on stream %d, path: %s, body length: %zu", stream_id, path.c_str(), request_body.length()); auto header_server = "nghttp2-server/" NGHTTP2_VERSION; // 准备响应头 nghttp2_nv hdrs[] = { {(uint8_t *) ":status", (uint8_t *) "200", 7, 3, NGHTTP2_NV_FLAG_NONE}, {(uint8_t *) "content-type", (uint8_t *) "text/html", 12, 9, NGHTTP2_NV_FLAG_NONE}, {(uint8_t *) "server", (uint8_t *) header_server, 6, strlen(header_server), NGHTTP2_NV_FLAG_NONE}}; if (path == "/" || path == "/index.html") { const char *body = "

Welcome to HTTP/2 Server

" "

This is a simple HTTP/2 server implementation.

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

404 Not Found

" "

The requested resource was not found on this server.

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

301 Moved

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

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

hello world

"); }); $server->handle('/test', function ($request, $response) { var_dump($request->get); $response->end("

Test

"); }); $server->start(); }); Swoole\Event::wait(); ================================================ FILE: examples/coroutine/http/write_func.php ================================================ SWOOLE_TRACE_HTTP2, 'log_level' => 0, ]); Co\run(function () { $client = new Swoole\Coroutine\Http\Client('www.jd.com', 443, true); $client->set(['write_func' => function($client, $data) { var_dump(strlen($data)); }]); $client->get('/'); var_dump(strlen($client->getBody())); return 0; }); ================================================ FILE: examples/coroutine/http2_client.php ================================================ SWOOLE_TRACE_HTTP2, // 'log_level' => SWOOLE_LOG_TRACE, ]); co::create(function () use ($fp) { $cli = new co\Http2\Client('127.0.0.1', 9518); $cli->set([ 'timeout' => 1, 'package_max_length' => 1024*1024*8]); var_dump($cli->connect()); if (in_array('get', TEST)) { $req = new Swoole\Http2\Request; $req->path = "/index.html"; $req->headers = [ 'host' => "localhost", "user-agent" => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip', ]; $req->cookies = ['name' => 'rango', 'email' => '1234@qq.com']; var_dump($cli->send($req)); $resp = $cli->recv(); var_dump($resp); } if (in_array('post', TEST)) { $req2 = new Swoole\Http2\Request; $req2->path = "/index.php"; $req2->headers = [ 'host' => "localhost", "user-agent" => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip', ]; $req2->data = "hello world\n"; var_dump($cli->send($req2)); $resp = $cli->recv(); var_dump($resp); } if (in_array('pipeline', TEST)) { $req3 = new Swoole\Http2\Request; $req3->path = "/index.php"; $req3->headers = [ 'host' => "localhost", "user-agent" => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip', ]; $req3->pipeline = true; $req3->method = "POST"; $streamId = $cli->send($req3); $cli->write($streamId, ['int' => rand(1000, 9999)]); $cli->write($streamId, ['int' => rand(1000, 9999)]); //end stream $cli->write($streamId, ['int' => rand(1000, 9999), 'end' => true], true); var_dump($cli->recv()); } // $cli->close(); }); ================================================ FILE: examples/coroutine/http_backend_serv.php ================================================ server = new Swoole\Http\Server("0.0.0.0", 9510); $this->server->set([ 'worker_num' => 1, 'daemonize' => true, 'log_file' => '/data/markyuan/swoole.log', ]); $this->server->on('Request', ['Server', 'onRequest']); $this->server->start(); } public static function onRequest($request, $response) { $response->end('xxxx'); } public static function staticFunc() { echo "in static function"; } } $server = new Server(); $server->run(); ================================================ FILE: examples/coroutine/http_client.php ================================================ setHeaders(['Host' => 'localhost']); $cli->set(['http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT]); $result = $cli->get('/get?json=true'); var_dump($cli->body); // assert($result); // $ret = json_decode($cli->body, true); // assert(is_array($ret) and $ret['json'] == 'true'); }); ================================================ FILE: examples/coroutine/http_download.php ================================================ set(['timeout' => -1]); $cli->setHeaders([ 'Host' => $host, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => '*', 'Accept-Encoding' => 'gzip' ]); $cli->download('/dist/skin1/images/logo-white.png', '/tmp/logo.png'); }); ================================================ FILE: examples/coroutine/http_server.php ================================================ server = new Swoole\Http\Server("0.0.0.0", 9502, SWOOLE_BASE); $this->server->set([ 'worker_num' => 1, ]); // $this->server->on('Connect', [$this, 'onConnect']); $this->server->on('Request', [$this, 'onRequest']); // $this->server->on('Close', [$this, 'onClose']); $this->server->set(['trace_flags' => 1 << 15, 'log_level' => 0]); $this->server->start(); } public function onRequest($request, $response) { $fd = $request->fd; co::create(function () { co::sleep(0.1); }); $response->end(111); } } $server = new Server(); Swoole\Coroutine::set(array( 'max_coroutine' => 1000, )); $server->run(); ================================================ FILE: examples/coroutine/httpmulti.php ================================================ server = new Swoole\Http\Server("0.0.0.0", 9508); $this->server->set([ 'worker_num' => 1, 'daemonize' => true, 'log_file' => '/data/markyuan/swoole.log', ]); $this->server->on('Request', ['Server', 'onRequest']); $this->server->start(); } private static function https(){ //--enable-openssl for($i=0;$i<2;$i++){ $cli = new Swoole\Coroutine\Http\Client('0.0.0.0',443,TRUE ); $cli->set([ 'timeout' => 1]); $cli->setHeaders([ 'Host' => "api.mp.qq.com", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = ($cli->get('/cgi-bin/token?appid=3333&secret=222'.$i.$i.$i.$i.$i)); error_log(__LINE__.var_export($cli,true).PHP_EOL,3,'/tmp/markyuan'); $cli->close(); } } private static function http(){ error_log(__LINE__.'---------- begin --- http --------------'.PHP_EOL,3,'/tmp/markyuan'); for($i=0;$i<2;$i++){ $cli = new Swoole\Coroutine\Http\Client('0.0.0.0', 9510); $cli->set([ 'timeout' => 1]); $cli->setHeaders([ 'Host' => "api.mp.qq.com", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); error_log(__LINE__.var_export($cli,true).PHP_EOL,3,'/tmp/markyuan'); $ret = ($cli->get('/cn/token?appid=1FxxxxS9V'.$i.$i.$i.$i.$i)); error_log(__LINE__.var_export($ret,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cli,true).PHP_EOL,3,'/tmp/markyuan'); $cli->close(); } error_log(__LINE__.'---------- end --- http --------------'.PHP_EOL,3,'/tmp/markyuan'); } private static function multihttp(){ error_log(__LINE__.'---------- begin --- multi --------------'.PHP_EOL,3,'/tmp/markyuan'); $cliAA= new Swoole\Coroutine\Http\Client('0.0.0.0', 9510); $cliAA->set(['timeout' => 1]); $cliAA->setHeaders([ 'Host' => "api.mp.qq.com", "User-Agent" => 'Chrome/49.0.2587.3', ]); $cliBB= new Swoole\Coroutine\Http\Client('0.0.0.0', 9510); $cliBB->set([ 'timeout' => 1]);// $cliBB->setHeaders([ 'Host' => "api.mp.qq.com", "User-Agent" => 'Chrome/49.0.2587.3', ]); error_log(__LINE__.var_export($cliAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliBB,true).PHP_EOL,3,'/tmp/markyuan'); $retAA=$cliAA->setDefer(1); $retBB=$cliBB->setDefer(1); error_log(__LINE__.var_export($retAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($retBB,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliBB,true).PHP_EOL,3,'/tmp/markyuan'); $retAA = ($cliAA->get('/cn/token?appid=AAA')); $retBB = ($cliBB->get('/cn/token?appid=BBB')); error_log(__LINE__.var_export($retAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($retBB,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliBB,true).PHP_EOL,3,'/tmp/markyuan'); $retAA=$cliAA->recv(); $retBB=$cliBB->recv(); error_log(__LINE__.var_export($retAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($retBB,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliAA,true).PHP_EOL,3,'/tmp/markyuan'); error_log(__LINE__.var_export($cliBB,true).PHP_EOL,3,'/tmp/markyuan'); $retAA=$cliAA->close(); $retBB=$cliBB->close(); error_log(__LINE__.'---------- end --- multi --------------'.PHP_EOL,3,'/tmp/markyuan'); } private static function tcp(){ for($i=0;$i<2;$i++){ $tcp_cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $ret = $tcp_cli ->connect("0.0.0.0", 9511); $ret = $tcp_cli ->send('test for the coro'); $ret = $tcp_cli ->recv(); $ret=$tcp_cli->close(); } } private static function coro_dns(){ swoole_async_set(array('use_async_resolver'=>1)); swoole_async_set(array('dns_cache_refresh_time'=>0)); $ret=swoole_async_dns_lookup_coro("www.baidu.com",0.5); error_log(' ip and host '.$host.print_r($ret,true),'3','/home/yuanyizhi/markyuan/markyuan.log'); return $ret; // swoole_async_dns_lookup("www.baidu.com", function($host, $ip){ // error_log(' ip and host '.$host.' and ip '.$ip,'3','/home/yuanyizhi/markyuan/markyuan.log'); // }); } private static function tcpmulti(){ $cliAA = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cliBB = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $retAA = $cliAA ->connect("0.0.0.0", 9511); $retBB = $cliBB ->connect("0.0.0.0", 9511); $retAA = $cliAA ->send('test for the coro'); $retBB = $cliBB ->send('test for the coro'); $retAA = $cliAA->recv(); $retBB = $cliBB->recv(); $cliAA->close(); $cliBB->close(); } public static function onRequest($request, $response) { // self::multihttp(); // self::http(); //self::https(); // self::tcp(); // self::tcpmulti(); $ret=self::coro_dns(); $response->end(print_r($ret,true)); } public static function staticFunc() { echo "in static function"; } } $server = new Server(); $server->run(); ================================================ FILE: examples/coroutine/join.php ================================================ get(); $cid = \Swoole\Coroutine::getCid(); echo "cid:$cid ".var_export($object,1)."\n"; }); go(function() use ($pool){ $object = $pool->get(); $cid = \Swoole\Coroutine::getCid(); echo "cid:$cid ".var_export($object,1)."\n"; }); ================================================ FILE: examples/coroutine/list_coroutines.php ================================================ '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test', ); echo "connect\n"; $ret1 = $db->connect($server); var_dump($ret1); echo "prepare\n"; $ret2 = $db->query('SELECT * FROM userinfo WHERE id=3'); var_dump($ret2); $chan->push($db); }); go(function () use ($chan) { $db = $chan->pop(); $ret2 = $db->query('SELECT * FROM userinfo WHERE id=3'); var_dump($ret2); }); ================================================ FILE: examples/coroutine/mysql_escape.php ================================================ connect([ 'host' => '127.0.0.1', 'port' => 3306, 'user' => 'root', 'password' => 'root', 'database' => 'test', ]); $res = $swoole_mysql->escape(""); var_dump($res); }); ================================================ FILE: examples/coroutine/mysql_execute_empty.php ================================================ '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test' ]; $db->connect($server); $stmt = $db->prepare('SELECT * FROM `userinfo`'); $ret = $stmt->execute(); var_dump($ret); }); ================================================ FILE: examples/coroutine/mysql_prepare.php ================================================ '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test', ); echo "connect\n"; $ret1 = $db->connect($server); var_dump($ret1); echo "prepare [1]\n"; $stmt1 = $db->prepare('SELECT * FROM userinfo WHERE id=?'); var_dump($stmt1); if ($stmt1 == false) { var_dump($db->errno, $db->error); } echo "execute\n"; $ret3 = $stmt1->execute(array(10)); var_dump(count($ret3)); echo "prepare [2]\n"; $stmt2 = $db->prepare('SELECT * FROM userinfo WHERE id > ? and level > ?'); var_dump($stmt2); if ($stmt2 == false) { var_dump($db->errno, $db->error); } echo "execute\n"; $ret4 = $stmt2->execute(array(10, 99)); var_dump($ret4); }); ================================================ FILE: examples/coroutine/mysql_prepare_2.php ================================================ '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test', ); echo "connect\n"; $ret1 = $db->connect($server); var_dump($ret1); echo "prepare [1]\n"; $stmt1 = $db->prepare('show tables'); echo "execute\n"; $ret1 = $stmt1->execute([]); var_dump($ret1); }); ================================================ FILE: examples/coroutine/mysql_procedure_exec.php ================================================ '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test' ]; $clear = <<connect($server); if ($db->query($clear) && $db->query($procedure)) { $stmt = $db->prepare('CALL say(?)'); $ret = $stmt->execute(['hello mysql!']); var_dump(current($ret[0])); // you said: "hello mysql!" } }); ================================================ FILE: examples/coroutine/mysql_query.php ================================================ 1]); co::create(function() { $function = new ReflectionFunction('title'); $function->invoke(); echo "invoke444\n"; }); function title() { echo "333invoke_________________________________\n"; $tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); var_dump($tcpclient->connect('127.0.0.1', 9501, 1)); } echo "111\n"; echo "222\n"; co::go(); ================================================ FILE: examples/coroutine/mysql_unixsocket.php ================================================ 'unix:/tmp/mysql.sock', 'user' => 'root', 'password' => 'root', 'database' => 'test' ]; $db->connect($server); $stmt = $db->prepare('SELECT * FROM `user` WHERE id=?'); $ret = $stmt->execute([1]); var_dump($ret); }); ================================================ FILE: examples/coroutine/pdo/pdo_persistent.phpt ================================================ true) ); echo "connected\n"; sleep(30); echo "sleep 30\n"; $pdo->exec("SELECT sleep(1)"); }); echo "DONE\n"; ================================================ FILE: examples/coroutine/proc_open.php ================================================ array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w"), ); $process = proc_open('unknown', $descriptorspec, $pipes); var_dump($pipes); var_dump(fread($pipes[2], 8192)); $return_value = proc_close($process); echo "command returned $return_value\n"; }); ================================================ FILE: examples/coroutine/reconnect_test.php ================================================ set([ 'worker_num' => 1, ]); $server->on('Request', function ($request, $response) { /* $mysql = new Swoole\Coroutine\MySQL(); $res = $mysql->connect(['host' => '192.168.244.128', 'user' => 'mha_manager', 'password' => 'mhapass', 'database' => 'tt']); if ($res == false) { $response->end("MySQL connect fail!"); return; } $res = $mysql->connect(['host' => '192.168.244.128', 'user' => 'mha_manager', 'password' => 'mhapass', 'database' => 'tt']); if ($res == false) { $response->end("MySQL connect fail!"); return; } $mysql->close(); $res = $mysql->connect(['host' => '192.168.244.128', 'user' => 'mha_manager', 'password' => 'mhapass', 'database' => 'tt']); if ($res == false) { $response->end("MySQL connect fail!"); return; } $res = $mysql->query('select sleep(1)', 2); var_dump($res); $res = $mysql->connect(['host' => '192.168.244.128', 'user' => 'mha_manager', 'password' => 'mhapass', 'database' => 'tt']); if ($res == false) { $response->end("MySQL connect fail!"); return; } $res = $mysql->query('select sleep(1)', 2); var_dump($res); */ $redis = new Swoole\Coroutine\Redis(); $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { $response->end("Redis connect fail!"); return; } $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { $response->end("Redis connect fail!"); return; } $redis->close(); $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { $response->end("Redis connect fail!"); return; } $res = $redis->get('key'); var_dump($res); $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { $response->end("Redis connect fail!"); return; } $res = $redis->get('key'); var_dump($res); $response->end('Test End'); }); $server->start(); ================================================ FILE: examples/coroutine/redis/auth.php ================================================ connect('127.0.0.1', 6379); $redis->auth('root'); $redis->set('key', 'swoole redis work'); var_dump($redis->get('key')); }); ================================================ FILE: examples/coroutine/redis/defer.php ================================================ connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis->setDefer(); $redis->set('key1', 'value'); $redis2 = new Swoole\Coroutine\Redis(); $redis2->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis2->setDefer(); $redis2->get('key1'); $result1 = $redis->recv(); $result2 = $redis2->recv(); var_dump($result1, $result2); }); ================================================ FILE: examples/coroutine/redis/eval.php ================================================ connect('127.0.0.1', 6379); $res = $redis->eval("return redis.call('get', 'key')"); var_dump($res); }); ================================================ FILE: examples/coroutine/redis/get.php ================================================ connect("127.0.0.1", 6379)); var_dump($redis->get("key")); var_dump($redis->set("key_22", str_repeat('A', 8192*256))); var_dump($redis->mget(["key", "key_22"])); }); ================================================ FILE: examples/coroutine/redis/multi.php ================================================ connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis->multi(); $redis->set('key3', 'rango'); $redis->get('key1'); $redis->get('key2'); $redis->get('key3'); $result = $redis->exec(); var_dump($result); }); ================================================ FILE: examples/coroutine/redis/pipeline.php ================================================ connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis->setDefer(); $redis->set('key1', 'value'); $redis->get('key1'); $result1 = $redis->recv(); $result2 = $redis->recv(); var_dump($result1, $result2); }); ================================================ FILE: examples/coroutine/redis/pub.php ================================================ connect('127.0.0.1', 6379); while (true) { $msg = $redis->publish('msg_1', 'hello-' . $i++); var_dump($msg); co::sleep(1); } }); ================================================ FILE: examples/coroutine/redis/request.php ================================================ connect('127.0.0.1', 6379); $res = $redis->request(['object', 'encoding', 'key1']); var_dump($res); }); ================================================ FILE: examples/coroutine/redis/serialize.php ================================================ connect('127.0.0.1', 6379, true); // param3 is serialize $redis->set('foo', ['bar' => 'baz']); $ret = $redis->get('foo'); var_dump($ret); }); ================================================ FILE: examples/coroutine/redis/sub.php ================================================ connect('127.0.0.1', 6379); $msg = $redis->subscribe(array("msg_1")); while ($msg = $redis->recv()) { var_dump($msg); } }); go(function () { $redis = new Swoole\Coroutine\Redis(); $redis->connect('127.0.0.1', 6379); $msg = $redis->subscribe(array("msg_2")); while ($msg = $redis->recv()) { var_dump($msg); } }); ================================================ FILE: examples/coroutine/redis_pool.php ================================================ on('Request', function($request, $response) use(&$count, $pool) { if (count($pool) == 0) { $redis = new Swoole\Coroutine\Redis(); $res = $redis->connect('127.0.0.1', 6379); if ($res == false) { $response->end("redis connect fail!"); return; } $pool->enqueue($redis); } $redis = $pool->dequeue(); $count ++; $ret = $redis->set('key', 'value'); $response->end("swoole response is ok, count = $count, result=" . var_export($ret, true)); $pool->enqueue($redis); }); $server->start(); ================================================ FILE: examples/coroutine/redis_subscribe.php ================================================ connect('127.0.0.1', 6379); while (true) { $val = $redis->subscribe(['test']); //订阅的channel,以第一次调用subscribe时的channel为准,后续的subscribe调用是为了收取Redis Server的回包 //如果需要改变订阅的channel,请close掉连接,再调用subscribe var_dump($val); } }); ================================================ FILE: examples/coroutine/scheduler.php ================================================ pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/2.php ================================================ pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/3.php ================================================ push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { $read_list = [$c1]; $write_list = null; echo "select yield\n"; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($read_list) { foreach($read_list as $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/4.php ================================================ push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1, $num) { echo "pop start\n"; for ($i=0;$i<$num;$i++) { $ret = $c1->pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/5.php ================================================ push($a); } if ($read_list) { $ret = $read_list[0]->pop(); if ($ret === 1) { echo "quit\n"; return 1; } } } }); } $num = 10; go(function () use ($c1, $c2, $num) { for ($i = 0; $i < $num; $i ++) { $ret = $c1->pop(); echo "fibonacci @$i $ret\n"; } $c2->push(1); }); fibonacci($c1, $c2); ================================================ FILE: examples/coroutine/select/6.php ================================================ pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/7.php ================================================ push("chan2-$i"); echo "chan 2 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { $read_list = [$c1]; $write_list = null; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($read_list) { foreach($read_list as $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); echo "chan1 pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "chan1 push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("chan1-$i"); echo "chan1 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c2,$num) { echo "chan2 pop start\n"; for ($i=0;$i<$num;$i++) { $ret = $c2->pop(); echo "chan2 pop [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/8.php ================================================ push("chan2-$i"); echo "chan 2 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$c2,$num) { $ori_list = $read_list = [$c1,$c2]; $write_list = null; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($ori_list) { foreach ($ori_list as $chan => $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); $chan_id = $chan + 1; echo "chan{$chan_id} pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "chan1 push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("chan1-$i"); echo "chan1 push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ================================================ FILE: examples/coroutine/select/9.php ================================================ push("chan1-$i"); echo "chan push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1) { $ret = $c1->pop(); echo "chan pop ret:".var_export($ret,1)."\n"; co::sleep(1); $ret = $c1->pop(); echo "chan pop ret:".var_export($ret,1)."\n"; }); ================================================ FILE: examples/coroutine/select/poptimeout.php ================================================ pop(1); $end = microtime(1); echo "chan pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; echo "use time:".($end-$start)."s\n"; }); go(function () use ($c1) { co::sleep(2); echo "sleep 2\n"; $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ================================================ FILE: examples/coroutine/select/test.php ================================================ push($i); echo "push {$i} res:".var_export($ret, 1)."\n"; }); }; go(function ()use ($chan){ $bool = true; while ($bool){ $data = $chan->pop(); echo "pop res:".var_export($data, 1)."\n"; if($data===false){ $bool = false; } //var_dump($data); } }); ================================================ FILE: examples/coroutine/send_yield.php ================================================ set(array( 'worker_num' => 1, 'send_yield' => true, 'socket_buffer_size' => 512 * 1024, 'kernel_socket_buffer_size' => 65536, )); $serv->on('connect', function ($serv, $fd) { echo "Client:Connect.\n"; }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { $length = 0; $size = 1024 * 128; while (true) { $ret = $serv->send($fd, str_repeat('A', $size)); if ($ret == false) { break; } $length += $size; echo "send $length success\n"; } }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->start(); ================================================ FILE: examples/coroutine/send_yield_client.php ================================================ set(array( 'kernel_socket_buffer_size' => 65536, )); if (!$client->connect('127.0.0.1', 9501, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } var_dump($client->getsockname()); $client->send("start\n"); $length = 0; while(true) { $data = $client->recv(65536); if ($data == false) { break; } $length += strlen($data); echo "recv ".$length." bytes\n"; usleep(100000); } ================================================ FILE: examples/coroutine/server/tcp.php ================================================ handle(function (Connection $conn) { while (true) { $data = $conn->recv(); if (!$data) { break; } $conn->send("hello $data"); } $conn->close(); }); $server->start(); }); Swoole\Event::wait(); ================================================ FILE: examples/coroutine/sleep.php ================================================ set([ 'worker_num' => 1, ]); $server->on('Request', function ($request, $response) { Swoole\Coroutine::sleep(0.2); $response->end('Test End'); }); $server->start(); ================================================ FILE: examples/coroutine/socket/accept.php ================================================ bind('127.0.0.1', 9601); var_dump($ret); assert($sock->listen(512)); $conn = $sock->accept(); $data = $conn->recv(); var_dump($data); $json = json_decode($data, true); var_dump($json); $ret = $conn->send("world\n"); echo "send res {$ret} \n"; $conn->close(); }); ================================================ FILE: examples/coroutine/socket/sendto.php ================================================ connect('127.0.0.1', 9601); echo "connect ret:".var_export($ret,1)." error:".var_export($conn->errCode,1)."\n"; $ret = $conn->send(json_encode(['data' => 'hello'])); echo "send ret:".var_export($ret,1)."\n"; echo $conn->recv(); }); echo "end \n"; ================================================ FILE: examples/coroutine/stack/1.php ================================================ 8192*4]); function test($n) { $a = 1; $b = 2; $c = 3; $d = 4; static $i; usleep(100000); echo "index=".($i++)."\n"; return test($n + $a + $b + $c + $d); } go(function () { test(9); }); ================================================ FILE: examples/coroutine/statvfs.php ================================================ set([ 'worker_num' => 1, 'task_worker_num' => 2, ]); $server->on('Task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { echo "#{$serv->worker_id}\tonTask: worker_id={$worker_id}, task_id=$task_id\n"; if ($serv->worker_id == 1) { sleep(1); } return $data; }); $server->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $server->on('Request', function ($request, $response) use ($server) { $result = $server->taskCo(["hello world", ['data' => 1234, 'code' => 200]], 0.5); $response->end('Test End, Result: '.var_export($result, true)); }); $server->start(); ================================================ FILE: examples/coroutine/tcp_backend_serv.php ================================================ set(array( 'worker_num' => 1, //工作进程数量 'daemonize' => true, //是否作为守护进程 )); $serv->on('connect', function ($serv, $fd){ echo "Client:Connect.\n"; }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { $serv->send($fd, 'Swoole: '.$data); $serv->close($fd); }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->start(); ================================================ FILE: examples/coroutine/tcp_echo.php ================================================ on('connect', function ($serv, $fd, $reactor_id){ echo "[#".posix_getpid()."]\tClient@[$fd]: Connect.\n"; }); $serv->set(array( 'worker_num' => 1, )); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".$serv->worker_id."]\tClient[$fd] receive data: $data\n"; if ($serv->send($fd, "{$data}\n") == false) { echo "error\n"; } }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/coroutine/test.php ================================================ $func) { go(function()use($channel,$func,$key){ $res = $func(); $channel->push([$key=>$res]); }); } } function test($value='') { \Co::sleep(1); return "test\n"; } function test2($value='') { \Co::sleep(1); return "test2 ".rand(1,10)."\n"; } go(function(){ $c = 2; $channel = new \Swoole\Coroutine\Channel(2); $task = ["test","test2","test"]; BatchExecMethodByCo($channel,$task); $list = []; $num = count($task); for ($i=0;$i<$num;$i++) { $list[$i] = $channel->pop(); } var_dump($list); }); ================================================ FILE: examples/coroutine/timer_test.php ================================================ ================================================ FILE: examples/coroutine/udp_client.php ================================================ data = "swoole test"; $this->send(); $this->moreThanOneRecv(); return $ret; } public function send() { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $ret = $cli->connect($this->ip, self::PORT); $cli->send($this->data); $ret = $cli->recv(); $cli->close(); } public function moreThanOneRecv() { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $ret = $cli->connect($this->ip, self::PORT); $cli->send("sent by cli"); $cli2 = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $ret = $cli2->connect($this->ip, self::PORT); $cli2->send("sent by cli2"); $cli3 = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $ret = $cli3->connect($this->ip, self::PORT); $cli3->send("sent by cli3"); sleep(1); $ret = $cli3->recv(); $ret = $cli2->recv(); $ret = $cli->recv(); return; } } class Server { public $server; public function run() { $this->server = new Swoole\Http\Server("127.0.0.1", 9502); $this->server->set([ 'worker_num' => 1, 'daemonize' => true, 'log_file' => '/tmp/swoole.log', ]); $this->server->on('Request',['Server', 'onRequest']); $this->server->start(); } public static function onRequest($request, $response) { self::staticFunc(); $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $client = new Client(); $ret = $client->sendRequest(); $response->end($ret); } } $server = new Server(); $server->run(); ================================================ FILE: examples/coroutine/udp_tcp_timeout.php ================================================ setting = $setting; } /** * [init description] * @return [type] [description] */ public function init(){ if (!isset($this ->setting['host'])) { $this ->setting['host'] = '0.0.0.0'; } if (!isset($this ->setting['port'])) { $this ->setting['port'] = '9999'; } $this ->http = new Swoole\Http\Server($this ->setting['host'], $this ->setting['port']); $this ->http ->set($this ->setting); $this ->http ->on('request', array($this, 'onRequest')); $this ->http ->on('close', array($this, 'onClose')); } /** * [onRequest description] * @param [type] $request [description] * @param [type] $response [description] * @return [type] [description] */ public function onRequest($request, $response){ //$udp_cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $tcp_cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); // $ret = $udp_cli ->connect('10.100.65.222', 9906); // $ret = $udp_cli ->send('test for the coro'); // $ret = $udp_cli ->recv(100); // $udp_cli->close(); // if ($ret) { // //error_log(" udp cli get rsp == " . print_r($ret, true),3, '/data/log/udp_timeout.log'); // } // else{ // error_log(" udp cli timeout \n",3, '/data/log/udp_timeout.log'); // } $ret = $tcp_cli ->connect("10.100.64.151", 9805); $ret = $tcp_cli ->send('test for the coro'); $ret = $tcp_cli ->recv(100); $tcp_cli->close(); if ($ret) { //error_log(" tcp cli get rsp == " . print_r($ret, true) . PHP_EOL, 3, '/data/log/udp_timeout.log'); } else{ error_log(" tcp cli timeout \n",3, '/data/log/udp_timeout.log'); } $response ->end(" swoole response is ok"); } /** * [onClose description] * @param [type] $server [description] * @param [type] $fd [description] * @param [type] $reactor_id [description] * @return [type] [description] */ public function onClose($server, $fd, $reactor_id){ //echo " on close fd = $fd reactor_id = $reactor_id \n"; } /** * [start description] * @return [type] [description] */ public function start(){ $this ->init(); $this ->http ->start(); } } $setting = array( 'host' => '0.0.0.0', 'port' => 10006, 'worker_num' => 4, 'dispatch_mode' => 3, //固定分配请求到worker 'reactor_num' => 4, //亲核 'daemonize' => 1, //守护进程 'backlog' => 128, 'log_file' => '/data/log/test_http_server.log', ); $th = new TestHttpServer(); $th ->set($setting); $th ->start(); ================================================ FILE: examples/coroutine/user_coroutine.php ================================================ connect('127.0.0.1', 6379); $ret = $redis->incr('coroutine'); $redis->close(); if ($i == 50) { Swoole\Coroutine::create(function() use ($i) { $redis = new Swoole\Coroutine\Redis(); $res = $redis->connect('127.0.0.1', 6379); $ret = $redis->set('coroutine_i', 50); $redis->close(); }); } }); } ================================================ FILE: examples/coroutine/util/resume001.php ================================================ chan = new chan; } function add() { $this->count++; } function done() { $this->chan->push(true); } function wait() { while ($this->count--) { $this->chan->pop(); } } } go(function () { $wg = new WaitGroup; for($i=0;$i<10;$i++) { $wg->add(); go(function() use ($wg, $i) { co::sleep(.3); echo "hello $i\n"; $wg->done(); }); } $wg->wait(); echo "all done\n"; }); ================================================ FILE: examples/coroutine/websocket/client.php ================================================ upgrade("/"); // if ($ret) { // while(true) { // $cli->push("hello"); // var_dump($cli->recv()); // co::sleep(0.1); // } // } // }); Co\Run(function () { $cli = new Co\http\Client("127.0.0.1", 9501); $cli->set([ 'timeout' => 1 ]); $ret = $cli->upgrade("/websocket"); if (!$ret) { echo "ERROR\n"; return; } $cli->push("websocket handshake 1\n"); $cli->push("websocket handshake 2\n"); var_dump($cli->recv()); for ($i = 0; $i < 5; $i ++) { $cli->push("hello @$i"); var_dump($cli->recv()); co::sleep(0.1); } }); ================================================ FILE: examples/coroutine/websocket/co_server.php ================================================ handle('/websocket', function ($request, $ws) { $ws->upgrade(); while (true) { $frame = $ws->recv(); if ($frame === false) { echo "error : " . swoole_last_error() . "\n"; break; } else if ($frame == '') { break; } else { if ($frame->data == "close") { $ws->close(); return; } $ws->push("Hello {$frame->data}!"); $ws->push("How are you, {$frame->data}?"); } } }); $server->handle('/', function ($request, $response) { $response->end(<<Swoole WebSocket Server

HTML ); }); $server->start(); }); ================================================ FILE: examples/coroutine/websocket/server.php ================================================ set(array( 'log_file' => '/dev/null' )); $ws->on("WorkerStart", function (\Swoole\Server $serv) { }); $ws->on('open', function ($serv, Swoole\Http\Request $request) { //$ip = co::gethostbyname('www.baidu.com'); if (1) { $serv->push($request->fd, "start\n"); } }); $ws->on('message', function ($serv, $frame) { var_dump($frame); co::sleep(0.1); $data = $frame->data; $serv->push($frame->fd, "hello client {$data}\n"); }); $ws->start(); ================================================ FILE: examples/cpp/Makefile ================================================ all: co repeat test_server co: co.cc g++ co.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o co -g repeat: repeat.cc g++ repeat.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o repeat -g test_server: test_server.cc g++ test_server.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o test_server -g clean: rm -f co repeat test_server ================================================ FILE: examples/cpp/co.cc ================================================ #include "swoole_coroutine.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" #include "swoole_signal.h" #include #include #include #include using swoole::Coroutine; using swoole::coroutine::Socket; using swoole::coroutine::System; using namespace std; list q; list slaves; size_t qs; int main(int argc, char **argv) { signal(SIGPIPE, SIG_IGN); swoole::coroutine::run([](void *arg) { Coroutine::create([](void *arg) { System::sleep(2.0); cout << "CO-1, sleep 2\n"; }); Coroutine::create([](void *arg) { System::sleep(1); cout << "CO-2, sleep 1\n"; }); Coroutine::create([](void *arg) { cout << "CO-3, listen tcp:0.0.0.0:9001\n"; Socket s(SW_SOCK_TCP); s.bind("0.0.0.0", 9001); s.listen(); while (1) { Socket *_client = s.accept(); Coroutine::create( [](void *arg) { Socket *client = (Socket *) arg; while (1) { char buf[1024]; auto retval = client->recv(buf, sizeof(buf)); if (retval == 0) { cout << "connection close\n"; break; } else { if (strncasecmp("push", buf, 4) == 0) { q.push_back(string(buf + 5, retval - 5)); qs += retval - 5; string resp("OK\n"); client->send(resp.c_str(), resp.length()); for (auto it = slaves.begin(); it != slaves.end();) { auto sc = *it; auto n = sc->send(buf, retval); if (n <= 0) { it = slaves.erase(it); delete sc; } else { it++; } } } else if (strncasecmp("pop", buf, 3) == 0) { if (q.empty()) { string resp("EMPTY\n"); client->send(resp.c_str(), resp.length()); } else { auto data = q.front(); q.pop_front(); qs -= data.length(); client->send(data.c_str(), data.length()); } } else if (strncasecmp("stat", buf, 4) == 0) { char stat_buf[64]; int n = snprintf(stat_buf, sizeof(stat_buf), "count=%ld,bytes=%ld\n", q.size(), qs); client->send(stat_buf, n); } else { string resp("ERROR\n"); client->send(resp.c_str(), resp.length()); } } } delete client; }, _client); } }); Coroutine::create([](void *arg) { Socket s(SW_SOCK_TCP); s.bind("0.0.0.0", 9002); s.listen(); while (1) { Socket *_client = s.accept(); if (_client == nullptr) { break; } for (auto data : q) { _client->send(data.c_str(), data.length()); } slaves.push_back(_client); } }); }); return 0; } ================================================ FILE: examples/cpp/repeat.cc ================================================ #include "swoole_server.h" using namespace swoole; int main(int argc, char **argv) { swoole_init(); enum Server::Mode factory_mode; if (argc > 1) { factory_mode = Server::MODE_PROCESS; } else { factory_mode = Server::MODE_BASE; } for (int i = 0; i < 2; i++) { Server serv(factory_mode); serv.reactor_num = 1; serv.worker_num = 1; serv.onReceive = [](Server *serv, RecvData *req) { return SW_OK; }; serv.onPacket = [](Server *serv, RecvData *req) { return SW_OK; }; serv.onWorkerStart = [](Server *serv, Worker *worker) { swoole_notice("WorkerStart[%d]PID=%d, serv=%p,", worker->id, getpid(), serv); swoole_timer_after( 1000, [serv](Timer *, TimerNode *tnode) { printf("timer=%p\n", tnode); if (serv->is_base_mode()) { kill(getpid(), SIGTERM); } else { kill(serv->gs->master_pid, SIGTERM); } }, nullptr); }; serv.add_port(SW_SOCK_UDP, "0.0.0.0", 9502); serv.add_port(SW_SOCK_TCP6, "::", 9503); serv.add_port(SW_SOCK_UDP6, "::", 9504); ListenPort *port = serv.add_port(SW_SOCK_TCP, "127.0.0.1", 9501); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); } port->open_eof_check = 0; // config port->backlog = 128; memcpy(port->protocol.package_eof, SW_STRL("\r\n\r\n")); if (serv.create()) { swoole_warning("create server fail[error=%d]", swoole_get_last_error()); exit(1); } if (serv.start() < 0) { swoole_warning("start server fail[error=%d]", swoole_get_last_error()); exit(3); } } return 0; } ================================================ FILE: examples/cpp/test_server.cc ================================================ /** * cmake . * make test_server * ./bin/test_server */ #include "swoole_server.h" #include "swoole_util.h" using namespace swoole; int my_onPacket(Server *serv, RecvData *req); int my_onReceive(Server *serv, RecvData *req); void my_onStart(Server *serv); void my_onShutdown(Server *serv); void my_onConnect(Server *serv, DataHead *info); void my_onClose(Server *serv, DataHead *info); void my_onWorkerStart(Server *serv, Worker *worker); void my_onWorkerStop(Server *serv, Worker *worker); static int g_receive_count = 0; int main(int argc, char **argv) { swoole_init(); sw_logger()->set_date_format("%F %T"); sw_logger()->set_date_with_microseconds(true); Server serv(Server::MODE_BASE); serv.reactor_num = 4; serv.worker_num = 1; serv.set_max_connection(10000); // serv.open_cpu_affinity = 1; // serv.open_tcp_nodelay = 1; // serv.daemonize = 1; // memcpy(serv.log_file, SW_STRS("/tmp/swoole.log")); serv.dispatch_mode = 2; // serv.open_tcp_keepalive = 1; #ifdef HAVE_OPENSSL // serv.ssl_cert_file = "tests/ssl/ssl.crt"; // serv.ssl_key_file = "tests/ssl/ssl.key"; // serv.open_ssl = 1; #endif serv.onStart = my_onStart; serv.onShutdown = my_onShutdown; serv.onConnect = my_onConnect; serv.onReceive = my_onReceive; serv.onPacket = my_onPacket; serv.onClose = my_onClose; serv.onWorkerStart = my_onWorkerStart; serv.onWorkerStop = my_onWorkerStop; // swSignal_set(SIGINT, user_signal); serv.add_port(SW_SOCK_UDP, "0.0.0.0", 9502); serv.add_port(SW_SOCK_TCP6, "::", 9503); serv.add_port(SW_SOCK_UDP6, "::", 9504); ListenPort *port = serv.add_port(SW_SOCK_TCP, "127.0.0.1", 9501); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); } port->open_eof_check = 0; // config port->backlog = 128; memcpy(port->protocol.package_eof, SW_STRL("\r\n\r\n")); if (serv.create()) { swoole_warning("create server fail[error=%d]", swoole_get_last_error()); exit(1); } if (serv.start() < 0) { swoole_warning("start server fail[error=%d]", swoole_get_last_error()); exit(3); } return 0; } void my_onWorkerStart(Server *serv, Worker *worker) { swoole_notice("WorkerStart[%d]PID=%d", worker->id, worker->pid); } void my_onWorkerStop(Server *serv, Worker *worker) { swoole_notice("WorkerStop[%d]PID=%d", worker->id, worker->pid); } int my_onReceive(Server *serv, RecvData *req) { char req_data[SW_IPC_BUFFER_SIZE]; char resp_data[SW_IPC_BUFFER_SIZE]; g_receive_count++; Connection *conn = serv->get_connection_by_session_id(req->info.fd); memcpy(req_data, req->data, req->info.len); swoole::rtrim(req_data, req->info.len); swoole_notice("onReceive[%d]: ip=%s|port=%d Data=%s|Len=%d", g_receive_count, conn->info.get_addr(), conn->info.get_port(), req_data, req->info.len); int n = sw_snprintf(resp_data, SW_IPC_BUFFER_SIZE, "Server: %.*s\n", req->info.len, req_data); if (!serv->send(req->info.fd, resp_data, n)) { swoole_notice("send to client fail. errno=%d", errno); } else { swoole_notice("send %d bytes to client success. data=%s", n, resp_data); } return SW_OK; } int my_onPacket(Server *serv, RecvData *req) { char address[256]; int port = 0; int ret = 0; DgramPacket *packet = (DgramPacket *) req->data; auto serv_socket = serv->get_server_socket(req->info.server_fd); if (packet->socket_type == SW_SOCK_UDP) { inet_ntop(AF_INET, &packet->socket_addr.addr.inet_v4.sin_addr, address, sizeof(address)); port = ntohs(packet->socket_addr.addr.inet_v4.sin_port); } else if (packet->socket_type == SW_SOCK_UDP6) { inet_ntop(AF_INET6, &packet->socket_addr.addr.inet_v6.sin6_addr, address, sizeof(address)); port = ntohs(packet->socket_addr.addr.inet_v6.sin6_port); } else if (packet->socket_type == SW_SOCK_UNIX_DGRAM) { strcpy(address, packet->socket_addr.addr.un.sun_path); } else { abort(); } char *data = packet->data; uint32_t length = packet->length; swoole_notice("Packet[client=%s:%d, %d bytes]: data=%.*s", address, port, length, length, data); char resp_data[SW_IPC_BUFFER_SIZE]; int n = sw_snprintf(resp_data, SW_IPC_BUFFER_SIZE, "Server: %.*s", length, data); ret = serv_socket->sendto(address, port, resp_data, n); if (ret < 0) { swoole_notice("send to client fail. errno=%d", errno); } else { swoole_notice("send %d bytes to client success. data=%s", n, resp_data); } return SW_OK; } void my_onStart(Server *serv) { swoole_notice("Server is running"); } void my_onShutdown(Server *serv) { swoole_notice("Server is shutdown"); } void my_onConnect(Server *serv, DataHead *info) { swoole_notice("PID=%d\tConnect fd=%ld|reactor_id=%d", getpid(), info->fd, info->reactor_id); } void my_onClose(Server *serv, DataHead *info) { swoole_notice("PID=%d\tClose fd=%ld|reactor_id=%d", getpid(), info->fd, info->reactor_id); } ================================================ FILE: examples/curl/hook.php ================================================ SWOOLE_HOOK_ALL | SWOOLE_HOOK_NATIVE_CURL, ]); //Co::set(['hook_flags' => SWOOLE_HOOK_ALL, ]); Co\run(function () { $n = 3; while($n--) { go('test'); } }); function test() { echo "curl init\n"; $ch = curl_init(); // $url = 'https://www.baidu.com/'; $url = "http://127.0.0.1:9801/"; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { //var_dump($ch, $strHeader); return strlen($strHeader); }); $output = curl_exec($ch); var_dump($output); var_dump(strlen($output)); if ($output === false) { echo "CURL Error:" . curl_error($ch); } // var_dump($output); curl_close($ch); } ================================================ FILE: examples/curl/multi.php ================================================ set(['worker_num' => 8, ]); $http->on("start", function ($server) { echo "Swoole http server is started at http://127.0.0.1:9501\n"; }); $http->on("request", function ($request, $response) { sleep(1); $response->end("Hello World\n"); }); $http->start(); ================================================ FILE: examples/dtls/client.php ================================================ connect("127.0.0.1", 9905); echo "connect OK\n"; $client->send("hello world"); echo $client->recv(); $client->close(); echo "END\n"; } ); ================================================ FILE: examples/dtls/server.php ================================================ set( [ 'ssl_cert_file' => __DIR__ . '/../ssl/ssl.crt', 'ssl_key_file' => __DIR__ . '/../ssl/ssl.key', //'ssl_method' => SWOOLE_TLSv1_2_SERVER_METHOD, 'worker_num' => 1, //'ssl_client_cert_file' => __DIR__ . '/ca.crt', //'ssl_verify_depth' => 10, ] ); $server->on('Receive', function (Swoole\Server $serv, $fd, $tid, $data) { var_dump($fd, $data, $serv->getClientInfo($fd)); $serv->send($fd, "Swoole: $data\n"); //echo "close dtls session\n"; //$serv->close($fd); }); $server->start(); ================================================ FILE: examples/eof/client.php ================================================ send(substr($data, $i * $chunk_size, $sendn)); } } $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', 9501, 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } //for ($i = 0; $i < 10; $i++) //{ // $client->send("hello world\r\n\r\n"); // echo "send\n"; //} //exit; $data = array( 'name' => __FILE__, 'content' => str_repeat('A', 8192 * rand(1, 3)), //800K ); $_serialize_data = serialize($data); $_send = $_serialize_data . "__doit__"; echo "serialize_data length=" . strlen($_serialize_data) . "send length=" . strlen($_send) . "\n"; //send_chunk($client, $_send); // if (!$client->send($_send)) { die("send failed.\n"); } //$client->send("\r\n".substr($_serialize_data, 0, 8000)); echo $client->recv(); exit; $client->send(substr($_serialize_data, 8000)); //usleep(500000); if (!$client->send("\r\n\r\n")) { die("send failed.\n"); } echo $client->recv(); //sleep(1); ================================================ FILE: examples/eof/server.php ================================================ set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, // 'worker_num' => 4, 'dispatch_mode' => 3, 'package_max_length' => 1024 * 1024 * 2, //2M )); //$serv->on('connect', function ($serv, $fd) { // //echo "[#" . posix_getpid() . "]\tClient:Connect.\n"; //}); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo '#' . $serv->worker_id . " recv: " . strlen($data) . "\n"; for ($i = 0; $i < 1000; $i++) { $resp = str_repeat('A', rand(10000, 50000)) . "\r\n\r\n"; $serv->send($fd, $resp); if ($i % 100 == 1) { sleep(1); echo "send " . strlen($resp) . " bytes\n"; } } }); //$serv->on('close', function ($serv, $fd) { //echo "[#" . posix_getpid() . "]\tClient: Close.\n"; //}); $serv->start(); ================================================ FILE: examples/event/cycle.php ================================================ 10) { echo "finish\n"; Event::del($socket); socket_close($socket); } else { sleep(1); Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); } } function socket_onWrite($socket) { socket_write($socket, "hi swoole"); Event::set($socket, null, null, SWOOLE_EVENT_READ); } function socket_onConnect($socket) { $err = socket_get_option($socket, SOL_SOCKET, SO_ERROR); if ($err == 0) { echo "connect server success\n"; Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ); socket_write($socket, "first package\n"); } else { echo "connect server failed\n"; Event::del($socket); socket_close($socket); } } Event::add($socket, 'socket_onRead', 'socket_onConnect', SWOOLE_EVENT_WRITE); socket_connect($socket, '127.0.0.1', 9501); Event::wait(); ================================================ FILE: examples/event/stdin.php ================================================ \n"); } fwrite($fp, "HELLO world"); function stream_onRead($fp) { echo fread($fp, 1024)."\n"; sleep(1); Swoole\Event::write($fp, "hello world"); //Swoole\Event::set($fp, null, null, SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); //Swoole\Event::del($fp); //fclose($fp); } Swoole\Event::add($fp, 'stream_onRead'); echo "start\n"; Swoole\Event::wait(); ================================================ FILE: examples/event/test.php ================================================ connect('127.0.0.1', 9501); //$type = 'GET'; $type = 'POST'; $cookie = "8MLP_5753_saltkey=RSU8HYED; 8MLP_5753_lastvisit=1426120671; pgv_pvi=1454765056; CNZZDATA1000008050=684878078-1426123263-http%253A%252F%252Fcznews-team.chinaz.com%252F%7C1426485386; attentiondomain=2z.cn%2cchinaz.com%2ckuaishang.cn%2ccxpcms.com; CNZZDATA33217=cnzz_eid%3D1036784254-1426122273-http%253A%252F%252Fcznews-team.chinaz.com%252F%26ntime%3D1427414208; CNZZDATA433095=cnzz_eid%3D1613871160-1426123273-http%253A%252F%252Fcznews-team.chinaz.com%252F%26ntime%3D1427848205; CNZZDATA1254679775=309722566-1427851758-http%253A%252F%252Fcznews-team.chinaz.com%252F%7C1427851758; 8MLP_5753_security_cookiereport=c014Hgufskpv55xgM9UaB%2FZZdMrcN0QqBYdcGomTu8OlTDWzTA0z; 8MLP_5753_ulastactivity=e4a1aRIbgdzoRDd8NlT5CMIwLnWjyjr2hWyfn6T5g82RitUOdf3o; 8MLP_5753_auth=9351LJpv7Xa%2FPUylJDQgRiAONZ5HysOaj%2BqRGb6jYmpqZpRkVc2ibPXm7LAfArC%2FpIpY2Fx%2B59AHqzr843qozZWxWNZi; mytool_user=uSHVgCUFWf5Sv2Y8tKytQRUJW3wMVT3rw5xQLNGQFIsod4C6vYWeGA==; 8MLP_5753_lip=220.160.111.22%2C1428036585; pgv_si=s4245709824; PHPSESSID=t3hp9h4o8rb3956t5pajnsfab1; 8MLP_5753_st_p=1024432%7C1428040399%7Cf7599ba9053aa27e12e9e597a4c372ce; 8MLP_5753_viewid=tid_7701248; 8MLP_5753_smile=5D1; 8MLP_5753_st_t=1024432%7C1428040402%7C46d40e02d899b10b431822eb1d39f6a1; 8MLP_5753_forum_lastvisit=D_140_1427103032D_165_1427427405D_168_1427870172D_167_1427870173D_166_1428021390D_163_1428040402; 8MLP_5753_sid=k25gxK; 8MLP_5753_lastact=1428040403%09misc.php%09patch; cmstop_page-view-mode=view; cmstop_rememberusername=error; cmstop_auth=Jcn2qzVn9nsjqtodER9OphcW3PURDWNx6mO7j0Zbb9k%3D; cmstop_userid=6; cmstop_username=error; Hm_lvt_aecc9715b0f5d5f7f34fba48a3c511d6=1427967317,1428021376,1428036617,1428040224; Hm_lpvt_aecc9715b0f5d5f7f34fba48a3c511d6=1428050417; YjVmNm_timeout=0"; if ($type == 'GET') { $header = "GET /home/explore/ HTTP/1.1\r\n"; $header .= "Host: 127.0.0.1\r\n"; $header .= "Connection: keep-alive\r\n"; $header .= "Cache-Control: max-age=0\r\n"; $header .= "Cookie: $cookie\r\n"; $header .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"; $header .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36\r\n"; $header .= "\r\n"; $_sendStr = $header; } else { // $header = "POST /home/explore/?hello=123&world=swoole#hello HTTP/1.1\r\n"; $header = "POST /post.php HTTP/1.1\r\n"; $header .= "Host: 127.0.0.1\r\n"; $header .= "Referer: http://group.swoole.com/\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ja;q=0.2\r\n"; $header .= "Cookie: pgv_pvi=9559734272; efr__Session=uddfvbm87dtdtrdsro1ohlt4o6; efr_r_uname=apolov%40vip.qq.com; efr__user_login=3N_b4tHW1uXGztWW2Ojf09vssOjR5abS4abO5uWRopnm0eXb7OfT1NbIoqjWzNCvodihq9qaptqfra6imtLXpNTNpduVoque26mniKej5dvM09WMopmmpM2xxcmhveHi3uTN0aegpaiQj8Snoa2IweHP5fCL77CmxqKqmZKp5ejN1c_Q2cPZ25uro6mWqK6BmMOzy8W8k4zi2d3Nlb_G0-PaoJizz97l3deXqKyPoKacr6ynlZ2nppK71t7C4uGarKunlZ-s; pgv_si=s8426935296; Hm_lvt_4967f2faa888a2e52742bebe7fcb5f7d=1410240641,1410241802,1410243730,1410243743; Hm_lpvt_4967f2faa888a2e52742bebe7fcb5f7d=1410248408\r\n"; $header .= "RA-Ver: 2.5.3\r\n"; $header .= "RA-Sid: 2A784AF7-20140212-113827-085a9c-c4de6e\r\n"; $_postData = ['body1' => 'swoole_http-server', 'message' => 'nihao']; $_postBody = json_encode($_postData); // $_postBody = http_build_query($_postData); $header .= "Content-Length: " . strlen($_postBody); echo "http header length=".strlen($header)."\n"; $header .= "Content-Length: " . (strlen($_postBody) - 2); // $cli->send($header); // usleep(100000); $_sendStr = $header . "\r\n\r\n" . $_postBody; // $_sendStr = "\r\n\r\n" . $_postBody; echo "postBody length=".strlen($_postBody)."\n"; } echo "-------------------------Request----------------------------\n"; echo $_sendStr; $cli->send($_sendStr); echo "send ".strlen($_sendStr)." byte\n"; echo "-------------------------Response----------------------------\n"; $data = $cli->recv(); var_dump($data); exit; ================================================ FILE: examples/http/curl.php ================================================ str_repeat('a', 80)); if (function_exists("curl_file_create")) { $cfile = curl_file_create(__DIR__ . '/../test.jpg'); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . __DIR__ . '/../test.jpg'; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 抓取URL并把它传递给浏览器 $res = curl_exec($ch); var_dump($res); // 关闭cURL资源,并且释放系统资源 curl_close($ch); ================================================ FILE: examples/http/detach.php ================================================ set(['task_worker_num' => 1, 'worker_num' => 1]); $http->on('request', function ($req, Swoole\Http\Response $resp) use ($http) { $resp->detach(); $http->task(strval($resp->fd)); }); $http->on('finish', function () { echo "task finish"; }); $http->on('task', function ($serv, $task_id, $worker_id, $data) { var_dump($data); $resp = Swoole\Http\Response::create($data); $resp->end("in task"); echo "async task\n"; }); //$http->on('close', function(){ // echo "on close\n"; //}); $http->on('workerStart', function ($serv, $id) { //var_dump($serv); }); $http->start(); ================================================ FILE: examples/http/download.php ================================================ on('request', function ($req, Swoole\Http\Response $resp) use ($http) { if ($req->server['request_uri'] == '/stream') { $resp->header("Content-Type", "text/event-stream"); $resp->header("Cache-Control", "no-cache"); $resp->header("Connection", "keep-alive"); $resp->header("X-Accel-Buffering", "no"); $resp->header('Content-Encoding', ''); $resp->header("Content-Length", ''); $resp->end(); go(function () use ($resp, $http) { while (true) { Co::sleep(1); $http->send($resp->fd, 'data: ' . base64_encode(random_bytes(random_int(16, 128))). "\n\n"); } }); } elseif ($req->server['request_uri'] == '/') { $resp->end(<< HTML ); } else { $resp->status(404); $resp->end(); } }); $http->start(); ================================================ FILE: examples/http/moc.moc ================================================ this is moc.moc ================================================ FILE: examples/http/no-compression.php ================================================ on('request', function ($req, Swoole\Http\Response $resp) use ($http) { if ($req->server['request_uri'] == '/') { $resp->header('Content-Encoding', ''); $resp->end(str_repeat('A', 1024)); } elseif ($req->server['request_uri'] == '/gzip') { $resp->end(str_repeat('A', 1024)); } else { $resp->status(404); $resp->end(); } }); $http->start(); ================================================ FILE: examples/http/post.data ================================================ dd=wwwwww&ddd=eee&3412=3234&hello=world%2C+i+am+rango%2C+thank+you.+no+body ================================================ FILE: examples/http/raw.php ================================================ on('request', function ($req, Swoole\Http\Response $resp) { $resp->redirect("http://www.baidu.com/", 301); }); $http->start(); ================================================ FILE: examples/http/server.php ================================================ setGlobal(HTTP_GLOBAL_ALL, HTTP_GLOBAL_GET|HTTP_GLOBAL_POST|HTTP_GLOBAL_COOKIE); $http->set([ // 'daemonize' => 1, // 'open_cpu_affinity' => 1, // 'task_worker_num' => 1, //'open_cpu_affinity' => 1, //'task_worker_num' => 100, //'enable_port_reuse' => true, // 'http_compression' => false, 'worker_num' => 1, 'upload_max_filesize' => 1 * 1024 * 1024 * 1024, 'package_max_length' => 1 * 1024 * 1024, //'log_file' => __DIR__.'/swoole.log', // 'reactor_num' => 24, //'dispatch_mode' => 3, //'discard_timeout_request' => true, // 'open_tcp_nodelay' => true, // 'open_mqtt_protocol' => true, //'task_worker_num' => 1, //'user' => 'www-data', //'group' => 'www-data', //'daemonize' => true, // 'ssl_cert_file' => $key_dir.'/ssl.crt', // 'ssl_key_file' => $key_dir.'/ssl.key', // 'enable_static_handler' => true, // 'document_root' => '/home/htf/workspace/php/www.swoole.com/web/' ]); $http->listen('127.0.0.1', 9502, SWOOLE_SOCK_TCP); function chunk(Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->write("

hello world1

"); //sleep(1); $response->write("

hello world2

"); //sleep(1); $response->end(); } function no_chunk(Swoole\Http\Request $request, Swoole\Http\Response $response) { /** * Cookie Test */ //$response->cookie('test1', '1234', time() + 86400, '/'); // $response->cookie('test2', '5678', time() + 86400); // var_dump($response->cookie); // var_dump($request->cookie); // try // { // if (rand(1, 99) % 2 == 1) // { // throw new Exception("just for fun."); // } // $response->end("

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

"); // } // catch(Exception $e) // { // $response->end("

Exceptiom

".$e->getMessage()."
"); // } //var_dump($request->server['request_uri'], substr($request->server['request_uri'], -4, 4)); if (substr($request->server['request_uri'], -8, 8) == 'test.jpg') { $response->header('Content-Type', 'image/jpeg'); $response->sendfile(dirname(__DIR__) . '/test.jpg'); return; } elseif ($request->server['request_uri'] == '/test.txt') { $last_modified_time = filemtime(__DIR__ . '/test.txt'); $etag = md5_file(__DIR__ . '/test.txt'); // always send headers $response->header("Last-Modified", gmdate("D, d M Y H:i:s", $last_modified_time) . " GMT"); $response->header("Etag", $etag); if (strtotime($request->header['if-modified-since']) == $last_modified_time or trim($request->header['if-none-match']) == $etag) { $response->status(304); $response->end(); } else { $response->sendfile(__DIR__ . '/test.txt'); } return; } else if ($request->server['request_uri'] == '/favicon.ico') { $response->status(404); $response->end(); return; } else if ($request->server['request_uri'] == '/big_response') { var_dump($response->end(str_repeat('A', 16 * 1024 * 1024))); return; } else if ($request->server['request_uri'] == '/code') { $response->sendfile(__FILE__); return; } elseif ($request->server['request_uri'] == '/save') { file_put_contents(__DIR__ . '/httpdata', $request->getData()); $response->end('hello'); return; } else { //var_dump($request->post); //var_export($request->cookie); // var_dump($request->rawContent()); // if ($request->server['request_method'] == 'POST') // { // var_dump($request->post); // } // echo "GET:" . var_export($_GET, true)."\n"; // echo "POST:" . var_export($_POST, true)."\n"; // echo "get:" . var_export($request->get, true)."\n"; // echo "post:" . var_export($request->post, true)."\n"; //var_dump($request->server); $output = ''; $output .= "

HEADER:

" . dump($request->header); $output .= "

SERVER:

" . dump($request->server); if (!empty($request->files)) { $files = $request->files; foreach ($files as &$f) { $f['md5'] = md5_file($f['tmp_name']); } $output .= "

FILE:

" . dump($files); } if (!empty($request->cookie)) { $output .= "

COOKIES:

" . dump($request->cookie); } if (!empty($request->get)) { $output .= "

GET:

" . dump($request->get); } if (!empty($request->post)) { $output .= "

POST:

" . dump($request->post); } var_dump($request->post); //$response->header('X-Server', 'Swoole'); //unset($request, $response); // Swoole\Timer::after(2000, function() use ( $response) { $response->end("

Hello Swoole.

" . $output); return; // }); } //var_dump($request); // var_dump($_GET); //var_dump($_POST); //var_dump($_COOKIE); //$response->status(301); //$response->header("Location", "http://www.baidu.com/"); //$response->cookie("hello", "world", time() + 3600); // $response->header("Content-Type", "text/html; charset=utf-8"); //var_dump($request->post); // var_dump($request->get); // echo strlen(gzdeflate("

Hello Swoole.

")); // $response->end("

Hello Swoole.

"); //$response->end("

Hello Swoole. #".str_repeat('A', rand(100, 999))."

"); //global $http; //$http->task("hello world"); $file = realpath(__DIR__ . '/../' . $request->server['request_uri']); if (is_file($file)) { echo "http get file=$file\n"; if (substr($file, -4) == '.php') { $response->gzip(); } else { $response->header('Content-Type', 'image/jpeg'); } $content = file_get_contents($file); echo "response size = " . strlen($content) . "\n"; // $response->write($content); // $response->end(); $response->end($content); } else { $response->end("

Hello Swoole.

"); } } $http->on('request', function ($req, $resp) { $uri = $req->server['request_uri']; if ($uri == '/favicon.ico') { $resp->status(404); $resp->end(); } elseif ($uri == '/chunk') { chunk($req, $resp); } else { no_chunk($req, $resp); } }); $http->on('finish', function () { echo "task finish"; }); $http->on('task', function () { echo "async task\n"; }); //$http->on('close', function(){ // echo "on close\n"; //}); $http->on('workerStart', function ($serv, $id) { //var_dump($serv); }); $http->start(); ================================================ FILE: examples/http/static_handler.php ================================================ set([ 'enable_static_handler' => true, 'http_autoindex' => true, 'document_root' => realpath(__DIR__.'/../www/'), ]); $http->on('request', function ($req, $resp) { $resp->end("hello world\n"); }); $http->start(); ================================================ FILE: examples/http/test.txt ================================================ hello world! ================================================ FILE: examples/http/url_rewrite.php ================================================ set([ 'enable_static_handler' => true, 'document_root' => realpath(__DIR__.'/../www/'), 'http_autoindex' => true, // URL重写规则配置 'url_rewrite_rules' => [ // 普通路径重写: 将 /api 开头的请求重写到 /static/api 目录 '/api' => '/static/api', // 正则表达式重写: 将 /user/123 格式的请求重写到 /static/user.html?id=123 '~^/user/(\\d+)$~' => '/static/user.html?id=$1', // 正则表达式重写: 将 /article/title-slug 格式的请求重写到 /static/article.html?slug=title-slug '~^/article/([\\w\\-]+)$~' => '/static/article.html?slug=$1' ] ]); $http->on('request', function ($request, $response) { $response->header('Content-Type', 'text/plain; charset=utf-8'); $response->end("动态处理: " . $request->server['request_uri']); }); $http->on('start', function ($server) { echo "HTTP服务器已启动,监听 0.0.0.0:9501\n"; echo "URL重写功能已启用\n"; echo "测试示例:\n"; echo "1. 普通重写: http://localhost:9501/api/test.txt -> /static/api/test.txt\n"; echo "2. 正则重写: http://localhost:9501/user/123 -> /static/user.html?id=123\n"; echo "3. 正则重写: http://localhost:9501/article/test-title -> /static/article.html?slug=test-title\n"; }); $http->start(); ================================================ FILE: examples/http2/server.php ================================================ SWOOLE_TRACE_HTTP2, 'log_level' => 0, ]); $key_dir = __DIR__ . '/../ssl/'; $http = new Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'open_http2_protocol' => 1, 'enable_static_handler' => TRUE, 'document_root' => dirname(__DIR__), 'package_max_length' => 1024 * 1024, 'ssl_cert_file' => $key_dir . '/ssl.crt', 'ssl_key_file' => $key_dir . '/ssl.key', ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('Test-Value', [ "a\r\n", 'd5678', "e \n ", null, 5678, 3.1415926, ]); $response->end("

Hello Swoole.

"); }); $http->start(); ================================================ FILE: examples/http2/streaming.php ================================================ set([ 'open_http2_protocol' => 1, ]); /** * nghttp -v http://localhost:9501 */ $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $n = 5; while ($n--) { $response->write("hello world, #$n
\n"); Co\System::sleep(1); } $response->end("hello world"); }); $http->start(); ================================================ FILE: examples/http2/test.html ================================================

Test

================================================ FILE: examples/ipv6/tcp_client.php ================================================ connect('::1', 9501, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } var_dump($client->getsockname()); for($i=0; $i < 1; $i ++) { $client->send("hello world\n"); echo $client->recv(); usleep(2000); } $client->close(); ================================================ FILE: examples/ipv6/tcp_server.php ================================================ set(array( 'worker_num' => 1, )); $serv->on('connect', function ($serv, $fd, $reactor_id){ echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; var_dump($serv->connection_info($fd)); $serv->send($fd, json_encode(array("hello" => '1213', "bat" => "ab"))); //$serv->close($fd); }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/ipv6/udp_client.php ================================================ connect('::1', 9502); $client->send("admin"); echo $client->recv()."\n"; var_dump($client->getsockname()); var_dump($client->getpeername()); $client->sendto('::1', 9502, "admin2"); echo $client->recv()."\n"; sleep(1); ================================================ FILE: examples/ipv6/udp_server.php ================================================ set(array( 'worker_num' => 1, )); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; var_dump($serv->connection_info($fd, $reactor_id)); $serv->send($fd, json_encode(array("hello" => '1213', "bat" => "ab"))); //$serv->close($fd); }); $serv->start(); ================================================ FILE: examples/length/client.php ================================================ set(array( 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => 0, //第N个字节是包长度的值 'package_body_offset' => 4, //第几个字节开始计算长度 'package_max_length' => 2000000, //协议最大长度 )); if (!$client->connect('127.0.0.1', 9501)) { exit("connect failed\n"); } for ($i = 0; $i < 10; $i++) { $data = array( 'str1' => str_repeat('A', rand(10000, 20000)), 'str2' => str_repeat('B', rand(5000, 10000)), 'str3' => str_repeat('C', rand(1000, 9000)), ); $data['int1'] = rand(100000, 999999); $sendStr = serialize($data); $sendData = pack('N', strlen($sendStr)) . $sendStr; $client->send($sendData); echo "send length=" . strlen($sendData) . ", SerId={$data['int1']}\n"; for ($j = 0; $j < 3; $j++) { $resp = $client->recv(); $data2 = unserialize(substr($resp, 4)); echo "#$j\trecv length=" . strlen($resp) . ", SerId={$data2['int1']}\n"; } } sleep(2); ================================================ FILE: examples/length/config.php ================================================ set(array( 'open_length_check' => true, 'dispatch_mode' => 1, 'package_length_func' => function ($data) { if (strlen($data) < 8) { return 0; } $length = intval(trim(substr($data, 0, 8))); if ($length <= 0) { return -1; } return $length + 8; }, 'package_max_length' => 2000000, //协议最大长度 )); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { var_dump($data); echo "#{$serv->worker_id}>> received length=" . strlen($data) . "\n"; }); $serv->start(); ================================================ FILE: examples/length/server.php ================================================ set(array( 'open_length_check' => true, 'dispatch_mode' => 1, // 'worker_num' => 4, 'package_length_type' => 'N', 'package_length_offset' => 0, //第N个字节是包长度的值 'package_body_offset' => 4, //第几个字节开始计算长度 'package_max_length' => 2000000, //协议最大长度 )); function send(Swoole\Server $serv, $fd, $data) { $serv->send($fd, $data); echo "#send =" . strlen($data) . " bytes\n"; } $serv->on('connect', function ($serv, $fd){ echo "Client:Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $req = unserialize(substr($data, 4)); echo "#{$serv->worker_id}>> received length=" . strlen($data) . ", SerId: {$req['int1']}\n"; send($serv, $fd, $data); send($serv, $fd, $data); send($serv, $fd, $data); }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->start(); ================================================ FILE: examples/lock/lock.php ================================================ lock(); if (pcntl_fork() > 0) { sleep(1); $lock->unlock(); } else { echo "[Child] Wait Lock\n"; $lock->lock(); echo "[Child] Get Lock\n"; $lock->unlock(); exit("[Child] exit\n"); } echo "[Master]release lock\n"; unset($lock); sleep(1); echo "[Master]exit\n"; ================================================ FILE: examples/misc/get_local_ip.php ================================================ ================================================ FILE: examples/multicast/client.php ================================================ connect('224.10.20.30', 9905); $client->send("hello world"); echo $client->recv() . "\n"; sleep(1); ================================================ FILE: examples/multicast/server.php ================================================ set(['worker_num' => 1]); $socket = $server->getSocket(); $ret = socket_set_option( $socket, IPPROTO_IP, MCAST_JOIN_GROUP, array('group' => '224.10.20.30', 'interface' => 0) ); if ($ret === false) { throw new RuntimeException('Unable to join multicast group'); } $server->on('Packet', function (Swoole\Server $serv, $data, $addr) { $serv->sendto($addr['address'], $addr['port'], "Swoole: $data"); var_dump($addr, strlen($data)); }); $server->start(); ================================================ FILE: examples/php/buf_size.php ================================================ alloc_point as $k => $v) { echo "$k => $v\n"; } } else { //var_dump($tag, $data); } } function run() { unlink(__DIR__.'/alloc.log'); unlink(__DIR__.'/free.log'); $socket = stream_socket_server("udp://127.0.0.1:9999", $errno, $errstr, STREAM_SERVER_BIND); if (!$socket) { die("$errstr ($errno)"); } while(1) { $pkt = stream_socket_recvfrom($socket, 65535, 0, $peer); $this->package_decode($pkt); //echo "$peer: $pkt\n"; //stream_socket_sendto($socket, date("D M j H:i:s Y\r\n"), 0, $peer); } } } $svr = new DebugServer; $svr->run(); ================================================ FILE: examples/php/error.php ================================================ test(); ================================================ FILE: examples/php/exception.php ================================================ array("pipe", "r"), // 标准输入,子进程从此管道中读取数据 1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据 2 => array("file", __DIR__ . "/error-output.txt", "a") // 标准错误,写入到一个文件 ); $cwd = '/tmp'; $env = array('some_option' => 'aeiou'); $process = proc_open('php', $descriptorspec, $pipes, $cwd, $env); // var_dump($process, $pipes);exit; // $pipes 现在看起来是这样的: // 0 => 可以向子进程标准输入写入的句柄 // 1 => 可以从子进程标准输出读取的句柄 // 错误输出将被追加到文件 /tmp/error-output.txt fwrite($pipes[0], ''); fclose($pipes[0]); // echo stream_get_contents($pipes[1]); // fclose($pipes[1]); // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。 $return_value = proc_close($process); echo "command returned $return_value\n"; }); ================================================ FILE: examples/php/socket_client.php ================================================ 1, 'usec' => 500000); socket_set_option($socket,SOL_SOCKET,SO_RCVTIMEO,$timeout); $n = socket_recv($socket, $buf, 2048, MSG_WAITALL); var_dump($n, $buf); ================================================ FILE: examples/php/socket_server.php ================================================ \n"; } else { fwrite($fp, "GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n"); sleep(1000); fclose($fp); } ================================================ FILE: examples/php/stream_server.php ================================================ \n"; } else { while ($conn = stream_socket_accept($socket)) { $i = 0; while(true) { $r = fwrite($conn, str_repeat("A", 8192)); usleep(1000); if (empty($r)) { echo "count $i \n"; var_dump($r); stream_set_blocking($conn, 0); } else{ $i++; } if ($r === false) break; } fclose($conn); } fclose($socket); } ================================================ FILE: examples/php/tick.php ================================================ connect ("host=127.0.0.1 port=5432 dbname=test user=wuzhenyu password="); $result = $pg->query($conn, 'SELECT * FROM test;'); $arr = $pg->fetchAll($result);// the same with affectedRows(),fetchObject(),fetchAssoc(),fetchArray(),fetchRow(),numRows() var_dump($arr); }); go(function () { $pg = new Swoole\Coroutine\PostgreSql(); $conn = $pg -> connect ("host=127.0.0.1 port=5432 dbname=test user=wuzhenyu password="); $metaData = $pg->metaData($conn, 'test'); var_dump($metaData); }); ?> ================================================ FILE: examples/process/alarm.php ================================================ 20) { Swoole\Process::alarm(-1); } }); Swoole\Process::alarm(100 * 1000); ================================================ FILE: examples/process/async_master.php ================================================ read(); echo "RECV: " . $data; } //循环创建进程 for($i = 0; $i < $worker_num; $i++) { $process = new Swoole\Process(function(Swoole\Process $process) { $i = 1; while($i++) { $process->write("Worker#{$process->id}: hello master\n"); if ($i > 5 and $process->id == 1) $process->exit(); sleep(1); } }); $process->id = $i; $pid = $process->start(); $workers[$process->pipe] = $process; } Swoole\Process::signal(SIGCHLD, function(){ //表示子进程已关闭,回收它 $status = Swoole\Process::wait(); echo "Worker#{$status['pid']} exit\n"; }); //将子进程的管道加入EventLoop foreach($workers as $process) { Swoole\Event::add($process->pipe, 'onReceive'); } ================================================ FILE: examples/process/client.php ================================================ connect('127.0.0.1', 8089, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } function _send(Swoole\Client $client, $data) { return $client->send(pack('N', strlen($data)) . $data); } var_dump($client->getsockname()); _send($client, "hello world"); _send($client, "hello world [2]"); $client->close(); ================================================ FILE: examples/process/client3.php ================================================ 'hello', 'uid' => 1991]); fwrite($fp, pack('N', strlen($msg)) . $msg); sleep(1); var_dump(fread($fp, 8192)); fclose($fp); ================================================ FILE: examples/process/close.php ================================================ pid . "\n"; sleep(2); $worker->close(Swoole\Process::PIPE_READ); $worker->write("hello master\n"); $worker->exit(0); }, false); $pid = $process->start(); $r = array($process); $w = array(); $e = array(); $ret = swoole_select($r, $w, $e, 1.0); var_dump($ret); var_dump($process->read()); ================================================ FILE: examples/process/daemon.php ================================================ start(); function callback_function(Swoole\Process $worker) { $worker->exec('/usr/local/bin/php', array(__DIR__.'/stdin_stdout.php')); } echo "From Worker: ".$process->read(); $process->write("hello worker\n"); echo "From Worker: ".$process->read(); $ret = Swoole\Process::wait(); var_dump($ret); ================================================ FILE: examples/process/func_timeout.php ================================================ false, ]); class FunctionTimeoutException extends RuntimeException { } function test() { sleep(1); } $serv = new Swoole\Http\Server("127.0.0.1", 9502); $serv->set(['worker_num' => 1]); $serv->on('WorkerStart', function($serv, $workerId) { pcntl_signal(SIGALRM, function () { Swoole\Process::alarm(-1); throw new FunctionTimeoutException; }); }); $serv->on('Request', function($request, $response) { try { Swoole\Process::alarm(100 * 1000); test(); Swoole\Process::alarm(-1); $response->end("

Finish

"); } catch(FunctionTimeoutException $e) { $response->end("

Timeout

"); } }); $serv->start(); ================================================ FILE: examples/process/msg_pop.php ================================================ on('Message', function (Pool $pool, string $data) { var_dump($data); }); $pool->start(); ================================================ FILE: examples/process/msg_push.php ================================================ push($msg); var_dump($ret); ================================================ FILE: examples/process/msgqueue.php ================================================ useQueue(); $pid = $process->start(); $workers[$pid] = $process; //echo "Master: new worker, PID=".$pid."\n"; } function callback_function(Swoole\Process $worker) { //echo "Worker: start. PID=".$worker->pid."\n"; //recv data from master while(true) { $recv = $worker->pop(); echo "From Master: $recv\n"; } sleep(2); $worker->exit(0); } while(true) { /** * @var $process Swoole\Process */ $pid = array_rand($workers); $process = $workers[$pid]; $process->push("hello worker[$pid]\n"); sleep(1); } for($i = 0; $i < $worker_num; $i++) { $ret = Swoole\Process::wait(); $pid = $ret['pid']; unset($workers[$pid]); echo "Worker Exit, PID=".$pid.PHP_EOL; } ================================================ FILE: examples/process/msgqueue2.php ================================================ pid."\n"; //recv data from master while(true) { $recv = $worker->pop(); echo "From Master: $recv\n"; } sleep(2); $worker->exit(0); } $process = new Swoole\Process('callback_function', false, false); $process->useQueue(ftok(__FILE__, 1), 2 | Swoole\Process::IPC_NOWAIT); $send_bytes = 0; foreach(range(1, 10) as $i) { $data = str_repeat('A', 65535); // $data = "hello worker[$i]\n"; $send_bytes += strlen($data); $process->push($data); } $recv_bytes = 0; $r_data = true; while($r_data) { $r_data = $process->pop(); $recv_bytes += $r_data; } echo "send={$send_bytes}, recv=$recv_bytes\n"; var_dump($process->statQueue()); ================================================ FILE: examples/process/msgqueue_client.php ================================================ stats()); } elseif ($i % 300 == 299) { sleep(1); } $mq->push("hello $i"); } ================================================ FILE: examples/process/msgqueue_pool.php ================================================ on("Message", function ($pool, $message) { echo "Message: {$message}\n"; }); $pool->on("WorkerStart", function ($pool, $workerId) { echo "Worker#{$workerId} is started\n"; }); $pool->on("WorkerStop", function ($pool, $workerId) { echo "Worker#{$workerId} is stopped\n"; }); $pool->start(); ================================================ FILE: examples/process/pool_socket.php ================================================ on("Message", function ($pool, $message) { echo "Message: {$message}\n"; $pool->write("hello "); $pool->write("world "); $pool->write("\n"); }); $pool->listen('127.0.0.1', 8089); $pool->start(); ================================================ FILE: examples/process/python.php ================================================ start(); function pyhon_process(Swoole\Process $worker) { $worker->exec('/usr/bin/python', array("echo.py")); } $process->write("hello world\n"); echo $process->read(); $ret = Swoole\Process::wait(); var_dump($ret); ================================================ FILE: examples/process/select.php ================================================ pid . "\n"; sleep(2); $worker->write("hello master\n"); $worker->exit(0); }, false); $pid = $process->start(); $r = array($process); $ret = swoole_select($r, null, null, 1.0); var_dump($ret); var_dump($process->read()); ================================================ FILE: examples/process/set_cpu_affinity.php ================================================ id = $i; $pid = $process->start(); $workers[$pid] = $process; //echo "Master: new worker, PID=".$pid."\n"; } master_async($workers); //master_sync($workers); //异步主进程 function master_async($workers) { Swoole\Process::signal(SIGCHLD, function ($signo) use (&$workers) { while(1) { $ret = Swoole\Process::wait(false); if ($ret) { $pid = $ret['pid']; $child_process = $workers[$pid]; //unset($workers[$pid]); echo "Worker Exit, kill_signal={$ret['signal']} PID=" . $pid . PHP_EOL; $new_pid = $child_process->start(); $workers[$new_pid] = $child_process; unset($workers[$pid]); } else { break; } } }); /** * @var $process Swoole\Process */ foreach($workers as $pid => $process) { Swoole\Event::add($process->pipe, function($pipe) use ($process) { $recv = $process->read(); if ($recv) echo "From Worker: " . $recv; $process->write("HELLO worker {$process->pid}\n"); }); $process->write("hello worker[$pid]\n"); } } //同步主进程 function master_sync($workers) { foreach($workers as $pid => $process) { $process->write("hello worker[$pid]\n"); echo "From Worker: ".$process->read(); } } function child_sync(Swoole\Process $worker) { //echo "Worker: start. PID=".$worker->pid."\n"; //recv data from master $recv = $worker->read(); echo "From Master: $recv\n"; //send data to master $worker->write("hello master\n"); sleep(2); $worker->exit(0); } function child_async(Swoole\Process $worker) { //echo "Worker: start. PID=".$worker->pid."\n"; //recv data from master $GLOBALS['worker'] = $worker; global $argv; $worker->name("{$argv[0]}: worker #".$worker->id); Swoole\Process::signal(SIGTERM, function($signal_num) use ($worker) { echo "signal call = $signal_num, #{$worker->pid}\n"; }); // Swoole\Timer::tick(2000, function () use ($worker) // { // if (rand(1, 3) % 2) { // $worker->write("hello master {$worker->pid}\n"); // } // }); Swoole\Event::add($worker->pipe, function($pipe) use($worker) { $recv = $worker->read(); echo "From Master: $recv\n"; //$worker->write("hello master\n"); }); } ================================================ FILE: examples/process_pool/detach.php ================================================ on('WorkerStart', function (Process\Pool $pool, $workerId) { echo("[Worker #{$workerId}] WorkerStart\n"); if ($workerId == 1) { } }); $pool->on('WorkerStop', function (\Swoole\Process\Pool $pool, $workerId) { echo("[Worker #{$workerId}] WorkerStop\n"); }); $pool->on('Message', function ($pool, $msg) { var_dump($msg); $pool->detach(); while(1) { sleep(1); echo "pid=".posix_getpid()."\n"; }; }); $pool->listen('127.0.0.1', 8089); $pool->start(); ================================================ FILE: examples/process_pool/send.php ================================================ 'hello', 'uid' => 1991]); fwrite($fp, pack('N', strlen($msg)) . $msg); sleep(1); ================================================ FILE: examples/runtime/coroutine.php ================================================ connect("127.0.0.1", 6379); var_dump($retval, $redis->getLastError()); var_dump($redis->get("key")); var_dump($redis->set("key", "value2")); var_dump($redis->get("key")); $redis->close(); $db = new mysqli; $db->connect('127.0.0.1', 'root', 'root', 'test'); $result = $db->query("show databases"); var_dump($result->fetch_all()); $db = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8", "root" ,"root"); $query = $db->prepare("select * from userinfo where id=?"); $rs = $query->execute(array(1)); var_dump($rs); echo count($query->fetchAll()); }); ================================================ FILE: examples/runtime/curl.php ================================================ listDatabases(); echo "Available databases:\n"; foreach ($list as $database) { echo "- Name: {$database->getName()}\n"; echo " Size: {$database->getSizeOnDisk()} bytes\n"; echo " Empty: " . ($database->isEmpty() ? 'Yes' : 'No') . "\n"; echo "---\n"; } }); ================================================ FILE: examples/runtime/oci.php ================================================ exec('create table test (id int)'); $dbh->exec('insert into test values(1)'); $dbh->exec('insert into test values(2)'); $res = $dbh->query("select * from test"); var_dump($res->fetchAll()); $dbh = null; } catch (PDOException $exception) { echo $exception->getMessage(); exit; } } Co::set(['hook_flags' => SWOOLE_HOOK_PDO_ORACLE]); Co\run(function () { test(); }); ================================================ FILE: examples/runtime/odbc.php ================================================ query("select sleep(1) s"); var_dump($res->fetchAll()); $dbh = null; } catch (PDOException $exception) { echo $exception->getMessage(); exit; } } Co::set(['trace_flags' => SWOOLE_TRACE_CO_ODBC, 'log_level' => SWOOLE_LOG_DEBUG]); Co\run(function () { test(); }); ================================================ FILE: examples/runtime/read.php ================================================ \n"; } else { fwrite($fp1, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, 10); var_dump($r_array, $n); $html = ''; while (!feof($fp1)) { $html .= fgets($fp1, 1024); } var_dump(strlen($html)); fclose($fp1); } }); Swoole\Event::wait(); ================================================ FILE: examples/runtime/sleep.php ================================================ SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); for ($i = 0; $i < 10; $i++) { go(function() use($i, $db) { $db->query('select randomblob(99999999)'); var_dump($i); }); } }); ================================================ FILE: examples/runtime/ssl.php ================================================ \n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); while (!feof($fp)) { echo fgets($fp, 1024); } fclose($fp); } }); ================================================ FILE: examples/runtime/stream.php ================================================ \n"; } else { fwrite($fp, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nAccept: */*\r\n\r\n"); while (!feof($fp)) { echo fgets($fp, 1024); } fclose($fp); } }); ================================================ FILE: examples/runtime/sync.php ================================================ set(array( 'worker_num' => 100, 'task_worker_num' => 20, //database connection pool 'db_uri' => 'mysql:host=127.0.0.1;dbname=test', 'db_user' => 'root', 'db_passwd' => 'root', )); function my_onRequest_sync($req, $resp) { global $serv; $result = $serv->taskwait("show tables"); if ($result !== false) { $resp->end(var_export($result['data'], true)); return; } else { $resp->status(500); $resp->end("Server Error, Timeout\n"); } } function my_onTask($serv, $task_id, $reactor_id, $sql) { static $link = null; if ($link == null) { $link = new PDO($serv->setting['db_uri'], $serv->setting['db_user'], $serv->setting['db_passwd']);; if (!$link) { $link = null; return array("data" => '', 'error' => "connect database failed."); } } $result = $link->query($sql); if (!$result) { return array("data" => '', 'error' => "query error"); } $data = $result->fetchAll(); return array("data" => $data); } function my_onFinish($serv, $data) { echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL; } $serv->on('Request', 'my_onRequest_sync'); $serv->on('Task', 'my_onTask'); $serv->on('Finish', 'my_onFinish'); $serv->start(); ================================================ FILE: examples/server/dispatch_func.php ================================================ set(array( 'dispatch_func' => function ($serv, $fd, $type, $data) { var_dump($fd, $type, $data); return intval($data[0]); }, )); $serv->on('receive', function (Swoole\Server $serv, $fd, $threadId, $data) { var_dump($data); echo "#{$serv->worker_id}>> received length=" . strlen($data) . "\n"; }); $serv->start(); ================================================ FILE: examples/server/dispatch_stream.php ================================================ set(array( 'dispatch_mode' => 7, 'worker_num' => 2, )); $serv->on('receive', function (Swoole\Server $serv, $fd, $threadId, $data) { var_dump($data); echo "#{$serv->worker_id}>> received length=" . strlen($data) . "\n"; $serv->send($fd, "Swoole $data\n"); }); $serv->start(); ================================================ FILE: examples/server/echo.php ================================================ mode === SWOOLE_THREAD ? \Swoole\Thread::getId() : posix_getpid(); } $serv->set([ 'worker_num' => 2, 'task_worker_num' => 3, ]); $serv->on('workerStart', function ($serv, $worker_id) { echo "[#" . getpid() . "]\tWorker#{$worker_id} is started.\n"; }); $serv->on('workerStop', function ($serv, $worker_id) { echo "[#" . getpid() . "]\tWorker#{$worker_id} is stopped.\n"; }); $serv->on('connect', function ($serv, $fd, $reactor_id) { echo "[#" . getpid() . "]\tClient@[$fd:$reactor_id]: Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#" . $serv->worker_id . "]\tClient[$fd] receive data: $data\n"; if ($serv->send($fd, "hello {$data}\n") == false) { echo "error\n"; } }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#" . getpid() . "]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->on('task', function ($serv, $src_worker_id, $task) { var_dump($task); }); $serv->start(); ================================================ FILE: examples/server/eof_client.php ================================================ set( array( 'open_eof_split' => true, 'package_eof' => "\r\n", 'package_max_length' => 8 * 1024 * 1024, ) ); if (!$client->connect('127.0.0.1', 9504)) { exit("connect failed\n"); } $func = "send_test" . intval(empty($argv[1]) ? 3 : $argv[1]); for ($l = 0; $l < 1; $l++) { for ($i = 0; $i < 10; $i++) { $len = rand(100000, 200000); $func($client, $len); } } function send_test3($client, $len) { $data = str_repeat('A', $len) . "\r\n"; $chunks = str_split($data, 4000); foreach ($chunks as $ch) { $client->send($ch); } echo "send : " . strlen($data) . "\n"; // $data = $client->recv(); // echo "recv : " . strlen($data) . "\n"; } function send_test2($client, $len) { $data = pack('N', $len + 4); $data .= str_repeat('A', $len) . rand(100000, 999999); $client->send($data); $data = $client->recv(); } function send_test1($client, $len) { $client->send(pack('N', $len + 4)); usleep(10); $client->send(str_repeat('A', $len) . rand(1000, 9999)); $data = $client->recv(); } ================================================ FILE: examples/server/eof_server.php ================================================ run('0.0.0.0', 9504); class SocketServer { protected $serv; //swoole server const MAX_PACKAGE_LEN = 8000000; //max data accept function run($host, $port) { $this->serv = new Swoole\Server($host, $port, SWOOLE_BASE); $this->serv->set(array( 'enable_coroutine' => false, 'worker_num' => 1, //how much worker will start 'open_eof_split' => true, 'package_eof' => "\r\n", 'package_max_length' => 8 * 1024 * 1024, )); $this->serv->on('receive', array($this, 'onReceive')); $this->serv->start(); } function onReceive($serv, $fd, $tid, $data) { echo "recv " . strlen($data) . " bytes\n"; // $packet = substr($data, 4); // $result = array( // "code" => "0", // "msg" => "ok", // "data" => $packet, // ); // $resp = json_encode($result); // $send_data = pack('N', strlen($resp)) . $resp; // echo "send " . strlen($send_data) . " bytes\n"; // $serv->send($fd, $send_data); } } ================================================ FILE: examples/server/getReceivedTime.php ================================================ on('connect', function ($serv, $fd, $reactor_id){ // echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; //}); $serv->set(array( 'worker_num' => 1, )); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { usleep(rand(100000, 2000000)); var_dump(round($serv->getReceivedTime(), 10)); }); //$serv->on('close', function ($serv, $fd, $reactor_id) { // echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; //}); $serv->start(); ================================================ FILE: examples/server/host_update.php ================================================ set(array( 'worker_num' => 2, //'open_eof_check' => true, //'package_eof' => "\r\n", 'task_worker_num' => 2, //'dispatch_mode' => 2, //'daemonize' => 1, //'heartbeat_idle_time' => 5, //'heartbeat_check_interval' => 5, )); function my_onStart($serv) { echo "MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}\n"; echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n"; } function my_onShutdown($serv) { echo "Server: onShutdown\n"; } function my_onClose($serv, $fd, $reactor_id) { //echo "Client: fd=$fd is closed.\n"; } function my_onConnect($serv, $fd, $reactor_id) { //throw new Exception("hello world"); // echo "Client:Connect.\n"; } $class = null; function my_onWorkerStart($serv, $worker_id) { global $argv; global $class; opcache_reset(); include "hot_update_class.php"; $class = new HotUpdate(); if ($worker_id >= $serv->setting['worker_num']) { swoole_set_process_name("php {$argv[0]} task worker"); } else { swoole_set_process_name("php {$argv[0]} event worker"); } //echo "WorkerStart|MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}|WorkerId=$worker_id\n"; } function my_onWorkerStop($serv, $worker_id) { echo "WorkerStop[$worker_id]|pid=" . posix_getpid() . ".\n"; } function my_onReceive(Swoole\Server $serv, $fd, $reactor_id, $data) { $cmd = trim($data); if ($cmd == "reload") { $serv->reload($serv); } elseif ($cmd == "task") { $task_id = $serv->task("hello world", 0); echo "Dispath AsyncTask: id=$task_id\n"; } elseif ($cmd == "info") { $info = $serv->connection_info($fd); $serv->send($fd, 'Info: ' . var_export($info, true) . PHP_EOL); } elseif ($cmd == "broadcast") { $start_fd = 0; while (true) { $conn_list = $serv->connection_list($start_fd, 10); if ($conn_list === false) { break; } $start_fd = end($conn_list); foreach ($conn_list as $conn) { if ($conn === $fd) { continue; } $serv->send($conn, "hello from $fd\n"); } } } //这里故意调用一个不存在的函数 elseif ($cmd == "error") { hello_no_exists(); } elseif ($cmd == "shutdown") { $serv->shutdown(); } else { global $class; $data .= $class->getData(); $serv->send($fd, 'Swoole: ' . $data, $reactor_id); //$serv->close($fd); } //echo "Client:Data. fd=$fd|reactor_id=$reactor_id|data=$data"; } function my_onTask(Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "AsyncTask[PID=" . posix_getpid() . "]: task_id=$task_id." . PHP_EOL; $serv->finish("OK"); } function my_onFinish(Swoole\Server $serv, $data) { echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL; } $serv->on('Start', 'my_onStart'); $serv->on('Connect', 'my_onConnect'); $serv->on('Receive', 'my_onReceive'); $serv->on('Close', 'my_onClose'); $serv->on('Shutdown', 'my_onShutdown'); $serv->on('WorkerStart', 'my_onWorkerStart'); $serv->on('WorkerStop', 'my_onWorkerStop'); $serv->on('Task', 'my_onTask'); $serv->on('Finish', 'my_onFinish'); $serv->on('WorkerError', function ($serv, $worker_id, $worker_pid, $exit_code) { echo "worker abnormal exit. WorkerId=$worker_id|Pid=$worker_pid|ExitCode=$exit_code\n"; }); $serv->start(); ================================================ FILE: examples/server/hot_update_class.php ================================================ fdlist = []; $serv->workerid = 0; $serv->set(array( //'tcp_defer_accept' => 5, //'ipc_mode' => 2, 'worker_num' => 4, //'task_worker_num' => 2, 'dispatch_mode' => 4, //ip dispatch //'max_request' => 1000, //'daemonize' => true, //'log_file' => '/tmp/swoole.log' )); $serv->on('workerStart', function($serv, $worker_id) { echo "{$worker_id} start".PHP_EOL; $serv->workerid = $worker_id; }); $serv->on('connect', function ($serv, $fd, $reactor_id){ //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; echo "{$fd} connect, worker:".$serv->workerid.PHP_EOL; $conn = print_r($serv->connection_info($fd)); $serv->fdlist[$fd] = 1; print_r($serv->fdlist); }); $serv->on('task', function ($serv, $task_id, $reactor_id, $data){ //var_dump($task_id, $reactor_id, $data); $fd = $data; $serv->send($fd, str_repeat('B', 1024*rand(40, 60)).rand(10000, 99999)."\n"); }); $serv->on('finish', function ($serv, $fd, $reactor_id){ }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { foreach($serv->fdlist as $_fd=>$val) { $serv->send($_fd, "{$fd} say:".$data.PHP_EOL); } }); $serv->on('close', function ($serv, $fd, $reactor_id) { //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; unset($serv->fdlist[$fd]); }); $serv->start(); ================================================ FILE: examples/server/length_client.php ================================================ set(array( 'open_length_check' => true, 'package_max_length' => 8 * 1024 * 1024, 'package_length_type' => 'N', //see php pack() 'package_length_offset' => 0, 'package_body_offset' => 4, )); if (!$client->connect('127.0.0.1', 9504)) { exit("connect failed\n"); } $func = "send_test" . intval(empty($argv[1]) ? 3 : $argv[1]); for ($l = 0; $l < 1; $l++) { $data = ''; for ($i = 0; $i < 10; $i++) { $len = rand(100000, 200000); echo "send : " . ($len + 4) . "\n"; $func($client, $len); } sleep(1); } function send_test3($client, $len) { $data = pack('N', $len + 4); $data .= str_repeat('A', $len) . rand(1000, 9999); $chunks = str_split($data, 4000); foreach ($chunks as $ch) { $client->send($ch); } // $data = $client->recv(); // echo "recv : " . strlen($data) . "\n"; } function send_test2($client, $len) { $data = pack('N', $len + 4); $data .= str_repeat('A', $len) . rand(100000, 999999); $client->send($data); $data = $client->recv(); } function send_test1($client, $len) { $client->send(pack('N', $len + 4)); usleep(10); $client->send(str_repeat('A', $len) . rand(1000, 9999)); $data = $client->recv(); } ================================================ FILE: examples/server/length_server.php ================================================ run('0.0.0.0', 9504); class SocketServer { protected $serv; //swoole server const MAX_PACKAGE_LEN = 8000000; //max data accept function run($host, $port) { $this->serv = new Swoole\Server($host, $port, SWOOLE_BASE); $this->serv->set(array( 'enable_coroutine' => false, // 'dispatch_mode' => 3, //who come first who is 'worker_num' => 1, //how much worker will start 'open_length_check' => true, 'package_max_length' => 8 * 1024 * 1024, 'package_length_type' => 'N', //see php pack() 'package_length_offset' => 0, 'package_body_offset' => 4, )); $this->serv->on('receive', array($this, 'onReceive')); $this->serv->start(); } function onReceive($serv, $fd, $tid, $data) { echo "recv " . strlen($data) . " bytes\n"; // $packet = substr($data, 4); // $result = array( // "code" => "0", // "msg" => "ok", // "data" => $packet, // ); // $resp = json_encode($result); // $send_data = pack('N', strlen($resp)) . $resp; // echo "send " . strlen($send_data) . " bytes\n"; // $serv->send($fd, $send_data); } } ================================================ FILE: examples/server/listen_1k_port.php ================================================ listen("127.0.0.1", $port, SWOOLE_SOCK_TCP); } $serv->on("receive", function($serv, $fd, $reactor_id, $data) { $info = $serv->getClientInfo($fd); var_dump($info); }); $serv->start(); ================================================ FILE: examples/server/local_listener.php ================================================ on('workerstart', function($server, $id) { global $argv; swoole_set_process_name("php {$argv[0]}: worker"); $local_listener = stream_socket_server("127.0.0.1", 9999); Swoole\Event::add($local_listener, function($server){ $local_client = stream_socket_accept($server); Swoole\Event::add($local_client, function($client){ echo fread($client, 8192); fwrite($client, "hello"); }); }); }); $serv->on('connect', function (Swoole\Server $serv, $fd, $reactor_id) { //echo "connect\n";; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $serv->send($fd, "Swoole: ".$data); //$serv->close($fd); }); $serv->on('close', function (Swoole\Server $serv, $fd, $reactor_id) { //var_dump($serv->connection_info($fd)); //echo "onClose\n"; }); $serv->start(); ================================================ FILE: examples/server/manager_timer.php ================================================ set(array( 'worker_num' => 1, )); $serv->on('managerStart', function ($erv) { echo "manager start\n"; // sleep(30); $id = Swoole\Timer::tick(3000, function () { echo "timer 1\n"; }); Swoole\Timer::after(9000, function () use ($id) { echo "timer 2\n"; Swoole\Timer::clear($id); Swoole\Timer::tick(2000, function () { echo "timer 3\n"; }); Swoole\Timer::tick(300, function () { echo "timer 4\n"; }); }); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".$serv->worker_id."]\tClient[$fd] receive data: $data\n"; if ($serv->send($fd, "hello {$data}\n") == false) { echo "error\n"; } }); $serv->start(); ================================================ FILE: examples/server/mixed.php ================================================ 'www-data', 'group' => 'www-data', //'reactor_num' => 16, // 线程数. 一般设置为CPU核数的1-4倍 'worker_num' => 2, // 工作进程数量. 设置为CPU的1-4倍最合理 'max_request' => 1000, // 防止 PHP 内存溢出, 一个工作进程处理 X 次任务后自动重启 (注: 0,不自动重启) 'max_conn' => 10000, // 最大连接数 'task_worker_num' => 1, // 任务工作进程数量 // 'task_ipc_mode' => 2, // 设置 Task 进程与 Worker 进程之间通信的方式。 'task_max_request' => 0, // 防止 PHP 内存溢出 //'task_tmpdir' => '/tmp', //'message_queue_key' => ftok(SYS_ROOT . 'queue.msg', 1), 'dispatch_mode' => 2, //'daemonize' => 1, // 设置守护进程模式 'backlog' => 128, //'log_file' => '/data/logs/swoole.log', 'heartbeat_check_interval' => 2, // 心跳检测间隔时长(秒) 'heartbeat_idle_time' => 3, // 连接最大允许空闲的时间 //'open_eof_check' => 1, //'open_eof_split' => 1, //'package_eof' => "\r\r\n", //'open_cpu_affinity' => 1, 'socket_buffer_size' => 1024 * 1024 * 128, 'output_buffer_size' => 1024 * 1024 * 2, //'enable_delay_receive' => true, //'cpu_affinity_ignore' =>array(0,1)//如果你的网卡2个队列(或者没有多队列那么默认是cpu0来处理中断),并且绑定了core 0和core 1,那么可以通过这个设置避免swoole的线程或者进程绑定到这2个core,防止cpu0,1被耗光而造成的丢包 ]; } if (isset($argv[1]) and $argv[1] == 'daemon') { G::$config['daemonize'] = true; } else { G::$config['daemonize'] = false; } //$mode = SWOOLE_BASE; $mode = SWOOLE_PROCESS; $serv = new Swoole\Server('0.0.0.0', '9501', $mode, SWOOLE_SOCK_TCP); $serv->listen('0.0.0.0', 9502, SWOOLE_SOCK_UDP); $serv->listen('::', 9503, SWOOLE_SOCK_TCP6); $serv->listen('::', 9504, SWOOLE_SOCK_UDP6); $process1 = new Swoole\Process(function ($worker) use ($serv) { global $argv; swoole_set_process_name("php {$argv[0]}: my_process1"); Timer::tick(2000, function ($interval) use ($worker, $serv) { echo "#{$worker->pid} child process timer {$interval}\n"; // 如果worker中没有定时器,则会输出 process timer xxx foreach ($serv->connections as $conn) { $serv->send($conn, "heartbeat\n"); } }); Timer::tick(5000, function () use ($serv) { $serv->sendMessage('hello event worker', 0); $serv->sendMessage('hello task worker', 4); }); }, false); //$serv->addprocess($process1); $process2 = new Swoole\Process(function ($worker) use ($serv) { global $argv; swoole_set_process_name("php {$argv[0]}: my_process2"); Timer::tick(2000, function ($interval) use ($worker, $serv) { echo "#{$worker->pid} child process timer {$interval}\n"; // 如果worker中没有定时器,则会输出 process timer xxx }); }, false); //$serv->addprocess($process2); $serv->set(G::$config); $serv->set(['reactor_num' => 4]); /* * 使用类的静态属性,可以直接访问 */ G::$serv = $serv; function my_onStart(Swoole\Server $serv) { global $argv; swoole_set_process_name("php {$argv[0]}: master"); my_log('Server: start.Swoole version is [' . SWOOLE_VERSION . ']'); my_log("MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}"); } function my_log($msg) { global $serv; if (empty($serv->worker_pid)) { $serv->worker_pid = posix_getpid(); } echo '#' . $serv->worker_pid . "\t[" . date('H:i:s') . "]\t" . $msg . PHP_EOL; } function forkChildInWorker() { global $serv; echo "on worker start\n"; $process = new Swoole\Process(function (Swoole\Process $worker) use ($serv) { // $serv = new Swoole\Server( "0.0.0.0", 9503 ); // $serv->set(array( // 'worker_num' => 1 // )); // $serv->on ( 'receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { // $serv->send ( $fd, "Swoole: " . $data ); // $serv->close ( $fd ); // }); // $serv->start (); // Swoole\Event::add ($worker->pipe, function ($pipe) use ($worker) { // echo $worker->read()."\n"; // }); }); $pid = $process->start(); echo "Fork child process success. pid={$pid}\n"; //保存子进程对象,这里如果不保存,那对象会被销毁,管道也会被关闭 $serv->childprocess = $process; } function processRename(Swoole\Server $serv, $worker_id) { global $argv; if ($serv->taskworker) { swoole_set_process_name("php {$argv[0]}: task"); } else { swoole_set_process_name("php {$argv[0]}: worker"); } // if ($worker_id == 0) // { // var_dump($serv->setting); // } my_log("WorkerStart: MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}|WorkerId={$serv->worker_id}|WorkerPid={$serv->worker_pid}"); } function setTimerInWorker(Swoole\Server $serv, $worker_id) { if ($worker_id == 0) { echo 'Start: ' . microtime(true) . "\n"; //$serv->addtimer(3000); // $serv->addtimer(7000); //var_dump($serv->gettimer()); } // $serv->after(2000, function(){ // echo "Timeout: ".microtime(true)."\n"; // }); // $serv->after(5000, function(){ // echo "Timeout: ".microtime(true)."\n"; // global $serv; // $serv->deltimer(3000); // }); } function my_onShutdown($serv) { echo "Server: onShutdown\n"; } function my_onClose(Swoole\Server $serv, $fd, $reactor_id) { my_log("Client[{$fd}@{$reactor_id}]: fd={$fd} is closed"); var_dump($serv->getClientInfo($fd)); } function my_onConnect(Swoole\Server $serv, $fd, $reactor_id) { //throw new Exception("hello world"); // var_dump($serv->connection_info($fd)); //var_dump($serv, $fd, $reactor_id); // echo "Worker#{$serv->worker_pid} Client[$fd@$reactor_id]: Connect.\n"; $serv->after(2000, function () use ($serv, $fd) { $serv->confirm($fd); }); my_log("Client: Connect --- {$fd}"); } function timer_show($id) { my_log("Timer#{$id}"); } function my_onWorkerExit(Swoole\Server $serv, $worker_id) { global $argv; } function my_onWorkerStart(Swoole\Server $serv, $worker_id) { processRename($serv, $worker_id); if (!$serv->taskworker) { Swoole\Process::signal(SIGUSR2, function ($signo) { echo "SIGNAL: {$signo}\n"; }); Swoole\Event::defer(function () { echo "defer call\n"; }); // $serv->tick(2000, function() use ($serv) { // echo "Worker-{$serv->worker_id} tick-2000\n"; // }); } // Swoole\Timer::after(2000, function() { // echo "after 2 secends.\n"; // }); // $serv->tick(1000, function ($id) use ($serv) { // if (G::$index > 10) { // $serv->after(2500, 'timer_show', 2); // G::$index = 0; // } else { // G::$index++; // } // timer_show($id); // }); //forkChildInWorker(); // setTimerInWorker($serv, $worker_id); } function my_onWorkerStop($serv, $worker_id) { echo "WorkerStop[{$worker_id}]|pid=" . $serv->worker_pid . ".\n"; } function my_onPacket($serv, $data, $clientInfo) { $serv->sendto($clientInfo['address'], $clientInfo['port'], 'Server ' . $data); var_dump($clientInfo); } function my_onReceive(Swoole\Server $serv, $fd, $reactor_id, $data) { my_log("Worker#{$serv->worker_pid} Client[{$fd}@{$reactor_id}]: received: {$data}"); $cmd = trim($data); if ($cmd == 'reload') { $serv->reload(); } elseif ($cmd == 'task') { $task_id = $serv->task('task ' . $fd); echo "Dispath AsyncTask: id={$task_id}\n"; } elseif ($cmd == 'taskclose') { $serv->task('close ' . $fd); echo "close the connection in taskworker\n"; } elseif ($cmd == 'tasksend') { $serv->task('send ' . $fd); } elseif ($cmd == 'bigtask') { $serv->task(str_repeat('A', 8192 * 5)); } elseif ($cmd == 'taskwait') { $result = $serv->taskwait('taskwait'); if ($result) { $serv->send($fd, 'taskwaitok'); } echo 'SyncTask: result=' . var_export($result, true) . "\n"; } elseif ($cmd == 'taskWaitMulti') { $result = $serv->taskWaitMulti([ str_repeat('A', 8192 * 5), str_repeat('B', 8192 * 6), str_repeat('C', 8192 * 8), ]); if ($result) { $resp = "taskWaitMulti ok\n"; foreach ($result as $k => $v) { $resp .= "result[{$k}] length=" . strlen($v) . "\n"; } $serv->send($fd, $resp); } else { $serv->send($fd, "taskWaitMulti error\n"); } } elseif ($cmd == 'hellotask') { $serv->task('hellotask'); } elseif ($cmd == 'taskcallback') { $serv->task('taskcallback', -1, function (Swoole\Server $serv, $task_id, $data) { echo 'Task Callback: '; var_dump($task_id, $data); }); } elseif ($cmd == 'sendto') { $serv->sendto('127.0.0.1', 9999, 'hello world'); } elseif ($cmd == 'close') { $serv->send($fd, "close connection\n"); $result = $serv->close($fd); } elseif ($cmd == 'info') { $info = $serv->connection_info(strval($fd), $reactor_id); var_dump($info['remote_ip']); $serv->send($fd, 'Info: ' . var_export($info, true) . PHP_EOL); } elseif ($cmd == 'proxy') { $serv->send(1, "hello world\n"); } elseif ($cmd == 'sleep') { sleep(10); } elseif ($cmd == 'foreach') { foreach ($serv->connections as $fd) { echo "conn : {$fd}\n"; } return; } elseif ($cmd == 'tick') { $serv->tick(2000, function ($id) { echo "tick #{$id}\n"; }); } elseif ($cmd == 'addtimer') { $serv->addtimer(3000); } elseif ($cmd == 'list') { $start_fd = 0; echo "broadcast\n"; while (true) { $conn_list = $serv->connection_list($start_fd, 10); if (empty($conn_list)) { echo "iterates finished\n"; break; } $start_fd = end($conn_list); var_dump($conn_list); } } elseif ($cmd == 'list2') { foreach ($serv->connections as $con) { var_dump($serv->connection_info($con)); } } elseif ($cmd == 'stats') { $serv_stats = $serv->stats(); $serv->send($fd, 'Stats: ' . var_export($serv_stats, true) . "\ncount=" . count($serv->connections) . PHP_EOL); } elseif ($cmd == 'broadcast') { broadcast($serv, $fd, "hello from {$fd}\n"); } //这里故意调用一个不存在的函数 elseif ($cmd == 'error') { hello_no_exists(); } elseif ($cmd == 'exit') { exit("worker php exit.\n"); } //关闭fd elseif (substr($cmd, 0, 5) == 'close') { $close_fd = substr($cmd, 6); $serv->close($close_fd); } elseif ($cmd == 'shutdown') { $serv->shutdown(); } elseif ($cmd == 'fatalerror') { require __DIR__ . '/php/error.php'; } elseif ($cmd == 'defer') { $serv->defer(function () use ($fd, $serv) { $serv->close($fd); $serv->defer(function () { echo "deferd\n"; }); }); $serv->send($fd, 'Swoole: ' . $data, $reactor_id); } else { $serv->send($fd, 'Swoole: ' . $data, $reactor_id); //$serv->close($fd); } //echo "Client:Data. fd=$fd|reactor_id=$reactor_id|data=$data"; // $serv->after( // 800, function () { // echo "hello"; // } // ); //Swoole\Server_send($serv, $other_fd, "Server: $data", $other_reactor_id); } function my_onTask(Swoole\Server $serv, $task_id, $reactor_id, $data) { if ($data == 'taskwait') { $fd = str_replace('task-', '', $data); $serv->send($fd, 'hello world'); return ['task' => 'wait']; } if ($data == 'taskcallback') { return ['task' => 'callback']; } $cmd = explode(' ', $data); if ($cmd[0] == 'send') { $serv->send($cmd[1], str_repeat('A', 10000) . "\n"); } elseif ($cmd[0] == 'close') { $serv->close($cmd[1]); } else { echo 'bigtask: length=' . strlen($data) . "\n"; return $data; } // $serv->sendto('127.0.0.1', 9999, "hello world"); //Swoole\Timer::after(1000, "test"); // var_dump($data); // $serv->send($fd, str_repeat('A', 8192 * 2)); // $serv->send($fd, str_repeat('B', 8192 * 2)); // $serv->send($fd, str_repeat('C', 8192 * 2)); // $serv->send($fd, str_repeat('D', 8192 * 2)); return; if ($data == 'hellotask') { broadcast($serv, 0, 'hellotask'); } else { echo 'AsyncTask[PID=' . $serv->worker_pid . "]: task_id={$task_id}." . PHP_EOL; //eg: test-18 return $data; } } function my_onFinish(Swoole\Server $serv, $task_id, $data) { [$str, $fd] = explode('-', $data); $serv->send($fd, 'taskok'); var_dump($str, $fd); echo "AsyncTask Finish: result={$data}. PID=" . $serv->worker_pid . PHP_EOL; } function my_onWorkerError(Swoole\Server $serv, $worker_id, $worker_pid, $exit_code, $signo) { echo "worker abnormal exit. WorkerId={$worker_id}|Pid={$worker_pid}|ExitCode={$exit_code}|Signal={$signo}\n"; } function broadcast(Swoole\Server $serv, $fd = 0, $data = 'hello') { $start_fd = 0; echo "broadcast\n"; while (true) { $conn_list = $serv->connection_list($start_fd, 10); if ($conn_list === false) { break; } $start_fd = end($conn_list); foreach ($conn_list as $conn) { if ($conn === $fd) { continue; } $ret1 = $serv->send($conn, $data); //var_dump($ret1); //$ret2 = $serv->close($conn); //var_dump($ret2); } } } $serv->on('PipeMessage', function ($serv, $src_worker_id, $msg) { my_log("PipeMessage: Src={$src_worker_id},Msg=" . trim($msg)); if ($serv->taskworker) { $serv->sendMessage('hello user process', $src_worker_id); } }); $serv->on('Start', 'my_onStart'); $serv->on('Connect', 'my_onConnect'); $serv->on('Receive', 'my_onReceive'); $serv->on('Packet', 'my_onPacket'); $serv->on('Close', 'my_onClose'); $serv->on('Shutdown', 'my_onShutdown'); $serv->on('WorkerStart', 'my_onWorkerStart'); $serv->on('WorkerStop', 'my_onWorkerStop'); $serv->on('Task', 'my_onTask'); $serv->on('Finish', 'my_onFinish'); $serv->on('WorkerError', 'my_onWorkerError'); $serv->on('WorkerExit', 'my_onWorkerExit'); $serv->on('ManagerStart', function ($serv) { global $argv; swoole_set_process_name("php {$argv[0]}: manager"); Timer::after(5000, function () use ($serv) { echo "shutdown\n"; $serv->shutdown(); }); }); $serv->start(); ================================================ FILE: examples/server/multi_instance.php ================================================ on("start", function ($server) use ($port) { echo "Swoole http server is started at http://127.0.0.1:{$port}\n"; }); $http->on("request", function ($request, $response) { $response->header("Content-Type", "text/plain"); $response->end("Hello World\n"); }); $http->start(); }, false, false); $p->start(); } ================================================ FILE: examples/server/multi_port.php ================================================ addlistener('127.0.0.1', 9502, SWOOLE_SOCK_UDP); $port->on('packet', function($serv, $data, $client) { var_dump($data, $client); $serv->sendto($client['address'], $client['port'], "welcome admin\n"); }); $serv->on('connect', function ($serv, $fd) { echo "Client:Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $info = $serv->connection_info($fd, $reactor_id); //来自9502的内网管理端口 if($info['server_port'] == 9502) { $serv->send($fd, "welcome admin\n"); $start_fd = 0; while(true) { $conn_list = $serv->connection_list($start_fd, 10); if($conn_list === false) { break; } $start_fd = end($conn_list); var_dump($conn_list); foreach($conn_list as $conn) { if($conn === $fd) continue; $serv->send($conn, "hello from $fd\n"); } } } //来自外网 else { $serv->send($fd, 'Swoole: '.$data); } }); $serv->on('close', function ($serv, $fd) { echo "Client: Close.\n"; }); $serv->start(); ================================================ FILE: examples/server/pipe_message.php ================================================ set(array( 'worker_num' => 2, 'task_worker_num' => 2, )); $serv->on('pipeMessage', function($serv, $src_worker_id, $data) { echo "#{$serv->worker_id} message from #$src_worker_id: $data\n"; }); $serv->on('task', function (Swoole\Server $serv, $task_id, $reactor_id, $data){ echo "#{$serv->worker_id} NewTask: $data\n"; $serv->sendMessage($data, 0); //$serv->send($fd, str_repeat('B', 1024*rand(40, 60)).rand(10000, 99999)."\n"); }); $serv->on('finish', function ($serv, $fd, $reactor_id){ }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $cmd = trim($data); if($cmd == 'totask') { $serv->sendMessage("hello task process", 2); } elseif($cmd == 'toworker') { $worker_id = 1 - $serv->worker_id; $serv->sendMessage("hello worker", $worker_id); } elseif($cmd == 'task2worker') { $serv->task('hello worker from task.'); } else { echo "#{$serv->worker_id} Recv: $data\n"; } }); $serv->on('close', function ($serv, $fd, $reactor_id) { //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/server/proxy_sync.php ================================================ set(array( 'worker_num' => 32, //reactor thread num 'backlog' => 128, //listen backlog 'max_conn' => 10000, 'dispatch_mode' => 2, //'open_tcp_keepalive' => 1, //'log_file' => '/tmp/swoole.log', //swoole error log )); $serv->on('WorkerStart', array($this, 'onStart')); $serv->on('Connect', array($this, 'onConnect')); $serv->on('Receive', array($this, 'onReceive')); $serv->on('Close', array($this, 'onClose')); $serv->on('WorkerStop', array($this, 'onShutdown')); $serv->start(); } function onStart($serv) { $this->serv = $serv; echo "Server: start.Swoole version is [" . SWOOLE_VERSION . "]\n"; } function onShutdown($serv) { echo "Server: onShutdown\n"; } function onClose($serv, $fd, $reactor_id) { } function onConnect($serv, $fd, $reactor_id) { } function onReceive($serv, $fd, $reactor_id, $data) { $socket = new Swoole\Client(SWOOLE_SOCK_TCP); if($socket->connect('127.0.0.1', 80, 0.5)) { $socket->send($data); $serv->send($fd, $socket->recv(8192, 0)); } unset($socket); $serv->close($fd); } } $serv = new ProxyServer(); $serv->run(); ================================================ FILE: examples/server/redis_pool.php ================================================ set(array( 'worker_num' => 4,//base on you cpu nums 'task_worker_num' => 4,//better equal to worker_num, anyway you can define your own 'heartbeat_check_interval' => 5, 'heartbeat_idle_time' => 5, 'open_cpu_affinity' => 1, 'open_eof_check' => 1, 'package_eof' => "\r\n\r\n", 'package_max_length' => 1024 * 16, //'daemonize' => 1 )); function onStart($serv) { echo "MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}\n"; echo "Server: start.Swoole version is [".SWOOLE_VERSION."]\n"; } function onReceive($serv, $fd, $reactor_id, $key) { $key = trim($key); if($key === SERVER_RELOAD) { // check if this is a reload cmd $ret = $serv->reload($serv); ($ret === true) ? $serv->send($fd, "reload success\n") : $serv->send($fd, "reload fail\n"); }else { $result = $serv->taskwait($key); if ($result !== false) { list($status, $data) = explode(':', $result, 2); if ($status == 'OK') { $serv->send($fd, $key . " : " . var_export(unserialize($data), true) . "\n"); } else { $serv->send($fd, $data); } return; } else { $serv->send($fd, "Error. Task timeout\n"); } } } function onTask($serv, $task_id, $reactor_id, $key) { static $redis = null; if ($redis == null) { $redis = new Redis(); $redis->pconnect("127.0.0.1", 6379); if (!$redis) { $redis = null; $serv->finish("ER: Init Redis Fail."); return; } } $data = $redis->get($key); if ($data === false) { $serv->finish("ER: Get Data Fail."); return; } $serv->finish("OK:" . serialize($data)); } function onFinish($serv, $data) { echo "AsyncTask Finish:Connect.PID=" . posix_getpid() . PHP_EOL; } $serv->on('Start', 'onStart'); $serv->on('Receive', 'onReceive'); $serv->on('Task', 'onTask'); $serv->on('Finish', 'onFinish'); $serv->start(); ================================================ FILE: examples/server/reload_aysnc.php ================================================ set([ 'worker_num' => 4, 'reload_async' => true, 'max_wait_time' => 5, 'task_worker_num' => 2, ]); $serv->on('WorkerStart', function ($serv, $wid) { echo "Worker#$wid is started\n"; if ($serv->taskworker) { return; } Swoole\Event::add(STDIN, function () use ($wid) { $data = fread(STDIN, 8192); if ($data) { echo "#{$wid}: $data"; } }); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".$serv->worker_id."]\tClient[$fd]: $data\n"; }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { //echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; // $serv->finish($data); return $data; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('WorkerStop', function ($serv, $wid) { //sleep($wid + 1); }); $serv->on('WorkerExit', function ($serv, $wid) { echo "WorkerExit, PID=".posix_getpid()."\t$wid\n"; Swoole\Event::del(STDIN); }); $serv->start(); ================================================ FILE: examples/server/reload_force.php ================================================ set([ "worker_num" => 4, "max_wait_time" => 1 ]); $serv->on("WorkerStart", function (\Swoole\Server $server, $worker_id) { global $flag; echo "$worker_id [".$server->worker_pid."] start \n"; }); $serv->on('receive', function ($serv, $fd, $tid, $data) { echo "$tid recv $data\n"; if ($data) { sleep(100); } }); $serv->start(); ================================================ FILE: examples/server/reload_force2.php ================================================ set([ "worker_num" => 4, "max_wait_time" => 1 ]); $serv->on("WorkerStart", function (\Swoole\Server $server, $worker_id) { global $flag; echo "$worker_id [".$server->worker_pid."] start \n"; }); $serv->on('receive', function ($serv, $fd, $tid, $data) { echo "$tid recv $data\n"; if ($data) { sleep(100); } }); $serv->start(); ================================================ FILE: examples/server/send_1m.php ================================================ set(array('worker_num' => 1)); $serv->on('workerStart', function($serv, $worker_id) { //if($worker_id == 0) $serv->addtimer(500); }); $serv->on('connect', function ($serv, $fd, $reactor_id){ $serv->array['fd'] = &strval($fd); echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { //echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; $array = array('A', 'B', 'C', 'D', 'E', 'F', 'G'); $data = ''; $n_bytes = 0; for ($i = 0; $i < 10; $i++) { $_str = str_repeat($array[$i % 7], 4030) . "\n"; //$serv->send($fd, $_str); $n_bytes += strlen($_str); $data .= $_str; } echo "send " . $n_bytes . " bytes\n"; $serv->send( $serv->array['fd'], $data); $serv->close($fd); }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/server/sendfile.php ================================================ set(array( 'worker_num' => 1, )); $serv->on('timer', function($serv, $interval) { echo "onTimer: $interval\n"; }); $serv->on('workerStart', function($serv, $worker_id) { //if($worker_id == 0) $serv->addtimer(300); }); $serv->on('connect', function (Swoole\Server $serv, $fd){ $serv->send($fd, filesize(__DIR__.'/test.jpg')); //echo "Client:Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "Client[$fd]: $data\n"; $serv->sendfile($fd, __DIR__.'/test.jpg'); //$serv->close($fd); }); $serv->on('close', function ($serv, $fd) { //echo "Client: Close.\n"; }); $serv->start(); ================================================ FILE: examples/server/single.php ================================================ on('Receive', function($serv, $fd, $reactor_id, $data) { $serv->send($fd, "Swoole: $data"); }); $server->start(); ================================================ FILE: examples/server/tcp_client.php ================================================ connect('127.0.0.1', 9504)) { exit("connect failed\n"); } $func = "send_test" . intval(empty($argv[1]) ? 3 : $argv[1]); for ($l = 0; $l < 1; $l++) { $data = ''; for ($i = 0; $i < 10; $i++) { $len = rand(100000, 200000); echo "send : " . ($len + 4) . "\n"; send_test3($client, $len); } } function send_test3($client, $len) { $data = pack('N', $len + 4); $data .= str_repeat('A', $len) . rand(1000, 9999); $chunks = str_split($data, 4000); foreach ($chunks as $ch) { $client->send($ch); } // $data = $client->recv(); // echo "recv : " . strlen($data) . "\n"; } ================================================ FILE: examples/server/tcp_server.php ================================================ run('0.0.0.0', 9504); class SocketServer { protected $serv; //swoole server const MAX_PACKAGE_LEN = 8000000; //max data accept function run($host, $port) { $this->serv = new Swoole\Server($host, $port, SWOOLE_BASE); $this->serv->set(array( 'enable_coroutine' => false, 'worker_num' => 1, //how much worker will start )); $this->serv->on('receive', array($this, 'onReceive')); $this->serv->start(); } function onReceive($serv, $fd, $tid, $data) { echo "recv " . strlen($data) . " bytes\n"; // $packet = substr($data, 4); // $result = array( // "code" => "0", // "msg" => "ok", // "data" => $packet, // ); // $resp = json_encode($result); // $send_data = pack('N', strlen($resp)) . $resp; // echo "send " . strlen($send_data) . " bytes\n"; // $serv->send($fd, $send_data); } } ================================================ FILE: examples/server/trace.php ================================================ set([ 'worker_num' => 1, 'task_worker_num' => 1, 'request_slowlog_timeout' => 1, 'request_slowlog_file' => '/tmp/trace.log', ]); $server->on('Receive', function($serv, $fd, $reactor_id, $data) { if (trim($data) == 'task') { echo "task\n"; $serv->task($fd); return; } test(); $serv->send($fd, "Swoole: $data"); }); $server->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; test(); $serv->send($data, "Swoole: task\n"); }); $server->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $server->start(); ================================================ FILE: examples/server/uid_dispatch.php ================================================ fdlist = []; $serv->set(array( //'tcp_defer_accept' => 5, //'ipc_mode' => 2, 'worker_num' => 4, //'task_worker_num' => 2, 'dispatch_mode' => 5, //uid dispatch //'max_request' => 1000, //'daemonize' => true, //'log_file' => '/tmp/swoole.log' )); $serv->on('timer', function($serv, $interval) { echo "onTimer: $interval\n"; }); $serv->on('start', function($serv) { //$serv->addtimer(1000); }); $serv->on('workerStart', function($serv, $worker_id) { echo "{$worker_id} start".PHP_EOL; //if($worker_id == 0) $serv->addtimer(1000); }); $serv->on('connect', function ($serv, $fd, $reactor_id){ //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; echo "{$fd} connect, worker:".$serv->worker_id.PHP_EOL; }); $serv->on('task', function ($serv, $task_id, $reactor_id, $data){ }); $serv->on('finish', function ($serv, $fd, $reactor_id){ }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $conn = $serv->connection_info($fd); print_r($conn); echo "worker_id: " . $serv->worker_id . PHP_EOL; if (empty($conn['uid'])) { $uid = $fd + 1; if ($serv->bind($fd, $uid)) { $serv->send($fd, "bind {$uid} success"); } } else { if (empty($serv->fdlist[$fd])) { $serv->fdlist[$fd] = $conn['uid']; } print_r($serv->fdlist); foreach ($serv->fdlist as $_fd => $uid) { $serv->send($_fd, "{$fd} say:" . $data . PHP_EOL); } } }); $serv->on('close', function ($serv, $fd, $reactor_id) { //echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; unset($serv->fdlist[$fd]); }); $serv->start(); ================================================ FILE: examples/server/unix_stream.php ================================================ set(array( 'worker_num' => 1, )); $serv->on('connect', function ($serv, $fd, $reactor_id){ echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; $serv->send($fd, json_encode(array("hello" => '1213', "bat" => "ab"))); //$serv->close($fd); }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/server/zmq.php ================================================ bind("tcp://*:5557"); $receiver = new ZMQSocket($context, ZMQ::SOCKET_PULL); $receiver->bind("tcp://*:5558"); function onZMQR() { global $receiver; $string = $receiver->recv(); echo $string, PHP_EOL; } $serv->set(array( //'tcp_defer_accept' => 5, 'worker_num' => 1, 'reactor_num' => 1, //'daemonize' => true, //'log_file' => '/tmp/swoole.log' )); $serv->on('workerStart', function($serv, $worker_id) { global $sender; global $receiver; $rfd = $receiver->getsockopt(ZMQ::SOCKOPT_FD); Swoole\Event::add($rfd, 'onZMQR', NULL , SWOOLE_EVENT_READ); echo "worker start\n"; }); $serv->on('connect', function ($serv, $fd, $reactor_id){ echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { $cmd = trim($data); echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; if($cmd == "zmqtest") { echo 'aaaaaaaaaaaa'. PHP_EOL; $sender->send("msg to zmq"); } $serv->send($fd, 'OK'.PHP_EOL); //$serv->close($fd); }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); //$serv->start(); ================================================ FILE: examples/socket_coro/client.php ================================================ connect('localhost', 9601); while ($retval) { $n = $socket->send("hello"); var_dump($n); $data = $socket->recv(); var_dump($data); if (empty($data)) { $socket->close(); break; } co::sleep(1.0); } var_dump($retval, $socket->errCode); }); ================================================ FILE: examples/socket_coro/server.php ================================================ bind('127.0.0.1', 9601); $socket->listen(128); go(function () use ($socket) { while(true) { echo "Accept: \n"; $client = $socket->accept(); echo "New Coroutine: \n"; go(function () use ($client) { while(true) { echo "Client Recv: \n"; $data = $client->recv(); if (empty($data)) { $client->close(); break; } var_dump($client->getsockname()); var_dump($client->getpeername()); echo "Client Send: \n"; $client->send("Server: $data"); } }); } }); ================================================ FILE: examples/socket_coro/udp.php ================================================ bind('127.0.0.1', 9601); while (true) { $peer = null; $data = $socket->recvfrom($peer); echo "[Server] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n"; $socket->sendto($peer['address'], $peer['port'], "Swoole: $data"); } }); //Client go(function () { $socket = new Co\Socket(AF_INET, SOCK_DGRAM, 0); $i = 0; while (true) { $socket->sendto('127.0.0.1', 9601, "HELO-" . $i++); $peer = null; $data = $socket->recvfrom($peer); echo "[Client] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n"; co::sleep(1); } }); ================================================ FILE: examples/ssl/ca/ca-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKDCCAhACCQCEAJc680CvRDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc0OTA1WhcNMjgx MjA0MDc0OTA1WjBWMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTELMAkGA1UEAwwC Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDN8IQ468DyDRIIMSx4 v2Apmrbe1vIHYNOfW7DexM+DK8TX46l3v7SsLCHCYrKmEnl2SoDyjkDBQukrZRwQ lZ2VaNmllK3A3HQVl2tdFWHkLz/Qdq5bLnUEz1/tI33DcjJ8fo70wpz6pAgEsHZt +AX1MbZVWJDbMpH8SHaJduHsOD1WQo0qiJF1Z6PdrP9SPD2Z9g56b/TKA7BU9sd+ N7QsChqQHFWjsyC80Hl6f2c+YaYjd4/KbunzIvhFIuK/bOGB0U+Cu2I8Fq4yGHFA F/i/+Rthabc9jxWsbbAMFB2b/TkDhuFrjH+J7Bbqd8cZdUshuQv6l3wU4m5bv5Zm 7s1rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABkr7mqcRBJEBG+argNC0n+IY6Df kd3YRgNhAdedUfyrG6C+SwaFiAkhgZQH8NuHEzANAnLZe8DlqYEsmWJGRT7jy0Pa I8a8PA8PajSAg3tp6wR7ST2E4ZiQwjJsDxaFI+yDMiP5uXyHEA9WHQZ9OiWRDO/9 X8dJw/uuEk4dXdpyqTfNleHN/5TGJP07qrPSRr0+1+HVw1YZjgKsn7ivgAoaNxEe fDnhoF8WncswJLP3e5D0EG6zrod/8r6H009CJWGadaGWEtcvOoGq6E97mCwRrKmI 25Fr/izhV9gvf/Uj+nDZfvJwdx7GjxoHM+D3uc/2D8AxRkAtIPcABTyvPvI= -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ca/ca-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAzfCEOOvA8g0SCDEseL9gKZq23tbyB2DTn1uw3sTPgyvE1+Op d7+0rCwhwmKyphJ5dkqA8o5AwULpK2UcEJWdlWjZpZStwNx0FZdrXRVh5C8/0Hau Wy51BM9f7SN9w3IyfH6O9MKc+qQIBLB2bfgF9TG2VViQ2zKR/Eh2iXbh7Dg9VkKN KoiRdWej3az/Ujw9mfYOem/0ygOwVPbHfje0LAoakBxVo7MgvNB5en9nPmGmI3eP ym7p8yL4RSLiv2zhgdFPgrtiPBauMhhxQBf4v/kbYWm3PY8VrG2wDBQdm/05A4bh a4x/iewW6nfHGXVLIbkL+pd8FOJuW7+WZu7NawIDAQABAoIBAG8NrW+4ikK9E6VB RzcSFHw7BzRh+wBJkbUUz+tBfJnCp5K4HTILOhXCcq4NZUMzITbN9H1Ygsj1GSIi kyDKXcEtWnj1FUG0YBEop2f52OEhLatv5JQdGFNwtMz/X5le4qTJ5rNMozRx8jT9 J9AjeKsBlM6vROY/fgPq26lDZ49mx9Rp8tIV4lps9cZ4YrsMu47tXTFjK2dYyVdh NbMNE1tgfIQd+ZH5HpLcEZas72y7djRC3UNRqlHJ6CvP1Kb98+ILa+CLj94mN+NV VvoqzLHLk6n7nam2FKVHMfHl0fSmQ1TQmI3dyyh62I7wpYAPi2Sk+s8UQvjRjKwI LCJkGZECgYEA+8DLpKnIvWKf864nknEt6XF9gV/Ft0Mld61rzkTJy5zVjD9FXqJl F8Cfn6RfSb1dw+wqXpoJd2R+WRTI60jCe9tfY8G7Kpmz6jV+At4jQ932QQyNRJnG yVYn8wz4XsPGgfz0OaFDZvIlIcQIEfkEOaow019+sU6K9S6oZsILvHMCgYEA0Wnf mZFlw6C9cbSb1f1Nl0YGm1NLMJuIcYudRx8OfcbNSlRFCQrVIjNcjIpnNE1ltd89 PrRZJnkkX0lxkg9jPFqSeYcvhAksZrhO0a7ZeKHyH7F2c6E6LIqwZnoZ3tRUWzm+ M7L5Bh6TS5G0/Y0tPB3UngRy6SWmkjTI1kCrJSkCgYEA+buHh4GRCyUxjHXe9Wrm Y2NcX6EUrbWjDjPu9D/SefPK9oOxGa44YDaJYcCZcbmysO6uHQfqihAbMdznblbP 7jNwTbHZK+oqyhNkPA4Fp4YgiOidnkZ5JjIcEQN1wfOtEDdQQXbSOOXNpdAjPUjr QxSruJJLSDGksJEnJkApijcCgYAvl24XinGkKe2j37XixfRSQrnRpvZZj59MnrwZ o7c5YmrZT5l42pthcGbCEdouisBoutlCXN493h4kAZ7r4M9esf7D27MywJr0pUJU aPZHchaCmWQgFy4PXV2FjI5Ak+Sv7smJGk151I0JCY4maWU0WlstpjkvPz1B0Pkr p/q9MQKBgQDjVs5yfWb8PEwlw5oqkk8IyNVHKMz/62r7tfuT1gGr9OepWXv900fn Z7TavI6tr0x3zCblqTSDROUYYV6sadMoCMrHQ6yihwJNM4Htp2Qi7tJJs1onC8wE p2gXL1fPFq0sjSaRcZhaxSrpF2KAfIPmoC17Li30A2iVe2FyLIQZvg== -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/ca/ca-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmzCCAYMCAQAwVjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCzAJBgNVBAMM AkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzfCEOOvA8g0SCDEs eL9gKZq23tbyB2DTn1uw3sTPgyvE1+Opd7+0rCwhwmKyphJ5dkqA8o5AwULpK2Uc EJWdlWjZpZStwNx0FZdrXRVh5C8/0HauWy51BM9f7SN9w3IyfH6O9MKc+qQIBLB2 bfgF9TG2VViQ2zKR/Eh2iXbh7Dg9VkKNKoiRdWej3az/Ujw9mfYOem/0ygOwVPbH fje0LAoakBxVo7MgvNB5en9nPmGmI3ePym7p8yL4RSLiv2zhgdFPgrtiPBauMhhx QBf4v/kbYWm3PY8VrG2wDBQdm/05A4bha4x/iewW6nfHGXVLIbkL+pd8FOJuW7+W Zu7NawIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAMs2wMLBDqw2TxBy2wIM2ytp Lupk/3QVkvMpBIiYD7Jbod5YRjkV+LagLq/KE10y0jWSGuQSpbJHypb/bXsvKcU7 18uzM/CU08xz5WWQ7LqAvITT/4GHT/WNLn38WBkIG6qHYeaaaLcGdo53KlbQiYqq 5An3sxVin1C5dHR2ew5bKMNh+Zfq8kNXIPQbKd2KcEDKPYvt7Zb92SOijpkK/SVb hWgHkGsyFothmIpdKarOsQ2E2SSyTtNUKws/ZcRq1KQs7qAxNT7As4vBAfCGv0Yv s7gohLXDPZDWvoXmRWOPiY7nYlTzxBDKXEwtEqN0ZJcQv9OHJveQ6SSK3o75EDM= -----END CERTIFICATE REQUEST----- ================================================ FILE: examples/ssl/ca/client-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKDCCAhACCQCRX5mTEHplWDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc1OTQzWhcNMjgx MjA0MDc1OTQzWjBWMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTELMAkGA1UEAwwC Y24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDATA3sGW041+GHub7 GgfAA1vcZP48Gw12F2jT15mX8SMjjQ8AutdnxG3OMt5lzgpw5+SVUgxK0nIdGg0f O0Rseh3VKYl7F2neUWzETfLo3qywVsu21fJiuxwtS8h+r/pM/0/drC20qbKzDEDT Q4hwVsCMjx0CUxZoFgiHBRhyIT6DqBdt8fylSfC0ocvLXI32Hl75QEhjrSqZqvHY z7cT5+YzuL/SQ5QOEtCGQarExhgqgQEtpD5ZGhbalDhrZDI/ZwRnA27I54s0VCTY ZT6XNASf1UUH6xqV9IT6lY2GQ9rNkzKVjaG5/gJDcKPO+sBLsjzgsW4qJ8GiLPLY tyIZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAICx9BbAwat+nF3Tcw3y4XyX0fpb J3YXAyIESNKEh1F6IRVdPkcHTMd2YU583pGD38UJzWOVj92bBfRRXUbINnCoqM30 QHiBfg0kQhc9UNKY+62urhSfUs4HGiyrRVwVbSDbqZpzg5MXa+msgktWMDL1KL8d gljnqn/Rad/hYKgjIGJ0MIk55qKl6z6tlVwIIPkdEm6FFcRhejLgkCpDC4WBwbmi U1hpSQfoRXICVyO96mL+/wpekZTAPFZB2LOaZOkT4/hGhlNVFZfcgws+hTh11EZb wfpgVYxk/T9MfgEyZGBtarUaNR/xobdnABo5eyxXrrd0RU7SjIrIRA5ORxE= -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ca/client-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAwwEwN7BltONfhh7m+xoHwANb3GT+PBsNdhdo09eZl/EjI40P ALrXZ8RtzjLeZc4KcOfklVIMStJyHRoNHztEbHod1SmJexdp3lFsxE3y6N6ssFbL ttXyYrscLUvIfq/6TP9P3awttKmyswxA00OIcFbAjI8dAlMWaBYIhwUYciE+g6gX bfH8pUnwtKHLy1yN9h5e+UBIY60qmarx2M+3E+fmM7i/0kOUDhLQhkGqxMYYKoEB LaQ+WRoW2pQ4a2QyP2cEZwNuyOeLNFQk2GU+lzQEn9VFB+salfSE+pWNhkPazZMy lY2huf4CQ3CjzvrAS7I84LFuKifBoizy2LciGQIDAQABAoIBAAUtZuZTnJ+s2cBp 2aAZFNOYQzwawlU+MqFMBdB6DO3nzRMIj7lpPIbYxEGRQC5bxhMxVPRR9VmXNMhp BKgrnqlypJAgRjIqcPkiGE0t9KESKGuWIlp8W68bYKgjNvOGIJMixDASfjQpvUDb tmzlgh3Zb8tLkW2aXyHsU9Wh4QBGrcW7faiu4CcC1kHJk5FUg6eSxN0vhZxCQgKP 6i4aHPOKoLu3jI36EGERTiFeDDUhjFynRLYg8gfaQc93gzuZGZEMjtlmiTUPMrKn 9O15Y5RIDFDQSRDngNbc60ieIJ1piqzHfL6X8BQxK+Pd/J/3SSfFuXGGfISYahGS 67SzHAECgYEA831stnQB0R6AP5UGuJDnknjgmkH9C4a0EpV3YFb8CKdLN7x3mU3p wLPk0bpjObW+PHmUNZWsrcnKtuw+5yVFefO+bZp8KdFptcFI2cgEsLm/PIoclZyK dBE0s2LlCqg/XNYImTYamsRD8RO0DYoT3yFK0/oQJUdyejBenOXjq7kCgYEAzQYL zKW0BP794eCo//fxmcjNTKrY5TVErWBQS8RI7uqdOsYSnlkBM/62pYnuGX3//Ol5 1fsuRbPBTNhdKMxAF+fjdGCgx6yxEcrt7d6aEp8hIDvix/YuXvYSe3HRSULsWboE yLxVNW7GaeXltcLa8chJzjv5QowPhFtMFnIeGWECgYASyc3WslLlkQXyYgx3t263 Ba7HGGpvNkC04mWowG2IOWh7b4aHBNURso/ogwpfDKAWSIdgLyF484Y54TblXSHk TCOYj4AfNfKDPNvjOiTqghq4kCuue7rPQ/ieEvZ7gQoKntVhBSS+ZoCbnJqbJNlt sUL14mjWh5HUzeCvGKoduQKBgQDAndSKEId/Rh05j/rWeJfoNgoC5GPfe1spU9o8 RI2MwKi05g/p6o7BzaTFAz/JfWH5t1P6oABKBeYuDvv+712r1/UOihWjYm/82l0M ZFz178CMaxbVbEkGeELG7RiFrrUkWypRePbIu2j3ZsCYMhfGnbgRZLD6P2H4pFlo oQ/8IQKBgGicAkmrhW1GLtEXpUaJDW03M59DQDkJzCJwp2ah/BCOuo0yNt3MywpM FPLay8ZGapGrg2vZKd713w540zrQYLNFueP8IobxHRHYvpQM+DTiNr8vHX5ISyH2 Rhc1ZZklKakTefKwTdySi212GFgdV7nCHXvurQL7hvlusWlie5ih -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/ca/client-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmzCCAYMCAQAwVjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCzAJBgNVBAMM AmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwwEwN7BltONfhh7m +xoHwANb3GT+PBsNdhdo09eZl/EjI40PALrXZ8RtzjLeZc4KcOfklVIMStJyHRoN HztEbHod1SmJexdp3lFsxE3y6N6ssFbLttXyYrscLUvIfq/6TP9P3awttKmyswxA 00OIcFbAjI8dAlMWaBYIhwUYciE+g6gXbfH8pUnwtKHLy1yN9h5e+UBIY60qmarx 2M+3E+fmM7i/0kOUDhLQhkGqxMYYKoEBLaQ+WRoW2pQ4a2QyP2cEZwNuyOeLNFQk 2GU+lzQEn9VFB+salfSE+pWNhkPazZMylY2huf4CQ3CjzvrAS7I84LFuKifBoizy 2LciGQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBABGKoAa56G5lDWbZEByBiKjJ Lf7E74z8CSFLWKd4ytbS/qBJJndMz4oRsVO2etu4XlmRuZTrt1gJ1QXbQHm5b7b2 Rx8CgE2ZGTfWIMc6B111z6Rl5ahZRIt7KEduC4+hsEHeim7eoQNkWRz6XU+fBWYA IAIjteH22Id0LnQuEXPcxvhBycZwcxFCaLKbimltgZskXkNpe1FCYbU3ri9ZTt8H 5MmQOPxyBr5aSWgmeJjLtrgGiH3hOkS494sYAxG+yrkKObDEa09xdP5+vS5Y8rDF 2WODi1xjIZEypAYoDk+UrI6JY6oaSEdZitw4sLew7+kopMXBZcEbsyVuGfA9Iag= -----END CERTIFICATE REQUEST----- ================================================ FILE: examples/ssl/ca/client.crt ================================================ -----BEGIN CERTIFICATE----- MIICKTCCAZICCQC8hk8d+ohSHDANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJD TjERMA8GA1UECBMIWmhlamlhbmcxETAPBgNVBAcTCEhhbmd6aG91MQ4wDAYDVQQK EwVNeSBDQTESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE3MDIwOTA3NTMxMloXDTE3 MDMxMTA3NTMxMlowWzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw DwYDVQQHEwhIYW5nemhvdTESMBAGA1UEChMJTXkgQ2xpZW50MRIwEAYDVQQDEwls b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEUnBzXTTiyUmDb yhkQoQ/yH1zTnuIk5Meg1Bp0fp1l4kwiizdPbZkk4YkTT/HXdTE6822Cqho+CwGE VqWZyyd2AZmj87OGb4ZRCyyFzzjfEwdCTvyqZSUBoc1gvSGdEiaA4mXE87Y0XcMB BasOrfmO76nuzyaXLT7xDjrB+Qw5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAC09q KOuSbAW4Zt4I3CZOh3j+bMax++6M37RWCpShCTdaapdm37y436QbtuB9K6ry7MLo 0HcqQksDm8tLmQqEenfhqZo10FiQj0v1ckvg3lzH4OIP5IM0zXkApnlX6aKuOBbC XMkYSqdwK0A8QNrl051RCKE2CaYK3cnSN0z4+Vs= -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ca/client.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCRFJwc1004slJg28oZEKEP8h9c057iJOTHoNQadH6dZeJMIos3 T22ZJOGJE0/x13UxOvNtgqoaPgsBhFalmcsndgGZo/Ozhm+GUQsshc843xMHQk78 qmUlAaHNYL0hnRImgOJlxPO2NF3DAQWrDq35ju+p7s8mly0+8Q46wfkMOQIDAQAB AoGAarOFvZB7st8zxxjfImAglOG2P0dE633G5Stb07kqBgkQzn35dcxtBt0hIveZ LH0SLAr3Tetzv6kx3wO91j2uM1QURztULIcFaDrQyrBbAYoka2WDoxJCSRoGvb9X 7JoyuYtYvbctT8dvYF9mVttq/YdAjFfs7RHwMGSZUc9xkCECQQDA1NNdmsupDZrG e6sBToEcNuLd/ahB77AS19dWt9WLGSDhG+/wj2bx9l73RTnadenjIPoRaY0RZu4u fDNlKYErAkEAwJtQn4PSLZe3Rx/FfrGn4pxLFvL/6EgaPKxY1nNd55JY3x15L9A9 IjODD+CH+BYbYufHfI5n27QIVRDIpNAOKwJAb/2q3BxA1+fs0gWU5WdgmLBPxjnB dLnt+qOcjuKphOWNMO/2xDGkyjYaJWXxGa2NrrnCQkaZBVhQUHMVrlUSjQJAeUhT +F5VlwgWDN9gyWqtQPESB510r5vXiaUtO7zhwNRSygwRJ56FIGg3e2PzurCRBjLV VwWFOL+hD4/GCKJKiQJAfQbp5pZ1Fni/YWo2gmuQi+9kMv3BKfpeHwNclPICY86c JSAo2+e7Xwz+GHxW9Hqpuz4J1CKjFGS0VzZAFWBi2w== -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/ca/server-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDJzCCAg8CCQCRX5mTEHplVjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc1NjUxWhcNMjgx MjA0MDc1NjUxWjBVMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTEKMAgGA1UEAwwB KjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANQSsIppKx1KEIZSyPlq VNA8NQJ04xsV0fUlQzehwAXTpM2Km9AMXKfLw3b4Z2dIE+O4HTc+2EpIo+oMdEeA Tn1atAAqO5XDfrYfmnRdDCcZg7wxqdCJHh6OHNXv2K58OnmoyZBh5gPoR9LQuIUF C4p9TyE+qUxSwrzast2XkoX5JH+p6uKE/a7eDUMu8Dlx6E1Zs9aTNyyTBO3onK0N iCZrguuOJDUnjSpSS4P8U74Wf4k7brl70QWS6KAXWC+12VSIKivJA8SZDIeQqgO+ ji5K1adQIxQR3g17QHOq8ZmrzwUWE+XdDCwTv7dkgcnt8yR4R3+fc9tMugqSS53/ lDMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAyQZRigqe+J/jGj1b08jAci4Dfu6k 4v52Hb0wvHvXi/C8ccL+g9f/9sLVKP5gdYsZVSf38wPNh9xTNBzbgKwUkXO37yHY 8vXSt/coBkhvXAjQT4ICcKdH683mfD5Vuyq7m8QP6EbqvCo0s0of1I97j0VV3xf/ SiSot3F++5jqaReLiDU4yjhSiACuZzyMBmqZml3jGEUg4s8+ruoj9t2PoeQv9fi8 LHC+GnICEKCgg55SoXGKtsBdM1sewIdy5KHvMet9v00PkVBFFNDSt9wDugttjPCZ RbonE46/2/+qpGG+nVU3jIfUjAoHvXy30w1dBwTAemUC0yeN+xjl3OHMjA== -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ca/server-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEA1BKwimkrHUoQhlLI+WpU0Dw1AnTjGxXR9SVDN6HABdOkzYqb 0Axcp8vDdvhnZ0gT47gdNz7YSkij6gx0R4BOfVq0ACo7lcN+th+adF0MJxmDvDGp 0IkeHo4c1e/Yrnw6eajJkGHmA+hH0tC4hQULin1PIT6pTFLCvNqy3ZeShfkkf6nq 4oT9rt4NQy7wOXHoTVmz1pM3LJME7eicrQ2IJmuC644kNSeNKlJLg/xTvhZ/iTtu uXvRBZLooBdYL7XZVIgqK8kDxJkMh5CqA76OLkrVp1AjFBHeDXtAc6rxmavPBRYT 5d0MLBO/t2SBye3zJHhHf59z20y6CpJLnf+UMwIDAQABAoIBABsaqGWIO3Jq3OHO AtGdvWk/yKafucbkawkh0g1yJtCX1CBQ7skQS2dGq5yw8sZIeAJKrbE53vrffdOT 8+iPhXiP9yFuRF9HtRIhx+PHYJ4+rI+I6WM1oxvUnerZ1J+4UODrqZdCa9tj/In9 ExgtJOR2T364O3r9uNXaPbIEhT2J9m6+3/7+hk1Cux7FHYLQmPnSSnxNmtuugwx5 aA4Wbytf3zFWG+1sDZQI4fgx80IBQCOVZPw1j8WzByQJko5Y0hYeEpVhXhU2AT/O b0Jo8w+vP/Z9N6UOLH3TCuLNKckeG5Bem/yh3NtldJztnt+rOPO4kL33d6GONbfl LAKTuoECgYEA9xlpIna96ZT9Cot2vgeLUIK/xyMU9WmPNRavx3akz50AxoU3ep4p Fwv1TarXn3pfaeAV34qzM4vxkPGQ7QF8j/qLrvjpXWQwfwiRqrC1T3S8J8FZegGc 11ZzTURICniOtgNn5Ai/RM3y2tR+rOC2gTUpB7UUAW1cOGxZ/xNMPksCgYEA27ZK HNdoDClAW22gWRLn8bJLjOWdVcWpoI8zULRgfcyvm9ffbRaguRx0R+a3TbHZvT7c bxppYTn4+RXuIhM5hI4PxqVtW9hWQvzta9wy44EBXUebwq0NjVEV3X1iB3T7s5UN aXf0p+jwPJeFYRjU46nSqHxLtCu+Z4N67itZsLkCgYBGmLf05L/1kF+LN7VBLZ6b 7f8kwd0nEHWd0VF/R2Jm/pBOJ5lmKvHKZ9w5yGm6YpgIA8G8EECKpZzqsFmbnSUd bZOi9fKWgB1q7ePQRJRIky1+njsWJIfO7iAbHzsF4tPDJM311Jnw6nZMFxHqCnma CLokPZPtpHUImcxMmBLcaQKBgCDCsZJTXCRUElCPrBUcCJG1cCZ2JlufuYdDUvd5 rdmUJ6spKRtvAFAZugzuje570mexc9TtL0FcD4+0gWl7TFxDgn6wM/o568LZS92F W+EoXze1cbg5bYfgy03XYEKUYuOIGhPb2xMeXJPsU+sCdNvteoe84NzgmOd+Z1oU BK+hAoGAXiQPmXfdk/hbxdiUwlClm9OGNH6Wd1jXiFRUcGLN43CYi1rRe0ziX+I0 2TwxHu8RcUy4RLiUlOnB1aDyQu1sUHJd+rnDqSUph5ozsSUuUBuJJxm4g7dC/9kZ Abs7BkHltPaTAGKW9RV07XydJPLEfbDJ0WGzkxeADMRzeOlHkhQ= -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/ca/server-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmjCCAYICAQAwVTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCjAIBgNVBAMM ASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUErCKaSsdShCGUsj5 alTQPDUCdOMbFdH1JUM3ocAF06TNipvQDFyny8N2+GdnSBPjuB03PthKSKPqDHRH gE59WrQAKjuVw362H5p0XQwnGYO8ManQiR4ejhzV79iufDp5qMmQYeYD6EfS0LiF BQuKfU8hPqlMUsK82rLdl5KF+SR/qerihP2u3g1DLvA5cehNWbPWkzcskwTt6Jyt DYgma4LrjiQ1J40qUkuD/FO+Fn+JO265e9EFkuigF1gvtdlUiCoryQPEmQyHkKoD vo4uStWnUCMUEd4Ne0BzqvGZq88FFhPl3QwsE7+3ZIHJ7fMkeEd/n3PbTLoKkkud /5QzAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAU1IPD//ftlyUZAoHrYBY0fkK ROOB/3ALET2+3QYkQJKnDFSucrvYRU8nHuGVe2aE+ej1QZ5yOzicwRoOuixkD98V cg+6ngol96SHXoV4j2EkXYv0w9bTVlUdTUH8/SDfnNLvzzZdRNiw9fOlt15DWgfY MtJi5ReH555lLQ6JU+NNzyP+zAExx9uUdDd1H8DyIPjip5xexxKbXGSgPoDnGGVS zJ7SkzmlGcHR3XYqQglD6poqnvxhDm4O9L6cK74RIJzUr+lDOVWhVND+6zu8vrwW OqM2V2qN8MNu18umKfqXCgcWYFpwhc0fJktPXi8bE2knU9vbOLJHV5w0XET4AQ== -----END CERTIFICATE REQUEST----- ================================================ FILE: examples/ssl/client.c ================================================ /** * gcc -g -o client client.c -lssl -lcrypt -lcrypto */ #include #include #include #include #include #include #include #include #include #include #include #define FAIL -1 int OpenConnection(const char *hostname, int port) { int sd; struct hostent *host; struct sockaddr_in addr; if ((host = gethostbyname(hostname)) == NULL) { printf("Eroor: %s\n", hostname); perror(hostname); abort(); } sd = socket(PF_INET, SOCK_STREAM, 0); sw_memset_zero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = *(long*) (host->h_addr); if (connect(sd, (struct sockaddr*) &addr, sizeof(addr)) != 0) { close(sd); perror(hostname); abort(); } return sd; } SSL_CTX* InitCTX(void) { SSL_METHOD *method; SSL_CTX *ctx; OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */ SSL_load_error_strings(); /* Bring in and register error messages */ // method = SSLv3_client_method(); /* Create new client-method instance */ method = TLSv1_2_client_method(); ctx = SSL_CTX_new(method); /* Create new context */ if (ctx == NULL) { ERR_print_errors_fp(stderr); printf("Eroor: %s\n", stderr); abort(); } return ctx; } void ShowCerts(SSL* ssl) { X509 *cert; char *line; cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */ if (cert != NULL) { printf("Server certificates:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("Subject: %s\n", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("Issuer: %s\n", line); free(line); X509_free(cert); } else printf("No certificates.\n"); } int main(int count, char *strings[]) { SSL_CTX *ctx; int server; SSL *ssl; char buf[1024]; int bytes; char *hostname, *portnum; if (count != 3) { printf("usage: %s \n", strings[0]); exit(0); } SSL_library_init(); hostname = strings[1]; portnum = strings[2]; ctx = InitCTX(); server = OpenConnection(hostname, sw_atoi(portnum)); ssl = SSL_new(ctx); /* create new SSL connection state */ SSL_set_fd(ssl, server); /* attach the socket descriptor */ TLSv1_2_client_method(); if (SSL_connect(ssl) == FAIL) /* perform the connection */ { printf("Eroor: %s\n", stderr); ERR_print_errors_fp(stderr); } else { char *msg = "HelloWorld"; printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); ShowCerts(ssl); /* get any certs */ SSL_write(ssl, msg, strlen(msg)); /* encrypt & send message */ bytes = SSL_read(ssl, buf, sizeof(buf)); /* get reply & decrypt */ buf[bytes] = 0; printf("Received: \"%s\"\n", buf); SSL_free(ssl); /* release connection state */ } close(server); /* close socket */ SSL_CTX_free(ctx); /* release context */ return 0; } ================================================ FILE: examples/ssl/client.php ================================================ set(array( 'ssl_cert_file' => __DIR__.'/ca/client-cert.pem', 'ssl_key_file' => __DIR__.'/ca/client-key.pem', // 'ssl_cert_file' => __DIR__.'/ca/client.crt', // 'ssl_key_file' => __DIR__.'/ca/client.key', 'ssl_allow_self_signed' => true, 'ssl_verify_peer' => true, 'ssl_cafile' => __DIR__.'/ca/ca-cert.pem', )); if (!$client->connect('127.0.0.1', 9501, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } echo "connect ok\n"; $client->send("hello world-" . str_repeat('A', $i) . "\n"); echo $client->recv(); ================================================ FILE: examples/ssl/co_client.php ================================================ set(['ssl_host_name' => 'pro-api.coinmarketcap.com']); $c->get('/'); var_dump($c->body, $c->headers); }); ================================================ FILE: examples/ssl/gen_cert.md ================================================ ```shell openssl genrsa -out ssl.key 2048 openssl req -new -key ssl.key -out ssl.csr openssl x509 -req -days 365 -in ssl.csr -signkey ssl.key -out ssl.crt ``` ================================================ FILE: examples/ssl/http_client.php ================================================ setHeaders(array('User-Agent' => 'swoole-http-client')); $cli->on('close', function($_cli) { echo "connection is closed\n"; }); $cli->get('/?dump.php?corpid=ding880f44069a80bca1&corpsecret=YB1cT8FNeN7VCm3eThwDAncsmSl4Ajl_1DmckaOFmOZhTFzexLbIzq5ueH3YcHrx', function ($cli) { var_dump($cli); var_dump($cli->headers); echo $cli->body; //$cli->close(); }); ================================================ FILE: examples/ssl/passphrase.php ================================================ set(array( "ssl_key_file" => __DIR__ . '/ssl.key', "ssl_cert_file" => __DIR__ . '/ssl.crt', 'ssl_passphrase' => '5524001', )); if (!$client->connect('127.0.0.1', 9501, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } echo "connect ok\n"; sleep(1); for ($i = 0; $i < 1000; $i++) { $client->send("hello world-" . str_repeat('A', $i) . "\n"); echo $client->recv(); } sleep(1); ================================================ FILE: examples/ssl/server.php ================================================ addlistener('0.0.0.0', 9502, SWOOLE_SOCK_TCP); // $port2->on('receive', function($serv, $fd, $reactor_id, $data){ // echo "port2: ".$data."\n"; // }); $serv->set(array( // 'worker_num' => 4, 'ssl_cert_file' => __DIR__.'/ca/server-cert.pem', 'ssl_key_file' => __DIR__.'/ca/server-key.pem', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => __DIR__.'/ca/ca-cert.pem', 'ssl_verify_depth' => 10, )); $serv->on('connect', function (Swoole\Server $serv, $fd, $reactor_id){ echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; $info = $serv->getClientInfo($fd); var_dump($info); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; $serv->send($fd, "Swoole: $data\n"); }); $serv->on('close', function ($serv, $fd, $reactor_id) { echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; }); $serv->start(); ================================================ FILE: examples/ssl/ssl.crt ================================================ -----BEGIN CERTIFICATE----- MIIDlTCCAn0CFGDgpe31nz4rUDOQKyH6U53AWXHoMA0GCSqGSIb3DQEBCwUAMIGG MQswCQYDVQQGEwJjbjERMA8GA1UECAwIc2hhbmdoYWkxETAPBgNVBAcMCHNoYW5n aGFpMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTEOMAwGA1UEAwwF cmFuZ28xHzAdBgkqhkiG9w0BCQEWEHJhbmdvQHN3b29sZS5jb20wHhcNMjAwNDA2 MTMzMjAwWhcNMjEwNDA2MTMzMjAwWjCBhjELMAkGA1UEBhMCY24xETAPBgNVBAgM CHNoYW5naGFpMREwDwYDVQQHDAhzaGFuZ2hhaTEPMA0GA1UECgwGc3dvb2xlMQ8w DQYDVQQLDAZzd29vbGUxDjAMBgNVBAMMBXJhbmdvMR8wHQYJKoZIhvcNAQkBFhBy YW5nb0Bzd29vbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA s6JM9QuOV4hQ7HlOBCp5aINATTFAYot2n/fwfWnRyrC9riUrLjdIXc3PdjBMZlyr Mq6ZameM7RDydl0yJTq4mMOMzk8QIydk4/YkWeNRtoKee7lBCS6AUVqh/PwoeuoA F3f4Mf6jICC1CEF3FECjHNZEBdA46jESAm/XvKjccevduXYUcVRBERRdst7Cd13A OmtHUE/tgRtTMWh9NxT5vOPOO+H7Ri9g3pEiGofxroOTDqxALxHRcj0k1UkH1J74 amvw+FY+twx9kBaj+f/JkaTflDjrrf/Stc11AmfjQCdZUnW62Banps9JqyqIsEdI BcTMYZq5EH0xBSoQDEr4cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAsk//RTiYF 5Fgfu5F1DTL3lNaG6JI78oKZTFx24sM7CE+wQRLUs5WEiDFkTSCInUbfA2tdRxjB Y4ye6fKGIL7K9yIN+3Y8ChYZBZ2xHjm4QcAKZ3TFwpxueM74sXZToaDQQooJ/5xD E1RTcA49waQ6zTFoG0aCeK1hjZ3ZtXaOCPLIP8yqRfkmezdKoU90LBRgXRyVR5jA mY+0v3Q6irZTpESY+e1RYHK5Yf8TesZ4J4LGRqSjWGP6GidpNc46wko3FOF2/KeB lN0jjSPx+rXCdA5hrfZIjNFjOLu/CSgOHzOflpSpYh8Jgfe9YBJRFFQI1PlcILGL 3JmkbOAiplbA -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ssl.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICzDCCAbQCAQAwgYYxCzAJBgNVBAYTAmNuMREwDwYDVQQIDAhzaGFuZ2hhaTER MA8GA1UEBwwIc2hhbmdoYWkxDzANBgNVBAoMBnN3b29sZTEPMA0GA1UECwwGc3dv b2xlMQ4wDAYDVQQDDAVyYW5nbzEfMB0GCSqGSIb3DQEJARYQcmFuZ29Ac3dvb2xl LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALOiTPULjleIUOx5 TgQqeWiDQE0xQGKLdp/38H1p0cqwva4lKy43SF3Nz3YwTGZcqzKumWpnjO0Q8nZd MiU6uJjDjM5PECMnZOP2JFnjUbaCnnu5QQkugFFaofz8KHrqABd3+DH+oyAgtQhB dxRAoxzWRAXQOOoxEgJv17yo3HHr3bl2FHFUQREUXbLewnddwDprR1BP7YEbUzFo fTcU+bzjzjvh+0YvYN6RIhqH8a6Dkw6sQC8R0XI9JNVJB9Se+Gpr8PhWPrcMfZAW o/n/yZGk35Q4663/0rXNdQJn40AnWVJ1utgWp6bPSasqiLBHSAXEzGGauRB9MQUq EAxK+HMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCxXN1YuqL06eAXPEUQNbTb BlZ/5r7yKpGlx1bOzdZYEveDZYTT3Mmhuy7GaX29j7TD1nxM+oVdMkN7ug/AjjY+ ybwUjWt3719kWfrIBteB0tZXqk+0pr9NDz6xxDtsDdpRxP4yhfxyZTSWX/1j2HQd a8W8YY5FRKJJWAYqM9zZY7+LIqBA9Tub0HPQS2O6tRfbiFJiKIiGkgOd7VrUXq+D B9y172aIRs704wSaBbq4Q4h6xzoRcqyVBp4wHNa3opiDPcJJyiu4zHCV+vV0nSVZ 7v9c5Cu5ttio6bHfOOWhZn64Z3wlYtQVIQ/Cj/fEujaFAvDDpdDU5Br46ZUHOmOP -----END CERTIFICATE REQUEST----- ================================================ FILE: examples/ssl/ssl.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAs6JM9QuOV4hQ7HlOBCp5aINATTFAYot2n/fwfWnRyrC9riUr LjdIXc3PdjBMZlyrMq6ZameM7RDydl0yJTq4mMOMzk8QIydk4/YkWeNRtoKee7lB CS6AUVqh/PwoeuoAF3f4Mf6jICC1CEF3FECjHNZEBdA46jESAm/XvKjccevduXYU cVRBERRdst7Cd13AOmtHUE/tgRtTMWh9NxT5vOPOO+H7Ri9g3pEiGofxroOTDqxA LxHRcj0k1UkH1J74amvw+FY+twx9kBaj+f/JkaTflDjrrf/Stc11AmfjQCdZUnW6 2Banps9JqyqIsEdIBcTMYZq5EH0xBSoQDEr4cwIDAQABAoIBAQCfwa8pm65rIJu/ w55jzBaPoXMVc8DsI1ZLrKkgEHV5CziWYQ4HGzlr23cviILZ4n6LI4bjf9Zwm8rX A8luHMxBaGCYtu4GJanvp1/q5WUtqPsj070IWmfacn1wTh3+Oo/8T0DXzbvr/cqi 3f32cAngwppahTWo6h03N5qpeZAca4ghy9VOdNEzLmDGkR6lkrgSibpJA92LVpFH yHsuWvnSm1kOe2GDVQv4GOKIKH5Fo5WEkTZOocXvM1C+NNQcEP+87ZIPjoyBrPwG Vlb1EkGHhNh0tDsNTIDuYWo/WxPsKqziczS/TB4QgR7cMePsH83e8fn5VrKSRU00 jqQEt43RAoGBANhCzG0ImvmjYSF8i53bPyuVAJfqpIMQY/BVYG82O1DVNjoK+2pU PwIEmX0QrltLDULxx9mV3SHzRmxnA280Y+GSIsvYqYBr7doO2cTAEP3Z+MzOiXEm bPAANIwt+iFp8OeMCbKh12Kr0WRhkF0oKFsLEsFT8/E/7eHwrDIF7A8pAoGBANSk hZ2K+4GblUEQ2vQgJc/RH9+W8NUMEX+Zv+mtEpCCEoErSjU5StcGrSMIgp2K+IGT yB/QdH9aYUM1iiBgXPr7IvWNF0IwxwYxJys0kagDoSDH7CdY+pANiEBgWHeHPPQW R+EBO9eJeEqyp/3nxouwQuhM/1UQXbJhOEmpyOo7AoGAUEqcs26ae0zZZyFihL09 +uRbUAviAHc5Y6WPJDsyUpSWiX5CUfvtWMZ0ZianVWXMCqrR1Er7yvsTjnPApq0A 2GL1YFGUvQVVTbxtxcq5hEZFRWT5SnVFxOWOnO0FDRrUQmOPxi6oVQ4NtAM1IiEi uoql7/lHfK6Ll3NPu1LPjeECgYB6mD/QjIhkOObcFHOnu8ERB2hw5/0BV2vfPS0/ 8+B4IAW7ItItzVIwllDLmeR0H+MWEbWXYf9ITTs2HG/+KpBwwsls+GMobibQkxYZ +rzfOPh0hc7JAKu00Zh0RRe8EeQ6dX4LY0K1CTSpHEduQ2pcrspkU8ABsuXSKerh zVlgcQKBgQCLCN9jSnIo5dT6N7QABjIEO9sMrnlVfoEzDxn76MwYQ6ZJxf2ONwhi YoOXguXxMfonleOxwAt8YbfYiT8DypryUM8cuSjB4GPJILn1W1XsNtEWBjFUeiqh C4MNUZis1dJrUjrieWNWp8rkGV2la1JOJYEq9SSQijeuPNFemdAvNQ== -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/ssl_passwd/ssl.crt ================================================ -----BEGIN CERTIFICATE----- MIICazCCAdQCCQDGckgNrF7jzDANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJD TjERMA8GA1UECAwIU2hhbmdIYWkxEDAOBgNVBAcMB01pbmhhbmcxDzANBgNVBAoM BmNoZWx1bjEUMBIGA1UEAwwLaGFudGlhbmZlbmcxHzAdBgkqhkiG9w0BCQEWEHJh bmdvQHN3b29sZS5jb20wHhcNMTcwNjIxMDg0NDIzWhcNMTgwNjIxMDg0NDIzWjB6 MQswCQYDVQQGEwJDTjERMA8GA1UECAwIU2hhbmdIYWkxEDAOBgNVBAcMB01pbmhh bmcxDzANBgNVBAoMBmNoZWx1bjEUMBIGA1UEAwwLaGFudGlhbmZlbmcxHzAdBgkq hkiG9w0BCQEWEHJhbmdvQHN3b29sZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A MIGJAoGBAM6DB6/k7gBq335W+46qJKOF2AJ8pJcyr8rwIculc2Guc/8Y7iAnv2gP NuZQWGtXuWNTgEkBc2vP2G2UbsHJRwR8fVgwFphZWmWJ1vomj9RZjm+v1ID04lIw 2d2oR4P2Nur7mw6Lxhpgx0y0DJ+4kW4/L/ObV13fJu7fNBKuprtzAgMBAAEwDQYJ KoZIhvcNAQELBQADgYEAPwHwRDG+PCToqybr9GZtz3oxM6ApvVGV0c24Clon8veY 57h78tJLVmjx/b6Y4alg4HnT+DDBRZT0hJnBhdtkZivGX7eOys9sUDLZxAWsqO7a bHzHkP0jVawHwqQv7WFoE83pvmUYzj2QbLMULj5PJ4LMlY3bMHYE07c0SVKeJFE= -----END CERTIFICATE----- ================================================ FILE: examples/ssl/ssl_passwd/ssl.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIBujCCASMCAQAwejELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5nSGFpMRAw DgYDVQQHDAdNaW5oYW5nMQ8wDQYDVQQKDAZjaGVsdW4xFDASBgNVBAMMC2hhbnRp YW5mZW5nMR8wHQYJKoZIhvcNAQkBFhByYW5nb0Bzd29vbGUuY29tMIGfMA0GCSqG SIb3DQEBAQUAA4GNADCBiQKBgQDOgwev5O4Aat9+VvuOqiSjhdgCfKSXMq/K8CHL pXNhrnP/GO4gJ79oDzbmUFhrV7ljU4BJAXNrz9htlG7ByUcEfH1YMBaYWVplidb6 Jo/UWY5vr9SA9OJSMNndqEeD9jbq+5sOi8YaYMdMtAyfuJFuPy/zm1dd3ybu3zQS rqa7cwIDAQABoAAwDQYJKoZIhvcNAQELBQADgYEASQqPl/+WGbjiFmejmI7cj5Te byHjUASEGc2VDRQhc1W7XRFjHFqyeKRh8xwFV7HDFbvVwsTos260s4PKGkjTD5hH XG2HKPevBQRzqV0dIJmdW1+gZ13QyIESnQyj9MzGkW4I6MDHXamCZmYAFel2MXtE Vsj7idZCTW/REX/7/z8= -----END CERTIFICATE REQUEST----- ================================================ FILE: examples/ssl/ssl_passwd/ssl.key ================================================ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,1B8CC4C05815AA64 i9kRuvTLN6Y329ctDI1Nt0oSRUXqRJo0omtGmLFPA7C8kRs8mwhvpo4G4m8Kh6pf l2fVpaKKVXeQxyTdfgPKPxvGsTODp3ZHODLuKswwj0ERPrjfzsDvKboEYdooC7FF 3oYpy33I6XtA3qytkqP/gCCoF9zNaFSlHUGup7ZjEZCO4rEYZmbqtW6CPn9lMNCd DgTl2oNa3t6wopQYRFg3uR8NUSeTjpy0fdWIU5GTWJAw7UEaHslnxeRJlwUHdoiY nG37EJpDgAiDspbHfWN3sfRnRDtL0gcvFy8yzSJr/mzlIdhmuxkAvlHFQ7dtav0+ VnaR32lNWWgJVUljnTsPPAAIpznVIaj1aXA32X/N12JWXkr2Pe64uSD0DPE8I9xM l/n2kaSBiiZR7MIXw1wrbLGwN55qZlc9hURlWEE6vlxEz2LFfuMX9wpN/OZtiPpA FdVz+0CojiLsjjBa1ayjinEW6ICPfOJFe8IhCEThF/O1bvkCuUK8edZBUd2JtChA RN1BuX+AwaUryvf93xrqNJ0xxK5mOICCUQi8lYCl/n9bpCJ4HCqfsUiqd4tLy8GO bXx22debalaoYIC3tBz26BY3HbZldDO7LQWvkxkAqK24T26NS/oLagWB8BmGHwwK vi6L++VHFEvfmHNmltmpkM/wjsFw6m5jg8t0ETlYffJTJdlb/1r48/R0U/1dh8Oe n5ymUXYs3KWGVKxZcC7nMzOwisyWI83tm3/Ifytbo1oX1Wk8UL0eQVPbcttAnKAO 543Fjs6tfhKUGiOSkvadYLOz08p3BlnXGLbbfdeOZ5b714guzdIHTg== -----END RSA PRIVATE KEY----- ================================================ FILE: examples/ssl/stream_client.php ================================================ [ 'verify_peer' => false, // 'allow_self_signed' => true, // 'cafile' => __DIR__.'/privkey.pem', 'peer_name' => 'example.com', ] ]; $context = stream_context_create($contextOptions); $fp = stream_socket_client("ssl://127.0.0.1:9501", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context); if (!$fp) { die("Unable to connect: $errstr ($errno)"); } stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); $ret = fwrite($fp, "hello\n"); var_dump($ret); $recv = fread($fp, 8192); var_dump($recv); echo "finish\n"; ================================================ FILE: examples/ssl/swoole.log ================================================ ================================================ FILE: examples/ssl/webserver.php ================================================ set([ 'ssl_cert_file' => __DIR__ . '/ssl.crt', 'ssl_key_file' => __DIR__ . '/ssl.key', //'ssl_method' => SWOOLE_TLSv1_2_SERVER_METHOD, 'worker_num' => 1, 'open_http2_protocol' => true, //'ssl_client_cert_file' => __DIR__ . '/ca.crt', //'ssl_verify_depth' => 10, ]); //c158354564362fcc $serv->on('Request', function(Swoole\Http\Request $request, Swoole\Http\Response $response) { //var_dump($request->get); //var_dump($request->post); //var_dump($request->cookie); //var_dump($request->files); // var_dump($request->header); // var_dump($request->server); //global $serv; //$info= $serv->getClientInfo($request->fd); // var_dump($info); //$response->cookie("User", "Swoole"); //$response->header("X-Server", "Swoole"); $response->end("

Hello Swoole!

\n"); }); $serv->start(); ================================================ FILE: examples/ssl/websocket_client.html ================================================ ================================================ FILE: examples/ssl/websocket_server.php ================================================ set([ 'ssl_cert_file' => $ssl_dir . '/ssl.crt', 'ssl_key_file' => $ssl_dir . '/ssl.key', 'worker_num' => 1, ]); $port = $serv->listen('127.0.0.1', 9501, SWOOLE_SOCK_TCP); $port->on('receive', function($serv, $fd, $reactor_id, $data){ var_dump($fd, $reactor_id, $data); $serv->send($fd, "Swoole: $data"); }); $serv->on('connect', function ($_server, $fd) { echo "client {$fd} connect\n"; }); $serv->on('open', function (Swoole\WebSocket\Server $_server, Swoole\Http\Request $request) { echo "server#{$_server->worker_pid}: handshake success with fd#{$request->fd}\n"; // var_dump($request); }); $serv->on('request', function ($req, $resp) { $resp->end(file_get_contents(__DIR__.'/websocket_client.html')); }); $serv->on('message', function (Swoole\WebSocket\Server $_server, $frame) { var_dump($frame->data); echo "received ".strlen($frame->data)." bytes\n"; $_send = str_repeat('B', rand(100, 800)); $_server->push($frame->fd, $_send); // echo "#$i\tserver sent " . strlen($_send) . " byte \n"; }); $serv->on('close', function ($_server, $fd) { echo "client {$fd} closed\n"; }); $serv->start(); ================================================ FILE: examples/stdext/array.php ================================================ slice(1, 2); var_dump($arr); $array1 = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]; $array2 = [6, 7, 8, 9, 10, 11, 12]; var_dump($array2->count()); // var_dump($array2->all(function ($value) { // return $value > 10; // })); $input_array = array("FirSt" => 1, "SecOnd" => 4); print_r($input_array->changeKeyCase(CASE_UPPER)); $array4 = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); $key = $array4->search('green'); var_dump($key); echo "==================================[contains]===================================\n"; $os = array("Mac", "Windows", "Linux"); var_dump($os->contains("Windows")); var_dump($os->contains("Unix")); echo "==================================[isList]===================================\n"; var_dump($array1->isList()); var_dump($array2->isList()); var_dump($array1->keys()); var_dump($array2->values()); echo "==================================[join]===================================\n"; var_dump(['a', 'b', 'c']->join(',')); echo "==================================[method not exists]===================================\n"; try { $array->notExists(); } catch (throwable $e) { echo "Caught exception: ", $e->getMessage(), "\n"; } function odd($var) { // returns whether the input integer is odd return $var & 1; } function even($var) { // returns whether the input integer is even return !($var & 1); } echo "Odd :\n"; print_r($array1->filter("odd")); echo "Even:\n"; print_r($array2->filter("even")); echo "==================================[array_map]===================================\n"; $a = [1, 2, 3, 4, 5]; $b = $a->map(function ($n) { return ($n * $n * $n); }); var_dump($b); echo "==================================[array_key_exists]===================================\n"; $searchArray = ['first' => null, 'second' => 4]; var_dump(isset($searchArray['first'])); var_dump($searchArray->keyExists('first')); ================================================ FILE: examples/stdext/foreach.php ================================================ '); $arr[] = 1; // $arr[0] += 10; // assert($arr[0] == 11); $arr[0] .= "hello world"; ================================================ FILE: examples/stdext/ref.php ================================================ $val) { echo "fruits[" . $key . "] = " . $val . "\n"; } $b = &$fruits; $b->sort(SORT_NATURAL | SORT_FLAG_CASE); echo "After sorting:\n"; foreach ($fruits as $key => $val) { echo "fruits[" . $key . "] = " . $val . "\n"; } $stack = array("orange", "banana", "apple", "raspberry"); $ref = &$stack; $fruit = $ref->shift(); var_dump($stack); $ref->unshift("kiwi"); var_dump($stack); ================================================ FILE: examples/stdext/string.php ================================================ upper()); var_dump($str->split(' ')->search('world')); var_dump($str->length()); var_dump('test'->length()); var_dump($str->indexOf('world')); var_dump($str->substr(1, 4)); var_dump($str->startsWith('hello')); var_dump($str->endsWith('world')); var_dump($str->endsWith('.php')); var_dump($str->md5(), $str->sha1(), $str->crc32()); var_dump($str->hash('sha256')); echo "==============================hash=====================\n"; var_dump($str->md5() === $str->hash('md5')); $str = 'first=value&arr[]=foo+bar&arr[]=baz'; $output = $str->parseStr(); echo $output['first']; // value echo $output['arr'][0]; // foo bar echo $output['arr'][1]; // baz var_dump($str->urlEncode()); ================================================ FILE: examples/stdext/typed_array.php ================================================ '); $list[] = 123; $list[] = 345; $list[] = 'hello'; // 异常 ================================================ FILE: examples/stdext/typed_array_map.php ================================================ '); $list["hello"] = 123; $list[] = 345; $list["hello"] = 'hello'; ================================================ FILE: examples/table/deadlock.php ================================================ column('name', Swoole\Table::TYPE_STRING, 1024 * 64); $table->create(); $table->set('key1', ['name' => str_repeat('A', 1024 * 64 - 1) . "\n"]); if (pcntl_fork() == 0) { sleep(1); $r = $table->get('key1'); var_dump(strlen($r['name'])); } else { $mu1 = memory_get_usage(); var_dump($mu1); $str = str_repeat('A', 1024 * 1024 * 5); $str2 = str_repeat('A', 1024 * 1024); $str3 = str_repeat('A', 1024 * 64); var_dump(memory_get_usage()); $r = $table->get('key1'); var_dump(strlen($r['name'])); echo substr($str, 0, 8); pcntl_wait($status); } ================================================ FILE: examples/table/iterator.php ================================================ column('name', Swoole\Table::TYPE_STRING, 64); $table->column('id', Swoole\Table::TYPE_INT, 4); //1,2,4,8 $table->column('num', Swoole\Table::TYPE_FLOAT); $table->create(); $table->set('tianfenghan@qq.com', array('id' => 145, 'name' => 'rango1', 'num' => 3.1415)); $table->set('350749960@qq.com', array('id' => 358, 'name' => "Rango2", 'num' => 3.1415)); $table->set('hello@qq.com', array('id' => 189, 'name' => 'rango3', 'num' => 3.1415)); var_dump($table->get('350749960@qq.com')); var_dump($table->get('350749960@qq.com', 'name')); foreach($table as $key => $value) { var_dump($key, $value); } echo "======================= Total Elements: {$table->count()} ============================\n"; $table->del('350749960@qq.com'); // delete a exist element foreach($table as $key => $value) { var_dump($key, $value); } echo "======================= Total Elements: {$table->count()} ============================\n"; $ret = $table->del('a invalid key'); // delete a invalid element var_dump($ret); foreach($table as $key => $value) { var_dump($key, $value); } echo "======================= Total Elements: {$table->count()} ============================\n"; ================================================ FILE: examples/table/server.php ================================================ column('fd', Swoole\Table::TYPE_INT); $table->column('reactor_id', Swoole\Table::TYPE_INT); $table->column('data', Swoole\Table::TYPE_STRING, 64); $table->create(); $serv = new Swoole\Server('127.0.0.1', 9501); $serv->set(['dispatch_mode' => 2]); $serv->table = $table; $serv->on('connect', function($serv, $fd, $reactor_id){ $info = $serv->connection_info($fd); $serv->send($fd, "INFO: fd=$fd, reactor_id=$reactor_id, addr={$info['remote_ip']}:{$info['remote_port']}\n"); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { $cmd = explode(" ", trim($data)); //get if ($cmd[0] == 'get') { //get self if (count($cmd) < 2) { $cmd[1] = $fd; } $get_fd = intval($cmd[1]); $info = $serv->table->get($get_fd); $serv->send($fd, var_export($info, true)."\n"); } //set elseif ($cmd[0] == 'set') { $ret = $serv->table->set($fd, array('reactor_id' => $data, 'fd' => $fd, 'data' => $cmd[1])); if ($ret === false) { $serv->send($fd, "ERROR\n"); } else { $serv->send($fd, "OK\n"); } } else { $serv->send($fd, "command error.\n"); } }); $serv->start(); ================================================ FILE: examples/table/set.php ================================================ column('id', Swoole\Table::TYPE_INT, 4); //1,2,4,8 $table->column('name', Swoole\Table::TYPE_STRING, 64); $table->column('num', Swoole\Table::TYPE_FLOAT); $table->create(); //$worker = new Swoole\Process('child1', false, false); //$worker->start(); // //child function child1($worker) { global $table; $s = microtime(true); $table->set('tianfenghan@qq.com', array('id' => 145, 'name' => 'rango', 'num' => 3.1415)); $table->set('350749960@qq.com', array('id' => 358, 'name' => "Rango1234", 'num' => 3.1415)); $table->set('hello@qq.com', array('id' => 189, 'name' => 'rango3', 'num' => 3.1415)); $table->set('tianfenghan@qq.com', array('id' => 145, 'name' => 'rango', 'num' => 3.1415)); $table->set('350749960@qq.com', array('id' => 358, 'name' => "Rango1234", 'num' => 3.1415)); echo "set - 5 use: ".((microtime(true) - $s) * 1000)."ms\n"; } //master sleep(1); child1(1245); $s = microtime(true); for($i =0; $i < 1000; $i++) { $arr = $table->get('350749960@qq.com'); } echo "get -5 use: ".((microtime(true) - $s) * 1000)."ms\n"; $s = microtime(true); //$table->incr('tianfenghan@qq.com', 'id', 5); //$table->decr('hello@qq.com', 'num', 1.1); $ret1 = $table->get('350749960@qq.com'); $ret2 = $table->get('tianfenghan@qq.com'); $ret3 = $table->get('350749960@qq.com'); $ret4 = $table->get('tianfenghan@qq.com'); $ret5 = $table->get('hello@qq.com'); echo "get -5 use: ".((microtime(true) - $s) * 1000)."ms\n"; var_dump($ret1, $ret2, $ret3, $ret4, $ret5); echo "id:".$ret1['id']."\n"; echo "name:".$ret1['name']."\n"; echo "num:".$ret1['num']."\n"; ================================================ FILE: examples/table/simulation.php ================================================ column('name', Swoole\Table::TYPE_STRING, 64); $table->column('id', Swoole\Table::TYPE_INT, 4); //1,2,4,8 $table->column('num', Swoole\Table::TYPE_FLOAT); $table->create(); while (true) { $i = rand(1, 1000); $if = rand(0,1); if ($if) { $table->set($i, ['id' => $i, 'name' => $i, 'num' => $i]); } else { $table->del($i); } var_dump('count ' . $table->count()); } ================================================ FILE: examples/table/usage.php ================================================ column('id', Swoole\Table::TYPE_INT); $table->column('name', Swoole\Table::TYPE_STRING, 64); $table->column('num', Swoole\Table::TYPE_FLOAT); $table->create(); $table->set('a', array('id' => 1, 'name' => 'swoole-co-uk', 'num' => 3.1415)); $table->set('b', array('id' => 2, 'name' => "swoole-uk", 'num' => 3.1415)); $table->set('hello@swoole.co.uk', array('id' => 3, 'name' => 'swoole', 'num' => 3.1415)); var_dump($table->get('a')); var_dump($table->get('b', 'name')); ================================================ FILE: examples/task/http.php ================================================ set(array( 'worker_num' => 1, 'task_worker_num' => 1, // 'task_ipc_mode' => 3, // 'message_queue_key' => 0x70001001, //'task_tmpdir' => '/data/task/', )); $serv->on('Request', function ($req, $resp) { $data = str_repeat('A', 8192 * 10); global $serv; $serv->task(array($data, 1000), -1, function ($serv, $task_id, $data) use ($resp) { $resp->end("Task#$task_id finished." . PHP_EOL); }); }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { //echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; // $serv->finish($data); return $data; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('workerStart', function($serv, $worker_id) { global $argv; if ($serv->taskworker) { swoole_set_process_name("php {$argv[0]}: task_worker"); } else { swoole_set_process_name("php {$argv[0]}: worker"); } }); $serv->on('workerStop', function (Swoole\Server $serv, $id) { echo "stop\n"; var_dump($id); }); $serv->start(); ================================================ FILE: examples/task/msg_push.php ================================================ queueId = msg_get_queue($key); if ($this->queueId === false) { throw new \Swoole\Exception("msg_get_queue() failed."); } $this->workerId = $workerId; } function dispatch($data) { if (!msg_send($this->queueId, $this->workerId + 1, Swoole\Server\Task::pack($data), false)) { return false; } else { return true; } } } $task = new SwooleTask(0x70001001, 0); //普通字符串 $task->dispatch("Hello from PHP!"); //数组 $task->dispatch(array('data' => str_repeat('A', 1024), 'type' => 1)); //大包 $task->dispatch(array('data' => str_repeat('B', 1024 * 32), 'type' => 2)); ================================================ FILE: examples/task/shared_client.php ================================================ connect('127.0.0.1', 9501)) { exit("connect failed\n"); } function help() { echo "get eg: php ".__FILE__." get key".PHP_EOL; echo "set eg: php ".__FILE__." set key value".PHP_EOL; echo "del eg: php ".__FILE__." del key".PHP_EOL; echo "task eg: php ".__FILE__." task key".PHP_EOL; exit(); } if($argc < 3) { help(); } $keys = array( 1 => 'cmd', 2 => 'key', 3 => 'val' ); $sends = array(); foreach ($keys as $i => $key) { if (isset($argv[$i])) { $sends[$key] = $argv[$i]; } } if (empty($sends)) { help(); } $client->send(serialize($sends)); $data = $client->recv(); echo $data . PHP_EOL; ================================================ FILE: examples/task/shared_server.php ================================================ set(array( 'worker_num' => 1, //'open_eof_check' => true, //'package_eof' => "\r\n", 'task_worker_num' => 1, //'dispatch_mode' => 2, //'daemonize' => 1, //'heartbeat_idle_time' => 5, //'heartbeat_check_interval' => 5, )); function my_onStart($serv) { echo "MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}\n"; echo "Server: start.Swoole version is [".SWOOLE_VERSION."]\n"; //$serv->addtimer(1000); } function my_onShutdown($serv) { echo "Server: onShutdown\n"; } function my_onClose($serv, $fd, $reactor_id) { //echo "Client: fd=$fd is closed.\n"; } function my_onConnect($serv, $fd, $reactor_id) { //throw new Exception("hello world"); // echo "Client:Connect.\n"; } function my_onWorkerStart($serv, $worker_id) { global $argv; if ($worker_id >= $serv->setting['worker_num']) { swoole_set_process_name("php {$argv[0]} task worker"); } else { swoole_set_process_name("php {$argv[0]} event worker"); } //echo "WorkerStart|MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}|WorkerId=$worker_id\n"; //$serv->addtimer(500); //500ms } function my_onWorkerStop($serv, $worker_id) { echo "WorkerStop[$worker_id]|pid=".posix_getpid().".\n"; } function my_onReceive(Swoole\Server $serv, $fd, $reactor_id, $rdata) { $data = unserialize($rdata); if (isset($data['cmd'])) { switch ($data['cmd']) { case 'get': $s = microtime(true); $res = $serv->taskwait($data, 0.5, 0); echo "use " . ((microtime(true) - $s) * 1000) . "ms\n"; $serv->send($fd, PHP_EOL . "get " . $res['key'] . ": " . $res['val']); break; case "set": $serv->task($data, 0); $serv->send($fd, "OK\n"); break; case "del": $serv->task($data, 0); break; case "reload": break; default: echo "server:" . $data . PHP_EOL; } } } function my_onTask(Swoole\Server $serv, $task_id, $reactor_id, $data) { static $datas = array(); if (isset($data['cmd'])) { switch ($data['cmd']) { case 'get': $key = $data['key']; $val = isset($datas[$key]) ? $datas[$key] : ""; $serv->finish(array('key'=>$key, 'val' => $val)); break; case "set": $key = $data['key']; $val = $data['val']."_".$reactor_id; $datas[$key] = $val; return; break; case "del": $key = $data['key']; if(isset($datas[$key])) { unset($datas[$key]); } break; case "task": $key = $data['key']; echo "Do task " . $key . PHP_EOL; break; } } echo "AsyncTask[PID=".posix_getpid()."]: task_id=$task_id.".PHP_EOL; // $serv->finish("OK"); } function my_onFinish(Swoole\Server $serv, $task_id, $from_worker_id, $data) { echo "AsyncTask Finish: Connect.PID=" . posix_getpid() . PHP_EOL; } function my_onWorkerError(Swoole\Server $serv, $worker_id, $worker_pid, $exit_code) { echo "worker abnormal exit. WorkerId=$worker_id|Pid=$worker_pid|ExitCode=$exit_code\n"; } $serv->on('Start', 'my_onStart'); $serv->on('Connect', 'my_onConnect'); $serv->on('Receive', 'my_onReceive'); $serv->on('Close', 'my_onClose'); $serv->on('Shutdown', 'my_onShutdown'); $serv->on('WorkerStart', 'my_onWorkerStart'); $serv->on('WorkerStop', 'my_onWorkerStop'); $serv->on('Task', 'my_onTask'); $serv->on('Finish', 'my_onFinish'); $serv->on('WorkerError', 'my_onWorkerError'); $serv->start(); ================================================ FILE: examples/task/task.php ================================================ set(array( //'worker_num' => 1, 'task_worker_num' => 1, // 'task_ipc_mode' => 3, // 'message_queue_key' => 0x70001001, //'task_tmpdir' => '/data/task/', )); $serv->on('Receive', function(Swoole\Server $serv, $fd, $reactor_id, $data) { //AsyncTask $data = trim($data); //$data = str_repeat('A', 8192*100); // if ($data == 'async') if(false) // if (true) { $task_id = $serv->task($data, 0); $serv->send($fd, "Dispath AsyncTask: id=$task_id\n"); } //Sync Task else { $res = $serv->taskwait($data, 10); echo "Dispath SyncTask: result=".$res.PHP_EOL; } //$serv->send($fd, "OK\n"); }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; $serv->finish($data); // return $data; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('workerStart', function($serv, $worker_id) { global $argv; if($worker_id >= $serv->setting['worker_num']) { swoole_set_process_name("php {$argv[0]}: task_worker"); } else { swoole_set_process_name("php {$argv[0]}: worker"); } }); $serv->start(); ================================================ FILE: examples/task/task_coro.php ================================================ set(array( 'worker_num' => 1, 'task_worker_num' => 4, //'task_tmpdir' => '/data/task/', )); $serv->on('Receive', function(Swoole\Server $serv, $fd, $reactor_id, $data) { $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); //等待所有Task结果返回,超时为10s var_dump($tasks); $results = $serv->taskWaitMulti($tasks, 2); var_dump($results); }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "onTask: [ID={$serv->worker_id}]: task_id=$task_id, data=$data, data_len=".strlen($data).".".PHP_EOL; //测试超时 if ($serv->worker_id % 4 == 3) { sleep(3); } elseif ($serv->worker_id % 4 == 2) { usleep(1500000); } elseif ($serv->worker_id % 4 == 1) { usleep(200000); } return "hello world.[{$data}]"; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->start(); ================================================ FILE: examples/task/task_num.php ================================================ set(array( 'worker_num' => 1, 'task_worker_num' => 2, //'task_tmpdir' => '/data/task/', )); $serv->on('Receive', function(Swoole\Server $serv, $fd, $reactor_id, $data) { //AsyncTask $data = intval($data); for($i=0;$i<$data;$i++) { $tid = mt_rand(0,1); echo "data:{$i} to task: {$tid} ".PHP_EOL; $serv->task($i, $tid); } }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "onTask: [PID=".posix_getpid()."]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; sleep(10); //$serv->finish($data); echo 'finish'.PHP_EOL; return; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('Timer', function(Swoole\Server $serv, $time) { echo "{$time} call".PHP_EOL; print_r($serv->stats()); }); $serv->on('workerStart', function($serv, $worker_id) { global $argv; if($worker_id >= $serv->setting['worker_num']) { swoole_set_process_name("php {$argv[0]}: task_worker"); } else { $serv->addtimer(5000); swoole_set_process_name("php {$argv[0]}: worker"); } }); $serv->start(); ================================================ FILE: examples/task/task_queue.php ================================================ set(array( //'worker_num' => 1, 'task_worker_num' => 4, 'task_ipc_mode' => 3, 'message_queue_key' => 0x70001001, 'task_tmpdir' => '/data/task/', )); $serv->on('Receive', function(Swoole\Server $serv, $fd, $reactor_id, $data) { }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: TASK_ID=$task_id]\n"; var_dump($data); }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('workerStart', function($serv, $worker_id) { global $argv; if($worker_id >= $serv->setting['worker_num']) { swoole_set_process_name("php {$argv[0]}: task_worker"); } else { swoole_set_process_name("php {$argv[0]}: worker"); } }); $serv->start(); ================================================ FILE: examples/task/task_stream.php ================================================ set(array( 'worker_num' => 1, 'task_worker_num' => 1, 'task_ipc_mode' => 4, // 'message_queue_key' => 0x70001001, )); $serv->on('Receive', function(Swoole\Server $serv, $fd, $reactor_id, $data) { //AsyncTask $data = trim($data); //$data = str_repeat('A', 8192*100); // if ($data == 'async') // if(false) if (true) { $task_id = $serv->task($data, 0); $serv->send($fd, "Dispath AsyncTask: id=$task_id\n"); } //Sync Task else { $res = $serv->taskwait($data, 10); echo "Dispath SyncTask: result=".$res.PHP_EOL; } //$serv->send($fd, "OK\n"); }); $serv->on('Task', function (Swoole\Server $serv, $task_id, $reactor_id, $data) { echo "#{$serv->worker_id}\tonTask: [PID={$serv->worker_pid}]: task_id=$task_id, data_len=".strlen($data).".".PHP_EOL; $serv->finish($data); return $data; }); $serv->on('Finish', function (Swoole\Server $serv, $task_id, $data) { echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL; }); $serv->on('workerStart', function($serv, $worker_id) { global $argv; if ($serv->taskworker) { swoole_set_process_name("php {$argv[0]}: task_worker"); } else { swoole_set_process_name("php {$argv[0]}: worker"); } echo "Worker#$worker_id, pid=".posix_getpid()." start".PHP_EOL; }); $serv->start(); ================================================ FILE: examples/thread/aio.php ================================================ join(); } var_dump($atomic->get()); sleep(2); Co\run(function () use($atomic) { $n = 1024; while ($n--) { $atomic->add(); $rs = \Swoole\Coroutine\System::readFile(__FILE__); var_dump(strlen($rs)); } }); var_dump($atomic->get()); } else { $atomic = $args[1]; Co\run(function () use($atomic) { $n = 1024; while ($n--) { $atomic->add(); $rs = \Swoole\Coroutine\System::readFile(__FILE__); var_dump(strlen($rs)); } }); } ================================================ FILE: examples/thread/argv.php ================================================ join(); } } else { var_dump($args[0], $args[1], $args[2]); sleep(1); } ================================================ FILE: examples/thread/array.php ================================================ join(); } var_dump($a1->get(), $a2->get()); } else { $a1 = $args[1]; $a2 = $args[2]; $a1->add(3); $a2->add(7); } ================================================ FILE: examples/thread/benchmark.php ================================================ id); //var_dump($t2->id); echo Swoole\Thread::getId() . "\t" . 'gmap[uuid]' . "\t" . $map['uuid'] . "\n"; try { var_dump($list[999]); } catch (Swoole\Exception $e) { assert(str_contains($e->getMessage(), 'out of range')); } try { unset($list[0]); } catch (Swoole\Exception $e) { assert(str_contains($e->getMessage(), 'unsupported')); } $t1->join(); $t2->join(); ================================================ FILE: examples/thread/exit.php ================================================ lock(); $thread = new Thread(__FILE__, $lock); echo "main thread\n"; $lock->unlock(); $thread->join(); var_dump($thread->getExitStatus()); } else { $lock = $args[0]; $lock->lock(); sleep(1); exit(234); } ================================================ FILE: examples/thread/hook.php ================================================ lock(); $thread = new Thread(__FILE__, $lock); echo "main thread\n"; $lock->unlock(); $thread->join(); var_dump($thread->getExitStatus()); } else { $lock = $args[0]; $lock->lock(); Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL); sleep(1); Swoole\Runtime::enableCoroutine(0); exit(234); } ================================================ FILE: examples/thread/lock.php ================================================ lock(); $thread = new Thread(__FILE__, $lock); $lock->lock(); echo "main thread\n"; $thread->join(); } else { $lock = $args[0]; sleep(1); $lock->unlock(); } ================================================ FILE: examples/thread/map.php ================================================ random_int(1, 999999999999999999), 'b' => random_bytes(128), 'c' => uniqid(), 'd' => time(), ]; $map = new Thread\Map($array); $thread = new Thread(__FILE__, $map); } else { $map = $args[0]; var_dump($map->toArray()); } ================================================ FILE: examples/thread/mt.php ================================================ keys()); $list[] = uniqid('swoole'); $list[count($list)] = uniqid('php'); var_dump($args); echo Swoole\Thread::getId() . "\t" . 'glist[0]' . "\t" . $list[0] . "\n"; var_dump(count($list)); //if ($args[0] == 'thread-2') { // $t3 = new Swoole\Thread('mt.php', 'thread-3', PHP_OS); // $t3->join(); //} //sleep(5); //echo "end\n"; ================================================ FILE: examples/thread/nested_map.php ================================================ uniqid(), 'b' => random_int(1000, 9999), ]; var_dump($map['map1']['key1']); var_dump($map['list1'][0]); var_dump($map['list1']->toArray()); var_dump($map['map2']); ================================================ FILE: examples/thread/pipe.php ================================================ recv(8192), PHP_EOL; $thread->join(); }); } else { $sockets = $args[0]; Co\run(function () use ($sockets) { sleep(1); $sockets[1]->send(uniqid()); }); } ================================================ FILE: examples/thread/run_test.php ================================================ join(); } ================================================ FILE: examples/thread/server.php ================================================ join(); } } else { $http = new Swoole\Http\Server("0.0.0.0", 9503); $http->on('request', function ($req, Swoole\Http\Response $resp) { $resp->end('hello world'); }); $http->start(); } ================================================ FILE: examples/thread/signal.php ================================================ send('exit'); } Co\go(function () use ($parent_pipe, $thread) { // 从管道中读取子线程退出的信息 echo $parent_pipe->recv(8192), PHP_EOL; // 回收子线程 $thread->join(); }); }); } else { echo "child thread\n"; $sockets = $args[0]; $child_pipe = $sockets[0]; Co\run(function () use ($child_pipe) { // 收到父线程的指令,开始退出 echo $child_pipe->recv(8192), PHP_EOL; // 通知父线程已退出 $child_pipe->send('child exit'); }); } ================================================ FILE: examples/thread/socket.php ================================================ join(); } else { $map = $args[0]; $sock = $map['socket']; $retval = socket_connect($sock, '127.0.0.1', 80); } ================================================ FILE: examples/thread/test.php ================================================ uuid = uniqid(); $map['obj'] = $o; var_dump($map['obj']); $s = serialize($map); var_dump(unserialize($s)); ================================================ FILE: examples/thread/thread_pool.php ================================================ push(base64_encode(random_bytes(16)), Queue::NOTIFY_ONE); usleep(random_int(10000, 100000)); } $n = 4; while ($n--) { $queue->push('', Queue::NOTIFY_ONE); } for ($i = 0; $i < $c; $i++) { $threads[$i]->join(); } var_dump($queue->count()); } else { $queue = $args[1]; while (1) { $job = $queue->pop(-1); if (!$job) { break; } var_dump($job); } } ================================================ FILE: examples/thread/thread_server.php ================================================ set([ 'worker_num' => 2, 'task_worker_num' => 3, 'enable_coroutine' => true, 'hook_flags' => SWOOLE_HOOK_ALL, // 'trace_flags' => SWOOLE_TRACE_SERVER, // 'log_level' => SWOOLE_LOG_TRACE, 'init_arguments' => function () use ($http) { $map = new Swoole\Thread\Map; return [$map]; } ]); $http->on('Request', function ($req, $resp) use ($http) { // $resp->end("tid=" . \Swoole\Thread::getId() . ', fd=' . $req->fd); if ($req->server['request_uri'] == '/task') { $http->task(['code' => uniqid()]); } elseif ($req->server['request_uri'] == '/stop') { var_dump($http->getWorkerId()); var_dump($req->get['worker_id']); $http->stop($req->get['worker_id'] ?? 0); } elseif ($req->server['request_uri'] == '/msg') { $dstWorkerId = random_int(0, 4); if ($dstWorkerId != $http->getWorkerId()) { $http->sendMessage('hello ' . base64_encode(random_bytes(16)), $dstWorkerId); echo "[worker#" . $http->getWorkerId() . "]\tsend pipe message to " . $dstWorkerId . "\n"; } } $resp->end('hello world'); }); $http->on('pipeMessage', function ($http, $srcWorkerId, $msg) { echo "[worker#" . $http->getWorkerId() . "]\treceived pipe message[$msg] from " . $srcWorkerId . "\n"; }); $http->addProcess(new \Swoole\Process(function () { echo "user process, id=" . \Swoole\Thread::getId() . "\n"; sleep(2); })); $http->on('Task', function ($server, $taskId, $srcWorkerId, $data) { var_dump($taskId, $srcWorkerId, $data); return ['result' => uniqid()]; }); $http->on('Finish', function ($server, $taskId, $data) { var_dump($taskId, $data); }); $http->on('workerStart', function ($serv, $worker_id) { echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is started.\n"; }); $http->on('workerStop', function ($serv, $worker_id) { echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is stopped.\n"; }); $http->on('workerExit', function (Server $serv, $worker_id) { echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is exited, event_num=" . Swoole\Coroutine::stats()['event_num'] . ".\n"; }); $http->start(); ================================================ FILE: examples/timer/after.php ================================================ false ]); Swoole\Timer::tick(1000, function () { $uid = Co::getuid(); assert($uid === -1); echo "#{$uid}\n"; }); ================================================ FILE: examples/timer/tick.php ================================================ 0]); // php -d swoole.blocking_detection=on -d swoole.blocking_threshold=500000 blocking.php function sleep_test() { echo "Start\n"; sleep(1); echo "End\n"; } function redis_test() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $result = $redis->blPop('queue_name', 1.5); if ($result) { list($queueName, $value) = $result; echo "获取到数据: {$value}\n"; } } function main() { sleep_test(); redis_test(); } Co\run(function () { main(); }); ================================================ FILE: examples/tracer/cli.php ================================================ arr = []; $this->str = ''; } } function foo(ClassA $obj) { $str = str_repeat("big string", 1024); $obj->arr[] = $str; $obj->str .= $str; } $obj = new ClassA(); $usage = memory_get_usage(); $n = 100; while ($n--) { foo($obj); } var_dump(strlen($obj->str)); var_dump(memory_get_usage() - $usage); swoole_tracer_leak_detect(); ================================================ FILE: examples/tracer/co.php ================================================ __DIR__]); function sleep_n($time) { Co::sleep($time); } Co\run(function () { Co\go(function () { sleep_n(0.1); }); Co\go(function () { sleep_n(0.2); }); sleep_n(0.3); }); swoole_tracer_prof_end('./test.json'); ================================================ FILE: examples/tracer/co_server.php ================================================ set(array( // 'worker_num' => 1, //)); //$http->on('request', function ($request, $response) { //startMemleakCheck(); // static $i=0; // global $arr; // $arr[] = $i++; // print_r($arr); //endMemleakCheck(); // $response->end("

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

"); //}); //$http->start(); // // // // //class Test class Test { public $arr = []; //问题的根源是run函数无法结束,所以run的局部变量和此函数所属的对象也应该是全局变量 function run() { $locals = ''; $this->run2($locals); } function run2(&$locals) { global $global1, $global2; $http = new \Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE); $http->set([ 'worker_num' => 1 ]); $http->on("start", function ($server) { }); $http->on("request", function ($req, $resp) use (&$global1, &$global2, &$locals) { $global2 .= "2222222222"; $locals .= "333333333333"; $global1[] = random_bytes(random_int(256, 4096)); $this->arr[] = "444444444"; // var_dump($global1, $global2, $run2var, $this->pro); $resp->end("hello world"); swoole_tracer_leak_detect(128); }); $http->start(); } } (new Test())->run(); ================================================ FILE: examples/tracer/co_socket.php ================================================ bind("0.0.0.0", 9501); $socket->listen(); go(function() use($socket) { while (1) { go(function() use ($socket) { $client = $socket->accept(-1); while (true) { $data = $client->recv(); if (empty($data)) { $client->close(); break; } //do business $client->send("server" . $data); } }); } }); ================================================ FILE: examples/tracer/fn_call.php ================================================ method_test(); call_user_func('test4'); } function test4() { usleep(random_int(100, 500) * 1000); var_dump(time()); } class T { function method_test() { usleep(random_int(100, 500) * 1000); var_dump(__METHOD__); } } swoole_tracer_prof_begin(['root_path' => __DIR__]); main(); var_dump(swoole_tracer_prof_end('./test.json')); ================================================ FILE: examples/tracer/gc.php ================================================ pro = $obj; unset($obj); swoole_tracer_leak_detect(64); } var_dump(memory_get_usage()); } foo(); ================================================ FILE: examples/udp/client.php ================================================ connect('127.0.0.1', 9905); $client->send(serialize(['hello' => str_repeat('A', 600), 'rand' => rand(1, 100)])); echo $client->recv() . "\n"; sleep(1); ================================================ FILE: examples/udp/server.php ================================================ listen('0.0.0.0', 9906 + $i, SWOOLE_SOCK_UDP); } $server->set(['worker_num' => 4]); $server->on('Packet', function (Swoole\Server $serv, $data, $addr) { $serv->sendto($addr['address'], $addr['port'], "Swoole: $data", $addr['server_socket']); }); $server->start(); ================================================ FILE: examples/unixsock/dgram_client.php ================================================ connect(__DIR__ . '/svr.sock', 0, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } $client->send("hello world\n"); echo $client->recv(); $client->close(); sleep(1); ================================================ FILE: examples/unixsock/dgram_server.php ================================================ set(array( //'tcp_defer_accept' => 5, 'worker_num' => 1, //'daemonize' => true, //'log_file' => '/tmp/swoole.log' )); //$serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { // echo "[#".posix_getpid()."]\tClient[$fd]: $data\n"; // $serv->send($fd, json_encode(array("hello" => $data, "from" => $reactor_id)).PHP_EOL); //}); $serv->on('Packet', function (Swoole\Server $serv, $data, $addr) { //echo "[#".posix_getpid()."]\tClient[{$addr['address']}]: $data\n"; var_dump($addr); $serv->send($addr['address'], json_encode(array("hello" => $data, "addr" => $addr)).PHP_EOL); }); $serv->start(); ================================================ FILE: examples/unixsock/stream_client.php ================================================ connect(__DIR__.'/svr.sock', 0, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } $client->send("hello world\n"); echo $client->recv(); $client->close(); sleep(1); ================================================ FILE: examples/unixsock/stream_server.php ================================================ set(array( //'tcp_defer_accept' => 5, 'worker_num' => 1, //'daemonize' => true, //'log_file' => '/tmp/swoole.log' )); $serv->on('start', function($serv){ chmod($serv->host, 0777); }); $serv->on('Connect', function($serv, $fd, $reactorId) { echo "Connect, client={$fd}\n"; }); $serv->on('Close', function($serv, $fd, $reactorId) { echo "Close, client={$fd}\n"; }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo "[#" . posix_getpid() . "]\tClient[$fd]: $data\n"; $serv->send($fd, json_encode(array("hello" => $data, "from" => $reactor_id)) . PHP_EOL); }); $serv->start(); ================================================ FILE: examples/websocket/client.html ================================================ ================================================ FILE: examples/websocket/client.php ================================================ connect(); //echo $data; $data = "data"; if (!empty($size)) { $data = str_repeat("A", $size * 1024); } for ($i = 0; $i < $count; $i++) { $client->send("hello swoole, number:" . $i . " data:" . $data); $recvData = ""; //while(1) { $tmp = $client->recv(); if (empty($tmp)) { break; } $recvData .= $tmp; //} echo $recvData . "size:" . strlen($recvData) . PHP_EOL; } echo PHP_EOL . "======" . PHP_EOL; sleep(1); echo 'finish' . PHP_EOL; ================================================ FILE: examples/websocket/server.php ================================================ addlistener('0.0.0.0', 9502, SWOOLE_SOCK_UDP); $server->set([ // 'worker_num' => 4, // 'task_worker_num' => 4, 'websocket_compression' => true, ]); function user_handshake(Request $request, Response $response) { //自定定握手规则,没有设置则用系统内置的(只支持version:13的) if (!isset($request->header['sec-websocket-key'])) { //'Bad protocol implementation: it is not RFC6455.' $response->end(); return false; } if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key']) || 16 !== strlen(base64_decode($request->header['sec-websocket-key'])) ) { //Header Sec-WebSocket-Key is illegal; $response->end(); return false; } $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); $headers = array( 'Upgrade' => 'websocket', 'Connection' => 'Upgrade', 'Sec-WebSocket-Accept' => $key, 'Sec-WebSocket-Version' => '13', 'KeepAlive' => 'off', ); foreach ($headers as $key => $val) { $response->header($key, $val); } $response->status(101); $response->end(); global $server; $fd = $request->fd; $server->defer(function () use ($fd, $server) { $server->push($fd, "hello, welcome\n"); }); return true; } //$server->on('handshake', 'user_handshake'); $server->on('open', function (Server $_server, Request $request) { echo "server#{$_server->worker_pid}: handshake success with fd#{$request->fd}\n"; var_dump($_server->exist($request->fd), $_server->getClientInfo($request->fd)); $fd = $request->fd; // $_server->tick(2000, function($id) use ($fd, $_server) { // $_send = str_repeat('B', rand(100, 5000)); // $ret = $_server->push($fd, $_send); // if (!$ret) // { // var_dump($id); // var_dump($_server->clearTimer($id)); // } // }); }); $server->on('message', function (Server $_server, $frame) { //var_dump($frame->data); echo "received " . strlen($frame->data) . " bytes\n"; if ($frame->data == "close") { $_server->close($frame->fd); } elseif ($frame->data == "task") { $_server->task(['go' => 'die']); } else { //echo "receive from {$frame->fd}:{$frame->data}, opcode:{$frame->opcode}, finish:{$frame->finish}\n"; // for ($i = 0; $i < 100; $i++) { // $_send = '' $_send = base64_encode(random_bytes(rand(100, 1024))); // $_send = str_repeat('B', rand(100, 800)); $_server->push($frame->fd, $_send, SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN | SWOOLE_WEBSOCKET_FLAG_COMPRESS); // echo "#$i\tserver sent " . strlen($_send) . " byte \n"; } } }); $server->on('close', function ($_server, $fd) { echo "client {$fd} closed\n"; }); $server->on('task', function ($_server, $worker_id, $task_id, $data) { var_dump($worker_id, $task_id, $data); return "hello world\n"; }); $server->on('finish', function ($_server, $task_id, $result) { var_dump($task_id, $result); }); $server->on('packet', function ($_server, $data, $client) { echo "#".posix_getpid()."\tPacket {$data}\n"; var_dump($client); }); $server->on('request', function (Request $request, Response $response) { $response->end(<<Swoole WebSocket Server HTML ); }); $server->start(); ================================================ FILE: examples/www/dir1/file1.txt ================================================ dir1/file1 ================================================ FILE: examples/www/dir1/file2.txt ================================================ dir1/file2 ================================================ FILE: examples/www/dir2/file1.txt ================================================ dir2/file1 ================================================ FILE: examples/www/dir2/file2.txt ================================================ dir2/file2.txt ================================================ FILE: examples/www/dir2/index.txt ================================================ dir2/index.txt ================================================ FILE: examples/www/file1.txt ================================================ file1.txt ================================================ FILE: examples/www/file2.txt ================================================ file2 ================================================ FILE: examples/www/index.html ================================================ index.html ================================================ FILE: examples/www/index.txt ================================================ index.txt ================================================ FILE: ext-src/php_swoole.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_library.h" #include "php_swoole_process.h" #include "php_swoole_thread.h" #include "swoole_iouring.h" BEGIN_EXTERN_C() #include "zend_exceptions.h" #include "zend_extensions.h" #include "ext/pcre/php_pcre.h" #include "ext/json/php_json.h" #include "php_open_temporary_file.h" #include "stubs/php_swoole_arginfo.h" #include "stubs/php_swoole_ex_arginfo.h" #include "stubs/php_swoole_tracer_arginfo.h" #ifdef SW_STDEXT #include "stubs/php_swoole_stdext_arginfo.h" #endif #ifdef SW_HAVE_SSH2LIB #include "stubs/php_swoole_ssh2_arginfo.h" #endif #ifdef SW_HAVE_FTP #include "stubs/php_swoole_ftp_arginfo.h" #endif END_EXTERN_C() #include "swoole_coroutine.h" #include "swoole_mime_type.h" #include "swoole_server.h" #include "swoole_util.h" #include "swoole_http2.h" #include #include #include #include #include #ifdef SW_USE_CURL #include #endif #if defined(__MACH__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #include #endif #ifdef SW_HAVE_ZLIB #include #endif #ifdef SW_HAVE_BROTLI #include #include #endif #ifdef SW_HAVE_ZSTD #include #endif #ifdef SW_USE_CARES #include #endif #ifdef SW_USE_PGSQL extern void swoole_libpq_version(char *buf, size_t len); #endif using swoole::Coroutine; using swoole::Server; using swoole::network::Socket; #ifdef SW_USE_IOURING using swoole::Iouring; #endif ZEND_DECLARE_MODULE_GLOBALS(swoole) SW_EXTERN_C_BEGIN static PHP_FUNCTION(swoole_version); static PHP_FUNCTION(swoole_cpu_num); static PHP_FUNCTION(swoole_strerror); static PHP_FUNCTION(swoole_clear_error); static PHP_FUNCTION(swoole_errno); static PHP_FUNCTION(swoole_error_log); static PHP_FUNCTION(swoole_error_log_ex); static PHP_FUNCTION(swoole_ignore_error); static PHP_FUNCTION(swoole_get_local_ip); static PHP_FUNCTION(swoole_get_local_mac); static PHP_FUNCTION(swoole_hashcode); static PHP_FUNCTION(swoole_mime_type_add); static PHP_FUNCTION(swoole_mime_type_set); static PHP_FUNCTION(swoole_mime_type_delete); static PHP_FUNCTION(swoole_mime_type_get); static PHP_FUNCTION(swoole_mime_type_exists); static PHP_FUNCTION(swoole_mime_type_list); static PHP_FUNCTION(swoole_substr_unserialize); static PHP_FUNCTION(swoole_substr_json_decode); static PHP_FUNCTION(swoole_internal_call_user_shutdown_begin); static PHP_FUNCTION(swoole_implicit_fn); SW_EXTERN_C_END #ifdef SW_STDEXT #include "php_swoole_stdext.h" #endif #ifdef SW_HAVE_SSH2LIB #include "php_swoole_ssh2_def.h" #endif #ifdef SW_HAVE_FTP #include "php_swoole_ftp_def.h" #endif // clang-format off const zend_function_entry swoole_functions[] = { PHP_FE(swoole_version, arginfo_swoole_version) PHP_FE(swoole_cpu_num, arginfo_swoole_cpu_num) PHP_FE(swoole_last_error, arginfo_swoole_last_error) /*------swoole_async_io------*/ PHP_FE(swoole_async_dns_lookup_coro, arginfo_swoole_async_dns_lookup_coro) PHP_FE(swoole_async_set, arginfo_swoole_async_set) /*------swoole_coroutine------*/ PHP_FE(swoole_coroutine_create, arginfo_swoole_coroutine_create) PHP_FE(swoole_coroutine_defer, arginfo_swoole_coroutine_defer) PHP_FE(swoole_coroutine_socketpair, arginfo_swoole_coroutine_socketpair) PHP_FE(swoole_test_kernel_coroutine, arginfo_swoole_test_kernel_coroutine) /*------other-----*/ PHP_FE(swoole_client_select, arginfo_swoole_client_select) PHP_FALIAS(swoole_select, swoole_client_select, arginfo_swoole_client_select) PHP_FE(swoole_set_process_name, arginfo_swoole_set_process_name) PHP_FE(swoole_get_local_ip, arginfo_swoole_get_local_ip) PHP_FE(swoole_get_local_mac, arginfo_swoole_get_local_mac) PHP_FE(swoole_strerror, arginfo_swoole_strerror) PHP_FE(swoole_errno, arginfo_swoole_errno) PHP_FE(swoole_clear_error, arginfo_swoole_clear_error) PHP_FE(swoole_error_log, arginfo_swoole_error_log) PHP_FE(swoole_error_log_ex, arginfo_swoole_error_log_ex) PHP_FE(swoole_ignore_error, arginfo_swoole_ignore_error) PHP_FE(swoole_hashcode, arginfo_swoole_hashcode) PHP_FE(swoole_mime_type_add, arginfo_swoole_mime_type_add) PHP_FE(swoole_mime_type_set, arginfo_swoole_mime_type_set) PHP_FE(swoole_mime_type_delete, arginfo_swoole_mime_type_delete) PHP_FE(swoole_mime_type_get, arginfo_swoole_mime_type_get) PHP_FALIAS(swoole_get_mime_type, swoole_mime_type_get, arginfo_swoole_mime_type_get) PHP_FE(swoole_mime_type_exists, arginfo_swoole_mime_type_exists) PHP_FE(swoole_mime_type_list, arginfo_swoole_mime_type_list) PHP_FE(swoole_clear_dns_cache, arginfo_swoole_clear_dns_cache) PHP_FE(swoole_substr_unserialize, arginfo_swoole_substr_unserialize) PHP_FE(swoole_substr_json_decode, arginfo_swoole_substr_json_decode) PHP_FE(swoole_internal_call_user_shutdown_begin, arginfo_swoole_internal_call_user_shutdown_begin) // for test PHP_FE(swoole_implicit_fn, arginfo_swoole_implicit_fn) // for admin server ZEND_FE(swoole_get_objects, arginfo_swoole_get_objects) ZEND_FE(swoole_get_vm_status, arginfo_swoole_get_vm_status) ZEND_FE(swoole_get_object_by_handle, arginfo_swoole_get_object_by_handle) ZEND_FE(swoole_name_resolver_lookup, arginfo_swoole_name_resolver_lookup) ZEND_FE(swoole_name_resolver_add, arginfo_swoole_name_resolver_add) ZEND_FE(swoole_name_resolver_remove, arginfo_swoole_name_resolver_remove) // for stdext #ifdef SW_STDEXT ZEND_FE(swoole_call_array_method, arginfo_swoole_call_array_method) ZEND_FE(swoole_call_string_method, arginfo_swoole_call_string_method) ZEND_FE(swoole_call_stream_method, arginfo_swoole_call_stream_method) ZEND_FE(swoole_array_search, arginfo_swoole_array_search) ZEND_FE(swoole_array_contains, arginfo_swoole_array_contains) ZEND_FE(swoole_array_join, arginfo_swoole_array_join) ZEND_FE(swoole_array_key_exists, arginfo_swoole_array_key_exists) ZEND_FE(swoole_array_map, arginfo_swoole_array_map) ZEND_FE(swoole_str_split, arginfo_swoole_str_split) ZEND_FE(swoole_parse_str, arginfo_swoole_parse_str) ZEND_FE(swoole_hash, arginfo_swoole_hash) ZEND_FE(swoole_typed_array, arginfo_swoole_typed_array) ZEND_FE(swoole_array_is_typed, arginfo_swoole_array_is_typed) ZEND_FE(swoole_str_is_empty, arginfo_swoole_str_is_empty) ZEND_FE(swoole_array_is_empty, arginfo_swoole_array_is_empty) ZEND_FE(swoole_str_match, arginfo_swoole_str_match) ZEND_FE(swoole_str_match_all, arginfo_swoole_str_match_all) ZEND_FE(swoole_str_json_decode, arginfo_swoole_str_json_decode) ZEND_FE(swoole_str_json_decode_to_object, arginfo_swoole_str_json_decode_to_object) ZEND_FE(swoole_str_replace, arginfo_swoole_str_replace) ZEND_FE(swoole_str_ireplace, arginfo_swoole_str_ireplace) ZEND_FE(swoole_array_replace_str, arginfo_swoole_array_replace_str) ZEND_FE(swoole_array_ireplace_str, arginfo_swoole_array_ireplace_str) #endif #ifdef SW_HAVE_SSH2LIB ZEND_FE(ssh2_connect, arginfo_ssh2_connect) ZEND_FE(ssh2_disconnect, arginfo_ssh2_disconnect) ZEND_FE(ssh2_methods_negotiated, arginfo_ssh2_methods_negotiated) ZEND_FE(ssh2_fingerprint, arginfo_ssh2_fingerprint) ZEND_FE(ssh2_auth_none, arginfo_ssh2_auth_none) ZEND_FE(ssh2_auth_password, arginfo_ssh2_auth_password) ZEND_FE(ssh2_auth_pubkey_file, arginfo_ssh2_auth_pubkey_file) ZEND_FE(ssh2_auth_pubkey, arginfo_ssh2_auth_pubkey) ZEND_FE(ssh2_auth_hostbased_file, arginfo_ssh2_auth_hostbased_file) ZEND_FE(ssh2_forward_listen, arginfo_ssh2_forward_listen) ZEND_FE(ssh2_forward_accept, arginfo_ssh2_forward_accept) ZEND_FE(ssh2_shell, arginfo_ssh2_shell) ZEND_FE(ssh2_shell_resize, arginfo_ssh2_shell_resize) ZEND_FE(ssh2_exec, arginfo_ssh2_exec) ZEND_FE(ssh2_tunnel, arginfo_ssh2_tunnel) ZEND_FE(ssh2_scp_recv, arginfo_ssh2_scp_recv) ZEND_FE(ssh2_scp_send, arginfo_ssh2_scp_send) ZEND_FE(ssh2_fetch_stream, arginfo_ssh2_fetch_stream) ZEND_FE(ssh2_send_eof, arginfo_ssh2_send_eof) ZEND_FE(ssh2_sftp, arginfo_ssh2_sftp) ZEND_FE(ssh2_sftp_rename, arginfo_ssh2_sftp_rename) ZEND_FE(ssh2_sftp_unlink, arginfo_ssh2_sftp_unlink) ZEND_FE(ssh2_sftp_mkdir, arginfo_ssh2_sftp_mkdir) ZEND_FE(ssh2_sftp_rmdir, arginfo_ssh2_sftp_rmdir) ZEND_FE(ssh2_sftp_chmod, arginfo_ssh2_sftp_chmod) ZEND_FE(ssh2_sftp_stat, arginfo_ssh2_sftp_stat) ZEND_FE(ssh2_sftp_lstat, arginfo_ssh2_sftp_lstat) ZEND_FE(ssh2_sftp_symlink, arginfo_ssh2_sftp_symlink) ZEND_FE(ssh2_sftp_readlink, arginfo_ssh2_sftp_readlink) ZEND_FE(ssh2_sftp_realpath, arginfo_ssh2_sftp_realpath) ZEND_FE(ssh2_publickey_init, arginfo_ssh2_publickey_init) ZEND_FE(ssh2_publickey_add, arginfo_ssh2_publickey_add) ZEND_FE(ssh2_publickey_remove, arginfo_ssh2_publickey_remove) ZEND_FE(ssh2_publickey_list, arginfo_ssh2_publickey_list) ZEND_FE(ssh2_auth_agent, arginfo_ssh2_auth_agent) #endif #ifdef SW_HAVE_FTP ZEND_FE(ftp_connect, arginfo_ftp_connect) #if defined(SW_HAVE_FTP_SSL) ZEND_FE(ftp_ssl_connect, arginfo_ftp_ssl_connect) #endif ZEND_FE(ftp_login, arginfo_ftp_login) ZEND_FE(ftp_pwd, arginfo_ftp_pwd) ZEND_FE(ftp_cdup, arginfo_ftp_cdup) ZEND_FE(ftp_chdir, arginfo_ftp_chdir) ZEND_FE(ftp_exec, arginfo_ftp_exec) ZEND_FE(ftp_raw, arginfo_ftp_raw) ZEND_FE(ftp_mkdir, arginfo_ftp_mkdir) ZEND_FE(ftp_rmdir, arginfo_ftp_rmdir) ZEND_FE(ftp_chmod, arginfo_ftp_chmod) ZEND_FE(ftp_alloc, arginfo_ftp_alloc) ZEND_FE(ftp_nlist, arginfo_ftp_nlist) ZEND_FE(ftp_rawlist, arginfo_ftp_rawlist) ZEND_FE(ftp_mlsd, arginfo_ftp_mlsd) ZEND_FE(ftp_systype, arginfo_ftp_systype) ZEND_FE(ftp_fget, arginfo_ftp_fget) ZEND_FE(ftp_nb_fget, arginfo_ftp_nb_fget) ZEND_FE(ftp_pasv, arginfo_ftp_pasv) ZEND_FE(ftp_get, arginfo_ftp_get) ZEND_FE(ftp_nb_get, arginfo_ftp_nb_get) ZEND_FE(ftp_nb_continue, arginfo_ftp_nb_continue) ZEND_FE(ftp_fput, arginfo_ftp_fput) ZEND_FE(ftp_nb_fput, arginfo_ftp_nb_fput) ZEND_FE(ftp_put, arginfo_ftp_put) ZEND_FE(ftp_append, arginfo_ftp_append) ZEND_FE(ftp_nb_put, arginfo_ftp_nb_put) ZEND_FE(ftp_size, arginfo_ftp_size) ZEND_FE(ftp_mdtm, arginfo_ftp_mdtm) ZEND_FE(ftp_rename, arginfo_ftp_rename) ZEND_FE(ftp_delete, arginfo_ftp_delete) ZEND_FE(ftp_site, arginfo_ftp_site) ZEND_FE(ftp_close, arginfo_ftp_close) #if PHP_VERSION_ID >= 80400 ZEND_RAW_FENTRY("ftp_quit", zif_ftp_close, arginfo_ftp_quit, 0, NULL, NULL) #else ZEND_RAW_FENTRY("ftp_quit", zif_ftp_close, arginfo_ftp_quit, 0) #endif ZEND_FE(ftp_set_option, arginfo_ftp_set_option) ZEND_FE(ftp_get_option, arginfo_ftp_get_option) #endif ZEND_FE(swoole_tracer_leak_detect, arginfo_swoole_tracer_leak_detect) ZEND_FE(swoole_tracer_prof_begin, arginfo_swoole_tracer_prof_begin) ZEND_FE(swoole_tracer_prof_end, arginfo_swoole_tracer_prof_end) PHP_FE_END /* Must be the last line in swoole_functions[] */ }; static const zend_module_dep swoole_deps[] = { ZEND_MOD_REQUIRED("json") #ifdef SW_USE_MYSQLND ZEND_MOD_REQUIRED("mysqlnd") #endif #ifdef SW_SOCKETS ZEND_MOD_REQUIRED("sockets") #endif #ifdef SW_USE_CURL ZEND_MOD_REQUIRED("curl") #endif #if defined(SW_USE_PGSQL) || defined(SW_USE_ORACLE) || defined(SW_USE_SQLITE) || defined(SW_USE_FIREBIRD) ZEND_MOD_REQUIRED("pdo") #endif #ifdef SW_HAVE_FTP ZEND_MOD_CONFLICTS("ftp") #endif #ifdef SW_HAVE_SSH2LIB ZEND_MOD_CONFLICTS("ssh2") #endif ZEND_MOD_END }; zend_module_entry swoole_module_entry = { STANDARD_MODULE_HEADER_EX, nullptr, swoole_deps, "swoole", swoole_functions, PHP_MINIT(swoole), PHP_MSHUTDOWN(swoole), PHP_RINIT(swoole), //RINIT PHP_RSHUTDOWN(swoole), //RSHUTDOWN PHP_MINFO(swoole), PHP_SWOOLE_VERSION, STANDARD_MODULE_PROPERTIES }; // clang-format on zend_class_entry *swoole_exception_ce; zend_object_handlers swoole_exception_handlers; zend_class_entry *swoole_error_ce; zend_object_handlers swoole_error_handlers; #ifdef COMPILE_DL_SWOOLE #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() #endif ZEND_GET_MODULE(swoole) #endif // clang-format off PHP_INI_BEGIN() STD_ZEND_INI_BOOLEAN("swoole.enable_library", "On", PHP_INI_ALL, OnUpdateBool, enable_library, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.enable_fiber_mock", "Off", PHP_INI_ALL, OnUpdateBool, enable_fiber_mock, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.enable_preemptive_scheduler", "Off", PHP_INI_ALL, OnUpdateBool, enable_preemptive_scheduler, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.display_errors", "On", PHP_INI_ALL, OnUpdateBool, display_errors, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.use_shortname", "On", PHP_INI_SYSTEM, OnUpdateBool, use_shortname, zend_swoole_globals, swoole_globals) STD_PHP_INI_ENTRY("swoole.socket_buffer_size", ZEND_TOSTR(SW_SOCKET_BUFFER_SIZE), PHP_INI_ALL, OnUpdateLong, socket_buffer_size, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.blocking_detection", "Off", PHP_INI_SYSTEM, OnUpdateBool, blocking_detection, zend_swoole_globals, swoole_globals) STD_PHP_INI_ENTRY("swoole.blocking_threshold", "100000", PHP_INI_SYSTEM, OnUpdateLong, blocking_threshold, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.profile", "Off", PHP_INI_SYSTEM, OnUpdateBool, profile, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.leak_detection", "Off", PHP_INI_SYSTEM, OnUpdateBool, leak_detection, zend_swoole_globals, swoole_globals) PHP_INI_END() // clang-format on static void php_swoole_init_globals(zend_swoole_globals *swoole_globals) { swoole_globals->enable_library = true; swoole_globals->enable_fiber_mock = false; swoole_globals->enable_preemptive_scheduler = false; swoole_globals->socket_buffer_size = SW_SOCKET_BUFFER_SIZE; swoole_globals->display_errors = true; swoole_globals->use_shortname = true; swoole_globals->in_autoload = nullptr; swoole_globals->blocking_detection = false; swoole_globals->blocking_threshold = 100000; swoole_globals->profile = false; swoole_globals->leak_detection = false; if (strcmp("cli", sapi_module.name) == 0 || strcmp("phpdbg", sapi_module.name) == 0 || strcmp("embed", sapi_module.name) == 0 || strcmp("micro", sapi_module.name) == 0) { swoole_globals->cli = true; } } void php_swoole_register_shutdown_function(const char *function) { #if PHP_VERSION_ID >= 80500 php_shutdown_function_entry shutdown_function_entry = { .fci_cache = empty_fcall_info_cache, .params = NULL, .param_count = 0, }; auto fn_len = strlen(function); auto fn_entry = (zend_function *) zend_hash_str_find_ptr(EG(function_table), function, fn_len); assert(fn_entry); shutdown_function_entry.fci_cache.function_handler = fn_entry; register_user_shutdown_function(function, fn_len, &shutdown_function_entry); #else php_shutdown_function_entry shutdown_function_entry; zval function_name; // In the user_shutdown_function_dtor function, the memory for the function name will be released, // so must not free it manually here. ZVAL_STRING(&function_name, function); zend_fcall_info_init( &function_name, 0, &shutdown_function_entry.fci, &shutdown_function_entry.fci_cache, nullptr, nullptr); register_user_shutdown_function(Z_STRVAL(function_name), Z_STRLEN(function_name), &shutdown_function_entry); #endif } void php_swoole_set_global_option(HashTable *vht) { zval *ztmp; #ifdef SW_DEBUG if (php_swoole_array_get_value(vht, "debug_mode", ztmp) && zval_is_true(ztmp)) { swoole_set_log_level(0); } #endif // [EventLoop] // ====================================================================== if (php_swoole_array_get_value(vht, "enable_signalfd", ztmp)) { SwooleG.enable_signalfd = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "enable_kqueue", ztmp)) { SwooleG.enable_kqueue = zval_is_true(ztmp); } // [Logger] // ====================================================================== if (php_swoole_array_get_value(vht, "trace_flags", ztmp)) { swoole_set_trace_flags(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "log_file", ztmp)) { swoole_set_log_file(zend::String(ztmp).val()); } if (php_swoole_array_get_value(vht, "log_level", ztmp)) { swoole_set_log_level(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "log_date_format", ztmp)) { sw_logger()->set_date_format(zend::String(ztmp).val()); } if (php_swoole_array_get_value(vht, "log_date_with_microseconds", ztmp)) { sw_logger()->set_date_with_microseconds(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "log_rotation", ztmp)) { sw_logger()->set_rotation(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "display_errors", ztmp)) { SWOOLE_G(display_errors) = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "print_backtrace_on_error", ztmp)) { #if !defined(HAVE_BOOST_STACKTRACE) && !defined(HAVE_EXECINFO) zend_throw_exception( swoole_error_ce, "The `print_backtrace_on_error` option requires `boost stacktrace` or `execinfo.h` to be installed", SW_ERROR_OPERATION_NOT_SUPPORT); #else SwooleG.print_backtrace_on_error = zval_is_true(ztmp); #endif } // [DNS] // ====================================================================== if (php_swoole_array_get_value(vht, "dns_server", ztmp)) { swoole_set_dns_server(zend::String(ztmp).to_std_string()); } // [Socket] // ====================================================================== auto timeout_format = [](zval *v) -> double { double timeout = zval_get_double(v); if (timeout <= 0 || timeout > INT_MAX) { return INT_MAX; } else { return timeout; } }; if (php_swoole_array_get_value(vht, "socket_dns_timeout", ztmp)) { Socket::default_dns_timeout = timeout_format(ztmp); } if (php_swoole_array_get_value(vht, "socket_connect_timeout", ztmp)) { Socket::default_connect_timeout = timeout_format(ztmp); } if (php_swoole_array_get_value(vht, "socket_write_timeout", ztmp) || php_swoole_array_get_value(vht, "socket_send_timeout", ztmp)) { Socket::default_write_timeout = timeout_format(ztmp); } if (php_swoole_array_get_value(vht, "socket_read_timeout", ztmp) || php_swoole_array_get_value(vht, "socket_recv_timeout", ztmp)) { Socket::default_read_timeout = timeout_format(ztmp); } if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { Socket::default_buffer_size = php_swoole_parse_to_size(ztmp); } if (php_swoole_array_get_value(vht, "socket_timeout", ztmp)) { Socket::default_read_timeout = Socket::default_write_timeout = timeout_format(ztmp); } // [HTTP2] // ====================================================================== if (php_swoole_array_get_value(vht, "http2_header_table_size", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE, php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "http2_enable_push", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH, zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "http2_max_concurrent_streams", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "http2_init_window_size", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "http2_max_frame_size", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "http2_max_header_list_size", ztmp)) { swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, php_swoole_parse_to_size(ztmp)); } } SW_API bool php_swoole_is_enable_coroutine() { if (sw_server()) { return sw_server()->is_enable_coroutine(); } else { return SwooleG.enable_coroutine; } } SW_API zend_long php_swoole_parse_to_size(zval *zv) { if (ZVAL_IS_STRING(zv)) { zend_string *errstr; auto size = zend_ini_parse_quantity(Z_STR_P(zv), &errstr); if (errstr) { php_swoole_fatal_error( E_ERROR, "failed to parse '%s' to size, Error: %s", Z_STRVAL_P(zv), ZSTR_VAL(errstr)); zend_string_release(errstr); } return size; } else { return zval_get_long(zv); } } SW_API zend_string *php_swoole_serialize(zval *zdata) { php_serialize_data_t var_hash; smart_str serialized_data = {}; PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&serialized_data, zdata, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); zend_string *result = nullptr; if (!EG(exception)) { result = zend_string_init(serialized_data.s->val, serialized_data.s->len, true); } smart_str_free(&serialized_data); return result; } SW_API bool php_swoole_unserialize(const zend_string *data, zval *zv) { php_unserialize_data_t var_hash; const char *p = ZSTR_VAL(data); size_t l = ZSTR_LEN(data); PHP_VAR_UNSERIALIZE_INIT(var_hash); zend_bool unserialized = php_var_unserialize(zv, (const uchar **) &p, (const uchar *) (p + l), &var_hash); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (!unserialized) { swoole_warning("unserialize() failed, Error at offset " ZEND_LONG_FMT " of %zd bytes", (zend_long) ((char *) p - ZSTR_VAL(data)), l); } return unserialized; } static void fatal_error(int code, const char *format, ...) { va_list args; va_start(args, format); zend_object *exception = zend_throw_exception(swoole_error_ce, swoole::std_string::vformat(format, args).c_str(), code); va_end(args); zend::print_error(exception, E_ERROR); if (code == SW_ERROR_CO_HAS_BEEN_BOUND) { fprintf(stderr, "\n [Coroutine-%ld] Stack trace:" "\n -------------------------------------------------------------------" "\n", Coroutine::get_socket_bound_cid()); sw_php_print_backtrace(Coroutine::get_socket_bound_cid()); } #ifdef SW_THREAD if (!tsrm_is_main_thread()) { php_swoole_thread_bailout(); } #endif swoole_exit(255); } static void print_backtrace() { fprintf(stderr, "\nStack trace:\n"); sw_php_print_backtrace_impl(0); } static void bug_report_message_init() { SwooleG.bug_report_message += swoole::std_string::format("PHP_VERSION : %s\n", PHP_VERSION); } static int g_module_number_; int sw_module_number() { return g_module_number_; } /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(swoole) { ZEND_INIT_MODULE_GLOBALS(swoole, php_swoole_init_globals, nullptr); REGISTER_INI_ENTRIES(); g_module_number_ = module_number; // clang-format off // MUST be on the same line for the inspection tool to recognize correctly SW_REGISTER_STRING_CONSTANT("SWOOLE_VERSION", SWOOLE_VERSION); SW_REGISTER_LONG_CONSTANT("SWOOLE_VERSION_ID", SWOOLE_VERSION_ID); SW_REGISTER_LONG_CONSTANT("SWOOLE_MAJOR_VERSION", SWOOLE_MAJOR_VERSION); SW_REGISTER_LONG_CONSTANT("SWOOLE_MINOR_VERSION", SWOOLE_MINOR_VERSION); SW_REGISTER_LONG_CONSTANT("SWOOLE_RELEASE_VERSION", SWOOLE_RELEASE_VERSION); SW_REGISTER_STRING_CONSTANT("SWOOLE_EXTRA_VERSION", SWOOLE_EXTRA_VERSION); #ifndef SW_DEBUG SW_REGISTER_BOOL_CONSTANT("SWOOLE_DEBUG", 0); #else SW_REGISTER_BOOL_CONSTANT("SWOOLE_DEBUG", 1); #endif #ifdef SW_HAVE_COMPRESSION SW_REGISTER_BOOL_CONSTANT("SWOOLE_HAVE_COMPRESSION", 1); #endif #ifdef SW_HAVE_ZLIB SW_REGISTER_BOOL_CONSTANT("SWOOLE_HAVE_ZLIB", 1); #endif #ifdef SW_HAVE_BROTLI SW_REGISTER_BOOL_CONSTANT("SWOOLE_HAVE_BROTLI", 1); #endif SW_REGISTER_BOOL_CONSTANT("SWOOLE_USE_HTTP2", 1); SW_REGISTER_BOOL_CONSTANT("SWOOLE_USE_SHORTNAME", SWOOLE_G(use_shortname)); /** * socket type */ SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_TCP", SW_SOCK_TCP); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_TCP6", SW_SOCK_TCP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UDP", SW_SOCK_UDP); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UDP6", SW_SOCK_UDP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UNIX_DGRAM", SW_SOCK_UNIX_DGRAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UNIX_STREAM", SW_SOCK_UNIX_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_RAW", SW_SOCK_RAW); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_RAW6", SW_SOCK_RAW6); /** * simple socket type alias */ SW_REGISTER_LONG_CONSTANT("SWOOLE_TCP", SW_SOCK_TCP); SW_REGISTER_LONG_CONSTANT("SWOOLE_TCP6", SW_SOCK_TCP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_UDP", SW_SOCK_UDP); SW_REGISTER_LONG_CONSTANT("SWOOLE_UDP6", SW_SOCK_UDP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_UNIX_DGRAM", SW_SOCK_UNIX_DGRAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_UNIX_STREAM", SW_SOCK_UNIX_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_RAW", SW_SOCK_RAW); SW_REGISTER_LONG_CONSTANT("SWOOLE_RAW6", SW_SOCK_RAW6); /** * simple api */ SW_REGISTER_BOOL_CONSTANT("SWOOLE_SOCK_SYNC", 0); SW_REGISTER_BOOL_CONSTANT("SWOOLE_SOCK_ASYNC", 1); SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL", SW_SOCK_SSL); /** * SSL methods */ SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv3_METHOD", SW_SSLv3_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv3_SERVER_METHOD", SW_SSLv3_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv3_CLIENT_METHOD", SW_SSLv3_CLIENT_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_METHOD", SW_TLSv1_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_SERVER_METHOD", SW_TLSv1_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_CLIENT_METHOD", SW_TLSv1_CLIENT_METHOD); #ifdef TLS1_1_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_1_METHOD", SW_TLSv1_1_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_1_SERVER_METHOD", SW_TLSv1_1_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_1_CLIENT_METHOD", SW_TLSv1_1_CLIENT_METHOD); #endif #ifdef TLS1_2_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_2_METHOD", SW_TLSv1_2_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_2_SERVER_METHOD", SW_TLSv1_2_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLSv1_2_CLIENT_METHOD", SW_TLSv1_2_CLIENT_METHOD); #endif #ifdef SW_SUPPORT_DTLS SW_REGISTER_LONG_CONSTANT("SWOOLE_DTLS_SERVER_METHOD", SW_DTLS_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DTLS_CLIENT_METHOD", SW_DTLS_CLIENT_METHOD); #endif SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv23_METHOD", SW_SSLv23_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv23_SERVER_METHOD", SW_SSLv23_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_SSLv23_CLIENT_METHOD", SW_SSLv23_CLIENT_METHOD); /* SSLv23_method have been renamed to TLS_method */ SW_REGISTER_LONG_CONSTANT("SWOOLE_TLS_METHOD", SW_SSLv23_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLS_SERVER_METHOD", SW_SSLv23_SERVER_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TLS_CLIENT_METHOD", SW_SSLv23_CLIENT_METHOD); /** * SSL protocols */ #ifdef HAVE_SSL3 SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_SSLv3", SW_SSL_SSLv3); #endif SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1", SW_SSL_TLSv1); #ifdef TLS1_1_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_1", SW_SSL_TLSv1_1); #endif #ifdef TLS1_2_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_2", SW_SSL_TLSv1_2); #endif #ifdef TLS1_3_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_3", SW_SSL_TLSv1_3); #endif #ifdef SW_SUPPORT_DTLS SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_DTLS", SW_SSL_DTLS); #endif SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_SSLv2", SW_SSL_SSLv2); /** * Register event constants */ SW_REGISTER_LONG_CONSTANT("SWOOLE_EVENT_READ", SW_EVENT_READ); SW_REGISTER_LONG_CONSTANT("SWOOLE_EVENT_WRITE", SW_EVENT_WRITE); /** * Register ERROR types */ SW_REGISTER_LONG_CONSTANT("SWOOLE_STRERROR_SYSTEM", SW_STRERROR_SYSTEM); SW_REGISTER_LONG_CONSTANT("SWOOLE_STRERROR_GAI", SW_STRERROR_GAI); SW_REGISTER_LONG_CONSTANT("SWOOLE_STRERROR_DNS", SW_STRERROR_DNS); SW_REGISTER_LONG_CONSTANT("SWOOLE_STRERROR_SWOOLE", SW_STRERROR_SWOOLE); /** * Register ERROR constants */ SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_MALLOC_FAIL", SW_ERROR_MALLOC_FAIL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SYSTEM_CALL_FAIL", SW_ERROR_SYSTEM_CALL_FAIL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PHP_FATAL_ERROR", SW_ERROR_PHP_FATAL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_NAME_TOO_LONG", SW_ERROR_NAME_TOO_LONG); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_INVALID_PARAMS", SW_ERROR_INVALID_PARAMS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_QUEUE_FULL", SW_ERROR_QUEUE_FULL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_OPERATION_NOT_SUPPORT", SW_ERROR_OPERATION_NOT_SUPPORT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PROTOCOL_ERROR", SW_ERROR_PROTOCOL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WRONG_OPERATION", SW_ERROR_WRONG_OPERATION); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PHP_RUNTIME_NOTICE", SW_ERROR_PHP_RUNTIME_NOTICE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FOR_TEST", SW_ERROR_FOR_TEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_NO_PAYLOAD", SW_ERROR_NO_PAYLOAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_UNDEFINED_BEHAVIOR", SW_ERROR_UNDEFINED_BEHAVIOR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_NOT_THREAD_SAFETY", SW_ERROR_NOT_THREAD_SAFETY); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_NOT_EXIST", SW_ERROR_FILE_NOT_EXIST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_TOO_LARGE", SW_ERROR_FILE_TOO_LARGE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_EMPTY", SW_ERROR_FILE_EMPTY); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DIR_NOT_EXIST", SW_ERROR_DIR_NOT_EXIST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_DUPLICATE_REQUEST", SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED", SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT", SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_UNSUPPORTED", SW_ERROR_DNSLOOKUP_UNSUPPORTED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_NO_SERVER", SW_ERROR_DNSLOOKUP_NO_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_IPV6_ADDRESS", SW_ERROR_BAD_IPV6_ADDRESS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_UNREGISTERED_SIGNAL", SW_ERROR_UNREGISTERED_SIGNAL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_HOST_ADDR", SW_ERROR_BAD_HOST_ADDR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_PORT", SW_ERROR_BAD_PORT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_SOCKET_TYPE", SW_ERROR_BAD_SOCKET_TYPE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_REMOVE_FAILED", SW_ERROR_EVENT_REMOVE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_ADD_FAILED", SW_ERROR_EVENT_ADD_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_UPDATE_FAILED", SW_ERROR_EVENT_UPDATE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_UNKNOWN_DATA", SW_ERROR_EVENT_UNKNOWN_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSED_BY_SERVER", SW_ERROR_SESSION_CLOSED_BY_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSED_BY_CLIENT", SW_ERROR_SESSION_CLOSED_BY_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSING", SW_ERROR_SESSION_CLOSING); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSED", SW_ERROR_SESSION_CLOSED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_NOT_EXIST", SW_ERROR_SESSION_NOT_EXIST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_INVALID_ID", SW_ERROR_SESSION_INVALID_ID); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_DISCARD_TIMEOUT_DATA", SW_ERROR_SESSION_DISCARD_TIMEOUT_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_DISCARD_DATA", SW_ERROR_SESSION_DISCARD_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_OUTPUT_BUFFER_OVERFLOW", SW_ERROR_OUTPUT_BUFFER_OVERFLOW); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_OUTPUT_SEND_YIELD", SW_ERROR_OUTPUT_SEND_YIELD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_NOT_READY", SW_ERROR_SSL_NOT_READY); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_CANNOT_USE_SENFILE", SW_ERROR_SSL_CANNOT_USE_SENFILE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_EMPTY_PEER_CERTIFICATE", SW_ERROR_SSL_EMPTY_PEER_CERTIFICATE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_VERIFY_FAILED", SW_ERROR_SSL_VERIFY_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_BAD_CLIENT", SW_ERROR_SSL_BAD_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_BAD_PROTOCOL", SW_ERROR_SSL_BAD_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_RESET", SW_ERROR_SSL_RESET); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_HANDSHAKE_FAILED", SW_ERROR_SSL_HANDSHAKE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_CREATE_CONTEXT_FAILED", SW_ERROR_SSL_CREATE_CONTEXT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_CREATE_SESSION_FAILED", SW_ERROR_SSL_CREATE_SESSION_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PACKAGE_LENGTH_TOO_LARGE", SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PACKAGE_LENGTH_NOT_FOUND", SW_ERROR_PACKAGE_LENGTH_NOT_FOUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DATA_LENGTH_TOO_LARGE", SW_ERROR_DATA_LENGTH_TOO_LARGE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PACKAGE_MALFORMED_DATA", SW_ERROR_PACKAGE_MALFORMED_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_TASK_PACKAGE_TOO_BIG", SW_ERROR_TASK_PACKAGE_TOO_BIG); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_TASK_DISPATCH_FAIL", SW_ERROR_TASK_DISPATCH_FAIL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_TASK_TIMEOUT", SW_ERROR_TASK_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_ID_TOO_BIG", SW_ERROR_HTTP2_STREAM_ID_TOO_BIG); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_NO_HEADER", SW_ERROR_HTTP2_STREAM_NO_HEADER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_NOT_FOUND", SW_ERROR_HTTP2_STREAM_NOT_FOUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_IGNORE", SW_ERROR_HTTP2_STREAM_IGNORE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED", SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_INTERNAL_ERROR", SW_ERROR_HTTP2_INTERNAL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_BAD_REQUEST", SW_ERROR_AIO_BAD_REQUEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_CANCELED", SW_ERROR_AIO_CANCELED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_TIMEOUT", SW_ERROR_AIO_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CLIENT_NO_CONNECTION", SW_ERROR_CLIENT_NO_CONNECTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_CLOSED", SW_ERROR_SOCKET_CLOSED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_POLL_TIMEOUT", SW_ERROR_SOCKET_POLL_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_NOT_EXISTS", SW_ERROR_SOCKET_NOT_EXISTS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_UNSUPPORT_VERSION", SW_ERROR_SOCKS5_UNSUPPORT_VERSION); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_UNSUPPORT_METHOD", SW_ERROR_SOCKS5_UNSUPPORT_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_AUTH_FAILED", SW_ERROR_SOCKS5_AUTH_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_SERVER_ERROR", SW_ERROR_SOCKS5_SERVER_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_HANDSHAKE_FAILED", SW_ERROR_SOCKS5_HANDSHAKE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_CONNECT_FAILED", SW_ERROR_SOCKS5_CONNECT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_HANDSHAKE_ERROR", SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_INVALID_PROTOCOL", SW_ERROR_HTTP_INVALID_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_HANDSHAKE_FAILED", SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_BAD_RESPONSE", SW_ERROR_HTTP_PROXY_BAD_RESPONSE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_CONFLICT_HEADER", SW_ERROR_HTTP_CONFLICT_HEADER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_CONTEXT_UNAVAILABLE", SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_COOKIE_UNAVAILABLE", SW_ERROR_HTTP_COOKIE_UNAVAILABLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_BAD_CLIENT", SW_ERROR_WEBSOCKET_BAD_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_BAD_OPCODE", SW_ERROR_WEBSOCKET_BAD_OPCODE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_UNCONNECTED", SW_ERROR_WEBSOCKET_UNCONNECTED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_HANDSHAKE_FAILED", SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_PACK_FAILED", SW_ERROR_WEBSOCKET_PACK_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_UNPACK_FAILED", SW_ERROR_WEBSOCKET_UNPACK_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_INCOMPLETE_PACKET", SW_ERROR_WEBSOCKET_INCOMPLETE_PACKET); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_MUST_CREATED_BEFORE_CLIENT", SW_ERROR_SERVER_MUST_CREATED_BEFORE_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_TOO_MANY_SOCKET", SW_ERROR_SERVER_TOO_MANY_SOCKET); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_TERMINATED", SW_ERROR_SERVER_WORKER_TERMINATED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_LISTEN_PORT", SW_ERROR_SERVER_INVALID_LISTEN_PORT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_TOO_MANY_LISTEN_PORT", SW_ERROR_SERVER_TOO_MANY_LISTEN_PORT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_PIPE_BUFFER_FULL", SW_ERROR_SERVER_PIPE_BUFFER_FULL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_NO_IDLE_WORKER", SW_ERROR_SERVER_NO_IDLE_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_ONLY_START_ONE", SW_ERROR_SERVER_ONLY_START_ONE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_SEND_IN_MASTER", SW_ERROR_SERVER_SEND_IN_MASTER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_REQUEST", SW_ERROR_SERVER_INVALID_REQUEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_CONNECT_FAIL", SW_ERROR_SERVER_CONNECT_FAIL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_COMMAND", SW_ERROR_SERVER_INVALID_COMMAND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_IS_NOT_REGULAR_FILE", SW_ERROR_SERVER_IS_NOT_REGULAR_FILE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT", SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_CALLBACK", SW_ERROR_SERVER_INVALID_CALLBACK); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_UNRELATED_THREAD", SW_ERROR_SERVER_UNRELATED_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_EXIT_TIMEOUT", SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA", SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_UNPROCESSED_DATA", SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_OUT_OF_COROUTINE", SW_ERROR_CO_OUT_OF_COROUTINE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_HAS_BEEN_BOUND", SW_ERROR_CO_HAS_BEEN_BOUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_HAS_BEEN_DISCARDED", SW_ERROR_CO_HAS_BEEN_DISCARDED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_MUTEX_DOUBLE_UNLOCK", SW_ERROR_CO_MUTEX_DOUBLE_UNLOCK); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_BLOCK_OBJECT_LOCKED", SW_ERROR_CO_BLOCK_OBJECT_LOCKED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_BLOCK_OBJECT_WAITING", SW_ERROR_CO_BLOCK_OBJECT_WAITING); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_YIELD_FAILED", SW_ERROR_CO_YIELD_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_GETCONTEXT_FAILED", SW_ERROR_CO_GETCONTEXT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_SWAPCONTEXT_FAILED", SW_ERROR_CO_SWAPCONTEXT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_MAKECONTEXT_FAILED", SW_ERROR_CO_MAKECONTEXT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_IOCPINIT_FAILED", SW_ERROR_CO_IOCPINIT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_PROTECT_STACK_FAILED", SW_ERROR_CO_PROTECT_STACK_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_STD_THREAD_LINK_ERROR", SW_ERROR_CO_STD_THREAD_LINK_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_DISABLED_MULTI_THREAD", SW_ERROR_CO_DISABLED_MULTI_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_CANNOT_CANCEL", SW_ERROR_CO_CANNOT_CANCEL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_NOT_EXISTS", SW_ERROR_CO_NOT_EXISTS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_CANCELED", SW_ERROR_CO_CANCELED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_TIMEDOUT", SW_ERROR_CO_TIMEDOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_SOCKET_CLOSE_WAIT", SW_ERROR_CO_SOCKET_CLOSE_WAIT); /** * trace log */ SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_SERVER", SW_TRACE_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CLIENT", SW_TRACE_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_BUFFER", SW_TRACE_BUFFER); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CONN", SW_TRACE_CONN); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_EVENT", SW_TRACE_EVENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_WORKER", SW_TRACE_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_MEMORY", SW_TRACE_MEMORY); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_REACTOR", SW_TRACE_REACTOR); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_PHP", SW_TRACE_PHP); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_HTTP", SW_TRACE_HTTP); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_HTTP2", SW_TRACE_HTTP2); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_EOF_PROTOCOL", SW_TRACE_EOF_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_LENGTH_PROTOCOL", SW_TRACE_LENGTH_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CLOSE", SW_TRACE_CLOSE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_WEBSOCKET", SW_TRACE_WEBSOCKET); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_REDIS_CLIENT", SW_TRACE_REDIS_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_MYSQL_CLIENT", SW_TRACE_MYSQL_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_HTTP_CLIENT", SW_TRACE_HTTP_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_AIO", SW_TRACE_AIO); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_SSL", SW_TRACE_SSL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_NORMAL", SW_TRACE_NORMAL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CHANNEL", SW_TRACE_CHANNEL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_TIMER", SW_TRACE_TIMER); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_SOCKET", SW_TRACE_SOCKET); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_COROUTINE", SW_TRACE_COROUTINE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CONTEXT", SW_TRACE_CONTEXT); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_HTTP_SERVER", SW_TRACE_CO_HTTP_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_TABLE", SW_TRACE_TABLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_CURL", SW_TRACE_CO_CURL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CARES", SW_TRACE_CARES); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_ZLIB", SW_TRACE_ZLIB); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_PGSQL", SW_TRACE_CO_PGSQL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_ODBC", SW_TRACE_CO_ODBC); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_ORACLE", SW_TRACE_CO_ORACLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_SQLITE", SW_TRACE_CO_SQLITE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_FIREBIRD", SW_TRACE_CO_FIREBIRD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_SSH2", SW_TRACE_CO_SSH2); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_THREAD", SW_TRACE_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_ALL", SW_TRACE_ALL); /** * log level */ SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_DEBUG", SW_LOG_DEBUG); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_TRACE", SW_LOG_TRACE); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_INFO", SW_LOG_INFO); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_NOTICE", SW_LOG_NOTICE); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_WARNING", SW_LOG_WARNING); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ERROR", SW_LOG_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_NONE", SW_LOG_NONE); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ROTATION_SINGLE", SW_LOG_ROTATION_SINGLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ROTATION_MONTHLY", SW_LOG_ROTATION_MONTHLY); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ROTATION_DAILY", SW_LOG_ROTATION_DAILY); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ROTATION_HOURLY", SW_LOG_ROTATION_HOURLY); SW_REGISTER_LONG_CONSTANT("SWOOLE_LOG_ROTATION_EVERY_MINUTE", SW_LOG_ROTATION_EVERY_MINUTE); SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_NONE", SW_IPC_NONE); SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_UNIXSOCK", SW_IPC_UNIXSOCK); SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_SOCKET", SW_IPC_SOCKET); /** * limit */ SW_REGISTER_LONG_CONSTANT("SWOOLE_IOV_MAX", IOV_MAX); /** * iouring */ #ifdef SW_USE_IOURING SW_REGISTER_LONG_CONSTANT("SWOOLE_IOURING_DEFAULT", SW_IOURING_DEFAULT); SW_REGISTER_LONG_CONSTANT("SWOOLE_IOURING_SQPOLL", SW_IOURING_SQPOLL); #endif // clang-format on if (SWOOLE_G(use_shortname)) { SW_FUNCTION_ALIAS( CG(function_table), "swoole_coroutine_create", CG(function_table), "go", arginfo_swoole_coroutine_create); SW_FUNCTION_ALIAS( CG(function_table), "swoole_coroutine_defer", CG(function_table), "defer", arginfo_swoole_coroutine_defer); #ifdef SW_STDEXT SW_FUNCTION_ALIAS( CG(function_table), "swoole_typed_array", CG(function_table), "typed_array", arginfo_swoole_typed_array); #endif } swoole_init(); // init bug report message bug_report_message_init(); SW_INIT_CLASS_ENTRY_EX2( swoole_exception, "Swoole\\Exception", nullptr, nullptr, zend_ce_exception, zend_get_std_object_handlers()); SW_INIT_CLASS_ENTRY_EX2( swoole_error, "Swoole\\Error", nullptr, nullptr, zend_ce_error, zend_get_std_object_handlers()); /** **/ php_swoole_event_minit(module_number); // base php_swoole_atomic_minit(module_number); php_swoole_lock_minit(module_number); php_swoole_process_minit(module_number); php_swoole_process_pool_minit(module_number); php_swoole_table_minit(module_number); php_swoole_timer_minit(module_number); // coroutine php_swoole_coroutine_minit(module_number); php_swoole_coroutine_system_minit(module_number); php_swoole_coroutine_scheduler_minit(module_number); php_swoole_coroutine_lock_minit(module_number); php_swoole_channel_coro_minit(module_number); php_swoole_runtime_minit(module_number); // client php_swoole_socket_coro_minit(module_number); php_swoole_client_minit(module_number); php_swoole_client_async_minit(module_number); php_swoole_client_coro_minit(module_number); php_swoole_http_client_coro_minit(module_number); php_swoole_http2_client_coro_minit(module_number); #ifdef SW_HAVE_SSH2LIB php_swoole_ssh2_minit(module_number); #endif #ifdef SW_HAVE_FTP PHP_MINIT(ftp)(type, module_number); #endif // server php_swoole_server_minit(module_number); php_swoole_server_port_minit(module_number); php_swoole_http_request_minit(module_number); php_swoole_http_response_minit(module_number); php_swoole_http_cookie_minit(module_number); php_swoole_http_server_minit(module_number); php_swoole_http_server_coro_minit(module_number); php_swoole_websocket_server_minit(module_number); php_swoole_redis_server_minit(module_number); php_swoole_name_resolver_minit(module_number); #ifdef SW_USE_PGSQL php_swoole_pgsql_minit(module_number); #endif #ifdef SW_USE_ODBC php_swoole_odbc_minit(module_number); #endif #ifdef SW_USE_ORACLE php_swoole_oracle_minit(module_number); #endif #ifdef SW_USE_SQLITE php_swoole_sqlite_minit(module_number); #endif #ifdef SW_USE_FIREBIRD php_swoole_firebird_minit(module_number); #endif #ifdef SW_THREAD php_swoole_thread_minit(module_number); php_swoole_thread_atomic_minit(module_number); php_swoole_thread_lock_minit(module_number); php_swoole_thread_barrier_minit(module_number); php_swoole_thread_queue_minit(module_number); php_swoole_thread_map_minit(module_number); php_swoole_thread_arraylist_minit(module_number); #endif #ifdef SW_STDEXT php_swoole_stdext_minit(module_number); #endif php_swoole_tracer_minit(module_number); SwooleG.fatal_error = fatal_error; SwooleG.print_backtrace = print_backtrace; Socket::default_buffer_size = SWOOLE_G(socket_buffer_size); SwooleG.dns_cache_refresh_time = 60; // enable pcre.jit and use swoole extension on macOS will lead to core dump, disable it temporarily #if defined(PHP_PCRE_VERSION) && defined(HAVE_PCRE_JIT_SUPPORT) && __MACH__ && !defined(SW_DEBUG) PCRE_G(jit) = 0; #endif zend::known_strings_init(); return SUCCESS; } /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(swoole) { zend::known_strings_dtor(); php_swoole_runtime_mshutdown(); #ifdef SW_USE_PGSQL php_swoole_pgsql_mshutdown(); #endif #ifdef SW_USE_ORACLE php_swoole_oracle_mshutdown(); #endif #ifdef SW_USE_SQLITE php_swoole_sqlite_mshutdown(); #endif #ifdef SW_USE_FIREBIRD php_swoole_firebird_mshutdown(); #endif #ifdef SW_HAVE_SSH2LIB php_swoole_ssh2_mshutdown(); #endif swoole_clean(); return SUCCESS; } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(swoole) { char buf[64]; php_info_print_table_start(); php_info_print_table_header(2, "Swoole", "enabled"); php_info_print_table_row(2, "Author", "Swoole Team "); php_info_print_table_row(2, "Version", SWOOLE_VERSION); snprintf(buf, sizeof(buf), "%s %s", __DATE__, __TIME__); php_info_print_table_row(2, "Built", buf); #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN php_info_print_table_row(2, "host byte order", "little endian"); #else php_info_print_table_row(2, "host byte order", "big endian"); #endif #if defined(SW_USE_THREAD_CONTEXT) php_info_print_table_row(2, "coroutine", "enabled with thread context"); #elif defined(SW_USE_ASM_CONTEXT) php_info_print_table_row(2, "coroutine", "enabled with boost asm context"); #else php_info_print_table_row(2, "coroutine", "enabled with ucontext"); #endif #ifdef SW_DEBUG php_info_print_table_row(2, "debug", "enabled"); #endif #ifdef SW_LOG_TRACE_OPEN php_info_print_table_row(2, "trace_log", "enabled"); #endif #ifdef HAVE_EPOLL php_info_print_table_row(2, "epoll", "enabled"); #endif #ifdef HAVE_EVENTFD php_info_print_table_row(2, "eventfd", "enabled"); #endif #ifdef SW_THREAD php_info_print_table_row(2, "thread", "enabled"); #endif #ifdef HAVE_SIGNALFD php_info_print_table_row(2, "signalfd", "enabled"); #endif #ifdef SW_USE_ACCEPT4 php_info_print_table_row(2, "accept4", "enabled"); #endif #ifdef HAVE_CPU_AFFINITY php_info_print_table_row(2, "cpu_affinity", "enabled"); #endif #ifdef HAVE_SPINLOCK php_info_print_table_row(2, "spinlock", "enabled"); #endif #ifdef HAVE_RWLOCK php_info_print_table_row(2, "rwlock", "enabled"); #endif #ifdef SW_SOCKETS php_info_print_table_row(2, "sockets", "enabled"); #endif #ifdef OPENSSL_VERSION_TEXT php_info_print_table_row(2, "openssl", OPENSSL_VERSION_TEXT); #else php_info_print_table_row(2, "openssl", "enabled"); #endif #ifdef SW_SUPPORT_DTLS php_info_print_table_row(2, "dtls", "enabled"); #endif php_info_print_table_row(2, "http2", "enabled"); php_info_print_table_row(2, "json", "enabled"); #ifdef SW_USE_CURL php_info_print_table_row(2, "curl-native", "enabled"); curl_version_info_data *d = curl_version_info(CURLVERSION_NOW); php_info_print_table_row(2, "curl-version", d->version); #endif #ifdef SW_USE_CARES php_info_print_table_row(2, "c-ares", ares_version(nullptr)); #endif #ifdef SW_HAVE_ZLIB #ifdef ZLIB_VERSION php_info_print_table_row(2, "zlib", ZLIB_VERSION); #else php_info_print_table_row(2, "zlib", "enabled"); #endif #endif #ifdef SW_HAVE_BROTLI snprintf(buf, sizeof(buf), "E%u/D%u", BrotliEncoderVersion(), BrotliDecoderVersion()); php_info_print_table_row(2, "brotli", buf); #endif #ifdef SW_HAVE_ZSTD #ifdef ZSTD_VERSION_NUMBER php_info_print_table_row(2, "zstd", ZSTD_VERSION_STRING); #else php_info_print_table_row(2, "zstd", "enabled"); #endif #endif #ifdef HAVE_MUTEX_TIMEDLOCK php_info_print_table_row(2, "mutex_timedlock", "enabled"); #endif #ifdef HAVE_PTHREAD_BARRIER php_info_print_table_row(2, "pthread_barrier", "enabled"); #endif #ifdef HAVE_FUTEX php_info_print_table_row(2, "futex", "enabled"); #endif #ifdef SW_USE_MYSQLND php_info_print_table_row(2, "mysqlnd", "enabled"); #endif #ifdef SW_USE_JEMALLOC php_info_print_table_row(2, "jemalloc", "enabled"); #endif #ifdef SW_USE_TCMALLOC php_info_print_table_row(2, "tcmalloc", "enabled"); #endif #ifdef SW_USE_PGSQL char libpq_version[16]; swoole_libpq_version(libpq_version, sizeof(libpq_version)); php_info_print_table_row(2, "postgresql(libpq) version", libpq_version); php_info_print_table_row(2, "coroutine_pgsql", "enabled"); #endif #ifdef SW_USE_ODBC php_info_print_table_row(2, "coroutine_odbc", "enabled"); #endif #ifdef SW_USE_ORACLE php_info_print_table_row(2, "coroutine_oracle", "enabled"); #endif #ifdef SW_USE_SQLITE php_info_print_table_row(2, "coroutine_sqlite", "enabled"); #endif #ifdef SW_USE_FIREBIRD php_info_print_table_row(2, "coroutine_firebird", "enabled"); #endif #ifdef SW_USE_IOURING php_info_print_table_row(2, "io_uring", "enabled"); #endif #ifdef SW_USE_URING_SOCKET php_info_print_table_row(2, "uring_socket", "enabled"); #endif #ifdef HAVE_BOOST_STACKTRACE php_info_print_table_row(2, "boost stacktrace", "enabled"); #elif defined(HAVE_EXECINFO) php_info_print_table_row(2, "execinfo", "enabled"); #endif #ifdef SW_HAVE_SSH2LIB php_swoole_ssh2_minfo(); #endif #ifdef SW_HAVE_FTP PHP_MINFO(ftp)(zend_module); #endif php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } /* }}} */ static void *sw_emalloc(size_t size) { return emalloc(size); } static void *sw_ecalloc(size_t nmemb, size_t size) { return ecalloc(nmemb, size); } static void *sw_erealloc(void *address, size_t size) { return erealloc(address, size); } static void sw_efree(void *address) { efree(address); } static void *sw_zend_string_malloc(size_t size) { zend_string *str = zend_string_alloc(size, false); if (str == nullptr) { return nullptr; } return str->val; } static void *sw_zend_string_calloc(size_t nmemb, size_t size) { void *mem = sw_zend_string_malloc(nmemb * size); if (mem) { sw_memset_zero(mem, size); } return mem; } static void *sw_zend_string_realloc(void *address, size_t size) { zend_string *str = zend_string_realloc(zend::fetch_zend_string_by_val(address), size, false); if (str == nullptr) { return nullptr; } return str->val; } static void sw_zend_string_free(void *address) { zend_string_release_ex(zend::fetch_zend_string_by_val(address), false); } static swoole::Allocator php_allocator{ sw_emalloc, sw_ecalloc, sw_erealloc, sw_efree, }; static swoole::Allocator zend_string_allocator{ sw_zend_string_malloc, sw_zend_string_calloc, sw_zend_string_realloc, sw_zend_string_free, }; const swoole::Allocator *sw_php_allocator() { return &php_allocator; } const swoole::Allocator *sw_zend_string_allocator() { return &zend_string_allocator; } void sw_php_exit(int status) { EG(exit_status) = status; #ifdef SW_THREAD php_swoole_thread_bailout(); #else zend_bailout(); #endif } bool sw_zval_is_serializable(const zval *struc) { again: switch (Z_TYPE_P(struc)) { case IS_OBJECT: { if (Z_OBJCE_P(struc)->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { return false; } break; } case IS_ARRAY: { zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(struc), elem) { if (!sw_zval_is_serializable(elem)) { return false; } } ZEND_HASH_FOREACH_END(); break; } case IS_REFERENCE: struc = Z_REFVAL_P(struc); goto again; default: break; } return true; } static void sw_after_fork(void *args) { #if PHP_VERSION_ID >= 80500 refresh_memory_manager(); #endif #ifdef ZEND_MAX_EXECUTION_TIMERS zend_max_execution_timer_init(); #endif } PHP_RINIT_FUNCTION(swoole) { if (!SWOOLE_G(cli)) { return SUCCESS; } SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_BEGIN; // Use `sys_get_temp_dir` to obtain the system temporary file directory. // The default temporary directory is `/tmp`, which does not exist on the Android platform. // The system root path is a read-only mapping. // The temporary file directory under termux is `/data/data/com.termux/files/usr/tmp` swoole_set_task_tmpdir(php_get_temporary_directory()); php_swoole_register_shutdown_function("swoole_internal_call_user_shutdown_begin"); if (SWOOLE_G(enable_library) #ifdef ZEND_COMPILE_PRELOAD /* avoid execution of the code during RINIT of preloader */ && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) #endif ) { // https://github.com/swoole/swoole-src/issues/5182 /** * xdebug will hook zend_execute_ex to xdebug_execute_ex. * This would cause php_swoole_load_library function not to execute correctly, so it must be replaced * with the execute_ex function. */ void (*old_zend_execute_ex)(zend_execute_data * execute_data) = nullptr; if (UNEXPECTED(zend_execute_ex != execute_ex)) { old_zend_execute_ex = zend_execute_ex; zend_execute_ex = execute_ex; } php_swoole_load_library(); if (UNEXPECTED(old_zend_execute_ex)) { zend_execute_ex = old_zend_execute_ex; old_zend_execute_ex = nullptr; } } #ifdef ZEND_SIGNALS /* Disable warning even in ZEND_DEBUG because we may register our own signal handlers */ SIGG(check) = false; #endif swoole_add_hook(SW_GLOBAL_HOOK_AFTER_FORK, sw_after_fork, 0); php_swoole_http_server_rinit(); php_swoole_coroutine_rinit(); php_swoole_runtime_rinit(); #ifdef SW_USE_ORACLE php_swoole_oracle_rinit(); #endif #ifdef SW_THREAD php_swoole_thread_rinit(); #endif php_swoole_tracer_rinit(); SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_END; return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(swoole) { if (!SWOOLE_G(cli)) { return SUCCESS; } SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_BEGIN; php_swoole_server_rshutdown(); php_swoole_http_server_rshutdown(); php_swoole_http_response_rshutdown(); php_swoole_async_coro_rshutdown(); php_swoole_redis_server_rshutdown(); php_swoole_coroutine_rshutdown(); php_swoole_coroutine_scheduler_rshutdown(); php_swoole_timer_rshutdown(); php_swoole_runtime_rshutdown(); php_swoole_process_rshutdown(); #ifdef SW_THREAD php_swoole_thread_rshutdown(); #endif php_swoole_tracer_rshutdown(); swoole_event_free(); SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_END; #ifdef PHP_STREAM_FLAG_NO_CLOSE auto php_swoole_set_stdio_no_close = [](const char *name, size_t name_len) { zval *zstream = zend_get_constant_str(name, name_len); if (!zstream) { return; } auto *stream = (php_stream *) zend_fetch_resource2_ex((zstream), nullptr, php_file_le_stream(), php_file_le_pstream()); if (!stream) { return; } stream->flags |= PHP_STREAM_FLAG_NO_CLOSE; }; /* do not close the stdout and stderr */ php_swoole_set_stdio_no_close(ZEND_STRL("STDOUT")); php_swoole_set_stdio_no_close(ZEND_STRL("STDERR")); #endif return SUCCESS; } static PHP_FUNCTION(swoole_version) { RETURN_STRING(SWOOLE_VERSION); } static uint32_t hashkit_one_at_a_time(const char *key, size_t key_length) { const char *ptr = key; uint32_t value = 0; while (key_length--) { auto val = (uint32_t) *ptr++; value += val; value += (value << 10); value ^= (value >> 6); } value += (value << 3); value ^= (value >> 11); value += (value << 15); return value; } static PHP_FUNCTION(swoole_hashcode) { char *data; size_t l_data; zend_long type = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(data, l_data) Z_PARAM_OPTIONAL Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); switch (type) { case 0: RETURN_LONG(zend_hash_func(data, l_data)); case 1: RETURN_LONG(hashkit_one_at_a_time(data, l_data)); default: RETURN_FALSE; } } static PHP_FUNCTION(swoole_clear_error) { swoole_set_last_error(0); } PHP_FUNCTION(swoole_last_error) { RETURN_LONG(swoole_get_last_error()); } static PHP_FUNCTION(swoole_cpu_num) { RETURN_LONG(SW_CPU_NUM); } static PHP_FUNCTION(swoole_strerror) { zend_long swoole_errno; zend_long error_type = SW_STRERROR_SYSTEM; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(swoole_errno) Z_PARAM_OPTIONAL Z_PARAM_LONG(error_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (error_type == SW_STRERROR_GAI) { RETURN_STRING(gai_strerror(swoole_errno)); } else if (error_type == SW_STRERROR_DNS) { RETURN_STRING(hstrerror(swoole_errno)); } else if (error_type == SW_STRERROR_SWOOLE || (swoole_errno > SW_ERROR_BEGIN && swoole_errno < SW_ERROR_END)) { RETURN_STRING(swoole_strerror(swoole_errno)); } else { RETURN_STRING(strerror(swoole_errno)); } } static PHP_FUNCTION(swoole_error_log) { char *msg; size_t l_msg; zend_long level; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(level) Z_PARAM_STRING(msg, l_msg) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); sw_logger()->put(level, msg, l_msg); } static PHP_FUNCTION(swoole_error_log_ex) { char *msg; size_t l_msg; zend_long level, error; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_LONG(level) Z_PARAM_LONG(error) Z_PARAM_STRING(msg, l_msg) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_error_log(level, (int) error, "%.*s", (int) l_msg, msg); } static PHP_FUNCTION(swoole_ignore_error) { zend_long error; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(error) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_ignore_error(error); } static PHP_FUNCTION(swoole_mime_type_add) { zend_string *suffix; zend_string *mime_type; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR(suffix) Z_PARAM_STR(mime_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(swoole::mime_type::add(ZSTR_VAL(suffix), ZSTR_VAL(mime_type))); } static PHP_FUNCTION(swoole_mime_type_set) { zend_string *suffix; zend_string *mime_type; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STR(suffix) Z_PARAM_STR(mime_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole::mime_type::set(ZSTR_VAL(suffix), ZSTR_VAL(mime_type)); } static PHP_FUNCTION(swoole_mime_type_delete) { zend_string *suffix; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(suffix) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(swoole::mime_type::del(ZSTR_VAL(suffix))); } static PHP_FUNCTION(swoole_mime_type_get) { zend_string *filename; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(filename) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_STRING(swoole::mime_type::get(ZSTR_VAL(filename)).c_str()); } static PHP_FUNCTION(swoole_mime_type_exists) { zend_string *filename; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(filename) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(swoole::mime_type::exists(ZSTR_VAL(filename))); } static PHP_FUNCTION(swoole_mime_type_list) { array_init(return_value); for (auto &i : swoole::mime_type::list()) { add_next_index_string(return_value, i.second.c_str()); } } static PHP_FUNCTION(swoole_errno) { RETURN_LONG(errno); } PHP_FUNCTION(swoole_set_process_name) { auto *cli_set_process_title = zend::get_function(ZEND_STRL("cli_set_process_title")); if (!cli_set_process_title) { php_swoole_fatal_error(E_WARNING, "swoole_set_process_name only support in CLI mode"); RETURN_FALSE; } cli_set_process_title->internal_function.handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); } static PHP_FUNCTION(swoole_get_local_ip) { struct ifaddrs *ipaddrs; if (getifaddrs(&ipaddrs) != 0) { php_swoole_sys_error(E_WARNING, "getifaddrs() failed"); RETURN_FALSE; } zend_long family = AF_INET; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(family) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); array_init(return_value); for (struct ifaddrs *ifa = ipaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr || !(ifa->ifa_flags & IFF_UP)) { continue; } if (ifa->ifa_addr->sa_family != family) { continue; } swoole::network::Address addr{}; switch (ifa->ifa_addr->sa_family) { case AF_INET: addr.type = SW_SOCK_TCP; memcpy(&addr.addr.inet_v4, ifa->ifa_addr, sizeof(addr.addr.inet_v4)); break; case AF_INET6: addr.type = SW_SOCK_TCP6; memcpy(&addr.addr.inet_v6, ifa->ifa_addr, sizeof(addr.addr.inet_v6)); break; default: continue; } if (!addr.is_loopback_addr()) { add_assoc_string(return_value, ifa->ifa_name, addr.get_addr()); } } freeifaddrs(ipaddrs); } static PHP_FUNCTION(swoole_get_local_mac) { auto add_assoc_address = [](zval *zv, const char *name, const unsigned char *addr) { char buf[32]; sw_snprintf( SW_STRS(buf), "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); add_assoc_string(zv, name, buf); }; #ifdef SIOCGIFHWADDR char buffer[4096]; struct ifconf ifc; struct ifreq *ifr; int sock; int num_interfaces; if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { php_swoole_sys_error(E_WARNING, "socket creation failed"); RETURN_FALSE; } ifc.ifc_len = sizeof(buffer); ifc.ifc_buf = buffer; if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { php_swoole_sys_error(E_WARNING, "ioctl SIOCGIFCONF failed"); close(sock); RETURN_FALSE; } array_init(return_value); ifr = ifc.ifc_req; num_interfaces = ifc.ifc_len / sizeof(struct ifreq); for (int i = 0; i < num_interfaces; i++) { struct ifreq temp_ifr; strncpy(temp_ifr.ifr_name, ifr[i].ifr_name, IFNAMSIZ - 1); temp_ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if (ioctl(sock, SIOCGIFFLAGS, &temp_ifr) < 0) { continue; } if (temp_ifr.ifr_flags & IFF_LOOPBACK) { continue; } if (ioctl(sock, SIOCGIFHWADDR, &temp_ifr) == 0) { add_assoc_address(return_value, temp_ifr.ifr_name, (unsigned char *) temp_ifr.ifr_hwaddr.sa_data); } } close(sock); #else #ifdef LLADDR ifaddrs *ifas, *ifa; if (getifaddrs(&ifas) != 0) { php_swoole_sys_error(E_WARNING, "getifaddrs failed"); RETURN_FALSE; } array_init(return_value); for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr && !(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr->sa_family == AF_LINK) { struct sockaddr_dl *sdl = (struct sockaddr_dl *) ifa->ifa_addr; if (sdl->sdl_alen == 6) { add_assoc_address(return_value, ifa->ifa_name, (unsigned char *) LLADDR(sdl)); } } } freeifaddrs(ifas); #else php_error_docref(nullptr, E_WARNING, "swoole_get_local_mac is not supported on this platform"); RETURN_FALSE; #endif #endif } static PHP_FUNCTION(swoole_internal_call_user_shutdown_begin) { if (SWOOLE_G(req_status) == PHP_SWOOLE_RINIT_END) { SWOOLE_G(req_status) = PHP_SWOOLE_CALL_USER_SHUTDOWNFUNC_BEGIN; RETURN_TRUE; } else { php_error_docref(nullptr, E_WARNING, "can not call this function in user level"); RETURN_FALSE; } } static PHP_FUNCTION(swoole_substr_unserialize) { zend_long offset, length = 0; char *buf = nullptr; size_t buf_len; zval *options = nullptr; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(buf, buf_len) Z_PARAM_LONG(offset) Z_PARAM_OPTIONAL Z_PARAM_LONG(length) Z_PARAM_ARRAY(options) ZEND_PARSE_PARAMETERS_END(); if (buf_len == 0) { RETURN_FALSE; } if (offset < 0) { offset = buf_len + offset; if (offset < 0) { RETURN_FALSE; } } if ((zend_long) buf_len <= offset) { RETURN_FALSE; } if (length <= 0 || length > (zend_long) (buf_len - offset)) { length = buf_len - offset; } zend::unserialize(return_value, buf + offset, length, options ? Z_ARRVAL_P(options) : nullptr); } static PHP_FUNCTION(swoole_substr_json_decode) { zend_long offset, length = 0; char *str; size_t str_len; zend_bool assoc = false; /* return JS objects as PHP objects by default */ zend_bool assoc_null = true; zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; zend_long options = 0; ZEND_PARSE_PARAMETERS_START(2, 6) Z_PARAM_STRING(str, str_len) Z_PARAM_LONG(offset) Z_PARAM_OPTIONAL Z_PARAM_LONG(length) Z_PARAM_BOOL_EX(assoc, assoc_null, 1, 0) Z_PARAM_LONG(depth) Z_PARAM_LONG(options) ZEND_PARSE_PARAMETERS_END(); if (str_len == 0) { php_error_docref(nullptr, E_WARNING, "Non-empty string required"); RETURN_NULL(); } if (offset < 0) { offset = str_len + offset; if (offset < 0) { php_error_docref(nullptr, E_WARNING, "Offset must be not less than the negative length of the string"); RETURN_NULL(); } } if ((zend_long) str_len <= offset) { php_error_docref(nullptr, E_WARNING, "Offset must be less than the length of the string"); RETURN_NULL(); } if (length <= 0 || length > (zend_long) (str_len - offset)) { length = str_len - offset; } /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */ if (!assoc_null) { if (assoc) { options |= PHP_JSON_OBJECT_AS_ARRAY; } else { options &= ~PHP_JSON_OBJECT_AS_ARRAY; } } zend::json_decode(return_value, str + offset, length, options, depth); } /** * The implicit functions are intended solely for internal testing and will not be documented. * These functions are unsafe, do not use if you are not an internal developer. */ static PHP_FUNCTION(swoole_implicit_fn) { char *fn; size_t l_fn; zval *zargs = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(fn, l_fn) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(zargs) ZEND_PARSE_PARAMETERS_END(); if (SW_STRCASEEQ(fn, l_fn, "fatal_error")) { swoole_fatal_error(SW_ERROR_FOR_TEST, "test"); php_printf("never be executed here\n"); } else if (SW_STRCASEEQ(fn, l_fn, "bailout")) { sw_php_exit(zargs ? zval_get_long(zargs) : 95); } else if (SW_STRCASEEQ(fn, l_fn, "abort")) { abort(); } else if (SW_STRCASEEQ(fn, l_fn, "refcount")) { RETURN_LONG(zval_refcount_p(zargs)); } else if (SW_STRCASEEQ(fn, l_fn, "func_handler")) { auto fn_str = zval_get_string(zargs); auto *zf = zend::get_function(fn_str); zend_string_release(fn_str); if (zf == nullptr) { RETURN_FALSE; } printf("zif_handler=%p\n", zf->internal_function.handler); } else { zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "unknown fn '%s'", fn); } } ================================================ FILE: ext-src/php_swoole_call_stack.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Yurun | +----------------------------------------------------------------------+ */ #include "php_swoole.h" #ifdef ZEND_CHECK_STACK_LIMIT #define HOOK_PHP_CALL_STACK(callback) \ void *__stack_limit = EG(stack_limit); \ void *__stack_base = EG(stack_base); \ EG(stack_base) = (void *) 0; \ EG(stack_limit) = (void *) 0; \ callback EG(stack_limit) = __stack_limit; \ EG(stack_base) = __stack_base; #else #define HOOK_PHP_CALL_STACK(callback) callback #endif ================================================ FILE: ext-src/php_swoole_client.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #include "swoole_client.h" enum ClientFlag { SW_FLAG_KEEP = 1u << 12, SW_FLAG_ASYNC = 1u << 10, SW_FLAG_SYNC = 1u << 11, }; struct AsyncClientObject { zend::Callable *onConnect; zend::Callable *onReceive; zend::Callable *onClose; zend::Callable *onError; zend::Callable *onBufferFull; zend::Callable *onBufferEmpty; zend::Callable *onSSLReady; zval _zobject; }; typedef swoole::network::Client NetClient; struct ClientObject { NetClient *cli; #ifdef SWOOLE_SOCKETS_SUPPORT zval *zsocket; #endif AsyncClientObject *async; zend_object std; }; static inline ClientObject *php_swoole_client_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_client_handlers.offset); } static inline ClientObject *php_swoole_client_fetch_object(const zval *zobj) { return php_swoole_client_fetch_object(Z_OBJ_P(zobj)); } static inline NetClient *php_swoole_client_get_cli(const zval *zobject) { return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cli; } static inline enum swSocketType php_swoole_client_get_type(long type) { return (enum swSocketType)(type & (~SW_FLAG_SYNC) & (~SW_FLAG_ASYNC) & (~SW_FLAG_KEEP) & (~SW_SOCK_SSL)); } NetClient *php_swoole_client_get_cli_safe(const zval *zobject); void php_swoole_client_free(const zval *zobject, NetClient *cli); void php_swoole_client_async_free_object(const ClientObject *client_obj); bool php_swoole_client_check_setting(NetClient *cli, const zval *zset); void php_swoole_client_check_ssl_setting(const NetClient *cli, const zval *zset); bool php_swoole_client_enable_ssl_encryption(NetClient *cli, zval *zobject); ================================================ FILE: ext-src/php_swoole_coroutine.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | shiguangqi | | Twosee | | Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine.h" #include "swoole_coroutine_system.h" #include "zend_vm.h" #include "zend_closures.h" #include "zend_fibers.h" #include "zend_observer.h" #include #include #define SW_DEFAULT_MAX_CORO_NUM 100000 #define SW_DEFAULT_PHP_STACK_PAGE_SIZE 8192 #define SWOG ((zend_output_globals *) &OG(handlers)) SW_EXTERN_C_BEGIN PHP_METHOD(swoole_coroutine_scheduler, set); PHP_METHOD(swoole_coroutine_scheduler, getOptions); SW_EXTERN_C_END namespace zend { struct Function; } namespace swoole { struct PHPContext { typedef std::function SwapCallback; JMP_BUF *bailout; zval *vm_stack_top; zval *vm_stack_end; zend_vm_stack vm_stack; size_t vm_stack_page_size; zend_execute_data *execute_data; uint32_t jit_trace_num; zend_error_handling_t error_handling; zend_class_entry *exception_class; zend_object *exception; zend_output_globals *output_ptr; /* * for var serialize/unserialize, * coroutine switching may occur in the __sleep/__wakeup magic method of the object */ unsigned serialize_lock; struct { struct php_serialize_data *data; unsigned level; } serialize; struct { struct php_unserialize_data *data; unsigned level; } unserialize; /* for error control `@` */ bool in_silence; bool enable_scheduler; int ori_error_reporting; int tmp_error_reporting; Coroutine *co; zend_fcall_info fci; zend_fcall_info_cache fci_cache; zval return_value; zend_fiber_context *fiber_context; bool fiber_init_notified; #ifdef ZEND_CHECK_STACK_LIMIT void *stack_base; void *stack_limit; #endif std::stack *defer_tasks; SwapCallback *on_yield; SwapCallback *on_resume; SwapCallback *on_close; long pcid; zend_object *context; int64_t last_msec; size_t switch_count; }; class PHPCoroutine { public: struct Args { zend_fcall_info_cache *fci_cache; zval *argv; uint32_t argc; zval *callable; }; struct Config { uint64_t max_num; uint32_t hook_flags; bool enable_preemptive_scheduler; bool enable_deadlock_check; }; static SW_THREAD_LOCAL zend_array *options; enum HookType { HOOK_NONE = 0, HOOK_TCP = 1u << 1, HOOK_UDP = 1u << 2, HOOK_UNIX = 1u << 3, HOOK_UDG = 1u << 4, HOOK_SSL = 1u << 5, HOOK_TLS = 1u << 6, HOOK_STREAM_FUNCTION = 1u << 7, HOOK_FILE = 1u << 8, HOOK_SLEEP = 1u << 9, HOOK_PROC = 1u << 10, HOOK_CURL = 1u << 11, HOOK_NATIVE_CURL = 1u << 12, HOOK_SOCKETS = 1u << 14, HOOK_STDIO = 1u << 15, HOOK_PDO_PGSQL = 1u << 16, HOOK_PDO_ODBC = 1u << 17, HOOK_PDO_ORACLE = 1u << 18, HOOK_PDO_SQLITE = 1u << 19, HOOK_PDO_FIREBIRD = 1u << 20, HOOK_NET_FUNCTION = 1u << 21, HOOK_MONGODB = 1u << 22, #ifdef SW_USE_CURL HOOK_ALL = 0x7fffffff & ~HOOK_CURL & ~HOOK_MONGODB, #else HOOK_ALL = 0x7fffffff & ~HOOK_NATIVE_CURL & ~HOOK_MONGODB, #endif }; static const uint8_t MAX_EXEC_MSEC = 10; static void shutdown(); static long create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *callable); static PHPContext *create_context(const Args *args); static void defer(zend::Function *fci); static void deadlock_check(); static bool enable_hook(uint32_t flags); static bool disable_hook(); static void disable_unsafe_function(); static void enable_unsafe_function(); static void interrupt_thread_stop(); static inline long get_cid() { return sw_likely(activated) ? Coroutine::get_current_cid() : -1; } static inline long get_pcid(long cid = 0) { PHPContext *ctx = cid == 0 ? get_context() : get_context_by_cid(cid); return sw_likely(ctx) ? ctx->pcid : 0; } static inline long get_elapsed(long cid = 0) { return sw_likely(activated) ? Coroutine::get_elapsed(cid) : -1; } static inline PHPContext *get_context() { PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); return ctx ? ctx : &main_context; } static inline PHPContext *get_origin_context(PHPContext *ctx) { Coroutine *co = ctx->co->get_origin(); return co ? (PHPContext *) co->get_task() : &main_context; } static inline PHPContext *get_context_by_cid(long cid) { return cid == -1 ? &main_context : (PHPContext *) Coroutine::get_task_by_cid(cid); } static inline ssize_t get_stack_usage(long cid) { zend_long current_cid = PHPCoroutine::get_cid(); if (cid == 0) { cid = current_cid; } PHPContext *ctx = (PHPContext *) PHPCoroutine::get_context_by_cid(cid); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); return -1; } zend_vm_stack stack = cid == current_cid ? EG(vm_stack) : ctx->vm_stack; size_t usage = 0; while (stack) { usage += (stack->end - stack->top) * sizeof(zval); stack = stack->prev; } return usage; } static inline uint64_t get_max_num() { return config.max_num; } static inline void set_max_num(uint64_t n) { config.max_num = n; } static inline void set_deadlock_check(bool value = true) { config.enable_deadlock_check = value; } static inline bool is_schedulable(PHPContext *ctx) { return ctx->enable_scheduler && (Timer::get_absolute_msec() - ctx->last_msec > MAX_EXEC_MSEC); } static inline bool enable_scheduler() { PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); if (ctx && !ctx->enable_scheduler) { ctx->enable_scheduler = true; return true; } return false; } static inline bool disable_scheduler() { PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); if (ctx && ctx->enable_scheduler) { ctx->enable_scheduler = false; return true; } return false; } static void set_hook_flags(uint32_t flags); static inline uint32_t get_hook_flags() { return config.hook_flags; } static inline void enable_preemptive_scheduler(bool value) { config.enable_preemptive_scheduler = value; } static inline bool is_activated() { return activated; } static inline long get_execute_time(long cid = 0) { return sw_likely(activated) ? Coroutine::get_execute_time(cid) : -1; } static inline void init_main_context() { main_context.co = nullptr; main_context.fiber_context = EG(main_fiber_context); main_context.fiber_init_notified = true; save_context(&main_context); } static inline void free_main_context() { main_context = {}; } protected: static SW_THREAD_LOCAL bool activated; static SW_THREAD_LOCAL PHPContext main_context; static SW_THREAD_LOCAL Config config; static SW_THREAD_LOCAL bool interrupt_thread_running; static SW_THREAD_LOCAL std::thread interrupt_thread; static void activate(); static void deactivate(void *ptr); static void save_vm_stack(PHPContext *ctx); static void restore_vm_stack(PHPContext *ctx); static void save_og(PHPContext *ctx); static void restore_og(PHPContext *ctx); static void save_bg(PHPContext *ctx); static void restore_bg(PHPContext *ctx); static void save_context(PHPContext *ctx); static void restore_context(PHPContext *ctx); static void destroy_context(PHPContext *ctx); static bool catch_exception(); static void bailout(); static void on_yield(void *arg); static void on_resume(void *arg); static void on_close(void *arg); static void main_func(void *arg); static zend_fiber_status fiber_get_status(const PHPContext *ctx); static void fiber_context_init(PHPContext *ctx); static void fiber_context_try_destroy(const PHPContext *ctx, PHPContext *origin_ctx); static void fiber_context_switch_notify(const PHPContext *from, PHPContext *to); static void fiber_context_switch_try_notify(const PHPContext *from, PHPContext *to); #ifdef ZEND_CHECK_STACK_LIMIT static void *stack_limit(PHPContext *ctx); static void *stack_base(PHPContext *ctx); #endif static void interrupt_thread_start(); static void record_last_msec(PHPContext *ctx) { if (interrupt_thread_running) { ctx->last_msec = Timer::get_absolute_msec(); } } }; } // namespace swoole /** * for gdb */ zend_executor_globals *php_swoole_get_executor_globals(); ================================================ FILE: ext-src/php_swoole_coroutine_system.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | shiguangqi | | Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" // clang-format off BEGIN_EXTERN_C() #include "stubs/php_swoole_coroutine_system_arginfo.h" END_EXTERN_C() // clang-format on SW_EXTERN_C_BEGIN PHP_METHOD(swoole_coroutine_system, exec); PHP_METHOD(swoole_coroutine_system, sleep); PHP_METHOD(swoole_coroutine_system, fread); PHP_METHOD(swoole_coroutine_system, fgets); PHP_METHOD(swoole_coroutine_system, fwrite); PHP_METHOD(swoole_coroutine_system, statvfs); PHP_METHOD(swoole_coroutine_system, getaddrinfo); PHP_METHOD(swoole_coroutine_system, readFile); PHP_METHOD(swoole_coroutine_system, writeFile); PHP_METHOD(swoole_coroutine_system, wait); PHP_METHOD(swoole_coroutine_system, waitPid); PHP_METHOD(swoole_coroutine_system, waitSignal); PHP_METHOD(swoole_coroutine_system, waitEvent); SW_EXTERN_C_END ================================================ FILE: ext-src/php_swoole_curl.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #ifdef SW_USE_CURL #include "swoole_util.h" SW_EXTERN_C_BEGIN #include #include #if PHP_VERSION_ID >= 80400 #include "thirdparty/php84/curl/curl_private.h" #else #include "thirdparty/php/curl/curl_private.h" #endif SW_EXTERN_C_END #if LIBCURL_VERSION_NUM < 0x073800 #error "require cURL version 7.56.0 or later" #endif #include CURLcode swoole_curl_easy_perform(CURL *cp); php_curl *swoole_curl_get_handle(zval *zid, bool exclusive = true, bool required = true); void swoole_curl_easy_reset(CURL *curl); namespace swoole { namespace curl { class Multi; struct Socket { Multi *multi; network::Socket *socket; int bitmask; int sockfd; int action; bool deleted; }; struct Handle { CURL *cp; Multi *multi; /** * This is only for the swoole_curl_easy_perform function, * and it has a one-to-one relationship with the curl handle. * It must be destroyed when the curl handle is released. */ Multi *easy_multi; explicit Handle(CURL *_cp) { cp = _cp; multi = nullptr; easy_multi = nullptr; } }; Handle *get_handle(CURL *cp); Handle *create_handle(CURL *ch); void destroy_handle(CURL *ch); struct Selector { bool timer_callback = false; bool executing = false; std::unordered_set active_sockets; std::unordered_set release_sockets; }; class Multi { CURLM *multi_handle_; TimerNode *timer = nullptr; long timeout_ms_ = 0; Coroutine *co = nullptr; int running_handles_ = 0; bool defer_callback = false; Selector selector; std::unordered_map sockets; CURLcode read_info() const; int set_event(void *socket_ptr, curl_socket_t sockfd, int action); int del_event(void *socket_ptr, curl_socket_t sockfd); void selector_finish(); void selector_prepare(); bool wait_event() const { return timer || !sockets.empty(); } void add_timer(long timeout_ms) { if (timer && swoole_timer_is_available()) { swoole_timer_del(timer); } timeout_ms_ = timeout_ms; timer = swoole_timer_add(timeout_ms, false, [this](Timer *_timer, TimerNode *tnode) { timer = nullptr; callback(nullptr, 0); }); } void del_timer() { if (timer && swoole_timer_is_available()) { swoole_timer_del(timer); timeout_ms_ = -1; timer = nullptr; } } void set_timer() { long _timeout_ms = 0; curl_multi_timeout(multi_handle_, &_timeout_ms); handle_timeout(multi_handle_, _timeout_ms, this); } public: Multi(); ~Multi(); CURLM *get_multi_handle() const { return multi_handle_; } int get_running_handles() const { return running_handles_; } CURLMcode add_handle(Handle *handle); CURLMcode remove_handle(Handle *handle) const; CURLMcode perform() { return curl_multi_perform(multi_handle_, &running_handles_); } int get_event(int action) { return action == CURL_POLL_IN ? SW_EVENT_READ : SW_EVENT_WRITE; } Coroutine *check_bound_co() { if (co) { Coroutine::set_socket_bound_cid(co->get_cid()); swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, "This cURL handle is currently executing in coroutine#%ld, cannot be operated", co->get_cid()); return nullptr; } return Coroutine::get_current_safe(); } CURLcode exec(Handle *handle); long select(php_curlm *mh, double timeout = -1); void callback(Socket *curl_socket, int bitmask, int sockfd = -1); static int cb_readable(Reactor *reactor, Event *event); static int cb_writable(Reactor *reactor, Event *event); static int cb_error(Reactor *reactor, Event *event); static int handle_socket(CURL *easy, curl_socket_t s, int action, void *userp, void *socketp); static int handle_timeout(CURLM *multi, long timeout_ms, void *userp); }; }; // namespace curl } // namespace swoole #endif ================================================ FILE: ext-src/php_swoole_cxx.cc ================================================ #include "php_swoole_cxx.h" //----------------------------------known string------------------------------------ static const char *sw_known_strings[] = { #define _SW_ZEND_STR_DSC(id, str) str, SW_ZEND_KNOWN_STRINGS(_SW_ZEND_STR_DSC) #undef _SW_ZEND_STR_DSC nullptr}; SW_API zend_string **sw_zend_known_strings = nullptr; SW_API zend_refcounted *sw_refcount_ptr; zend_refcounted *sw_get_refcount_ptr(zval *value) { return (sw_refcount_ptr = value->value.counted); } //----------------------------------known string------------------------------------ namespace zend { void known_strings_init() { sw_zend_known_strings = static_cast( pemalloc(sizeof(zend_string *) * ((sizeof(sw_known_strings) / sizeof(sw_known_strings[0]) - 1)), 1)); for (unsigned int i = 0; i < (sizeof(sw_known_strings) / sizeof(sw_known_strings[0])) - 1; i++) { zend_string *str = zend_string_init(sw_known_strings[i], strlen(sw_known_strings[i]), true); sw_zend_known_strings[i] = zend_new_interned_string(str); } } void known_strings_dtor() { pefree(sw_zend_known_strings, 1); sw_zend_known_strings = nullptr; } zend_function *get_function(const zend_array *function_table, const char *name, size_t name_len) { return static_cast(zend_hash_str_find_ptr(function_table, name, name_len)); } zend_function *get_function(const char *fname, size_t fname_len) { return get_function(EG(function_table), fname, fname_len); } zend_function *get_function(const std::string &fname) { return get_function(fname.c_str(), fname.length()); } zend_function *get_function(const zend_string *fname) { return get_function(ZSTR_VAL(fname), ZSTR_LEN(fname)); } static zend_always_inline zval *sw_zend_symtable_str_add( HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key, zval *pData) { if (numeric_key) { return zend_hash_index_add(ht, idx, pData); } else { return zend_hash_str_add(ht, str, len, pData); } } static zend_always_inline zval *sw_zend_symtable_str_find( HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key) { if (numeric_key) { return zend_hash_index_find(ht, idx); } else { return zend_hash_str_find(ht, str, len); } } static zend_always_inline zval *sw_zend_symtable_str_update( HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key, zval *pData) { if (numeric_key) { return zend_hash_index_update(ht, idx, pData); } else { return zend_hash_str_update(ht, str, len, pData); } } void array_add_or_merge(zval *zarray, const char *key, size_t key_len, zval *new_element) { zend_ulong idx; bool numeric_key = ZEND_HANDLE_NUMERIC_STR(key, key_len, idx); zend_array *array = Z_ARRVAL_P(zarray); zval *zresult = sw_zend_symtable_str_add(array, key, key_len, idx, numeric_key, new_element); // Adding element failed, indicating that this key already exists and must be converted to an array if (!zresult) { zval *current_elements = sw_zend_symtable_str_find(array, key, key_len, idx, numeric_key); if (ZVAL_IS_ARRAY(current_elements)) { add_next_index_zval(current_elements, new_element); } else { zval zvalue_array; array_init_size(&zvalue_array, 2); Z_ADDREF_P(current_elements); add_next_index_zval(&zvalue_array, current_elements); add_next_index_zval(&zvalue_array, new_element); sw_zend_symtable_str_update(array, key, key_len, idx, numeric_key, &zvalue_array); } } } namespace function { bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine) { bool success; if (enable_coroutine) { if (retval) { /* the coroutine has no return value */ ZVAL_NULL(retval); } success = swoole::PHPCoroutine::create(fci_cache, argc, argv, nullptr) >= 0; } else { success = sw_zend_call_function_ex(nullptr, fci_cache, argc, argv, retval) == SUCCESS; } /* we have no chance to return to ZendVM to check the exception */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return success; } Variable call(const std::string &func_name, int argc, zval *argv) { zval function_name; ZVAL_STRINGL(&function_name, func_name.c_str(), func_name.length()); Variable retval; if (call_user_function(EG(function_table), nullptr, &function_name, &retval.value, argc, argv) != SUCCESS) { ZVAL_NULL(&retval.value); } zval_dtor(&function_name); /* we have no chance to return to ZendVM to check the exception */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return retval; } } // namespace function Callable::Callable(zval *_zfn) { ZVAL_UNDEF(&zfn); if (!zval_is_true(_zfn)) { php_swoole_fatal_error(E_WARNING, "illegal callback function"); return; } if (!sw_zend_is_callable_ex(_zfn, nullptr, 0, &fn_name, nullptr, &fcc, nullptr)) { php_swoole_fatal_error(E_WARNING, "function '%s' is not callable", fn_name); return; } zfn = *_zfn; zval_add_ref(&zfn); } Callable::~Callable() { if (!ZVAL_IS_UNDEF(&zfn)) { zval_ptr_dtor(&zfn); } if (fn_name) { efree(fn_name); } } uint32_t Callable::refcount() const { return zval_refcount_p(&zfn); } } // namespace zend ================================================ FILE: ext-src/php_swoole_cxx.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_private.h" #include "php_swoole_coroutine.h" #include "swoole_socket_impl.h" #include "swoole_protocol.h" #include "swoole_util.h" #include // clang-format off //----------------------------------Swoole known string------------------------------------ #define SW_ZEND_KNOWN_STRINGS(_) \ _(SW_ZEND_STR_TYPE, "type") \ _(SW_ZEND_STR_HOST, "host") \ _(SW_ZEND_STR_USER_AGENT, "user-agent") \ _(SW_ZEND_STR_ACCEPT, "accept") \ _(SW_ZEND_STR_CONTENT_TYPE, "content-type") \ _(SW_ZEND_STR_CONTENT_LENGTH, "content-length") \ _(SW_ZEND_STR_AUTHORIZATION, "authorization") \ _(SW_ZEND_STR_CONNECTION, "connection") \ _(SW_ZEND_STR_ACCEPT_ENCODING, "accept-encoding") \ _(SW_ZEND_STR_PORT, "port") \ _(SW_ZEND_STR_SETTING, "setting") \ _(SW_ZEND_STR_ID, "id") \ _(SW_ZEND_STR_FD, "fd") \ _(SW_ZEND_STR_SOCK, "sock") \ _(SW_ZEND_STR_PIPE, "pipe") \ _(SW_ZEND_STR_HEADERS, "headers") \ _(SW_ZEND_STR_REQUEST_METHOD, "requestMethod") \ _(SW_ZEND_STR_REQUEST_HEADERS, "requestHeaders") \ _(SW_ZEND_STR_REQUEST_BODY, "requestBody") \ _(SW_ZEND_STR_UPLOAD_FILES, "uploadFiles") \ _(SW_ZEND_STR_COOKIES, "cookies") \ _(SW_ZEND_STR_DOWNLOAD_FILE, "downloadFile") \ _(SW_ZEND_STR_DOWNLOAD_OFFSET, "downloadOffset") \ _(SW_ZEND_STR_SERVER, "server") \ _(SW_ZEND_STR_HEADER, "header") \ _(SW_ZEND_STR_GET, "get") \ _(SW_ZEND_STR_POST, "post") \ _(SW_ZEND_STR_FILES, "files") \ _(SW_ZEND_STR_TMPFILES, "tmpfiles") \ _(SW_ZEND_STR_COOKIE, "cookie") \ _(SW_ZEND_STR_METHOD, "method") \ _(SW_ZEND_STR_PATH, "path") \ _(SW_ZEND_STR_DATA, "data") \ _(SW_ZEND_STR_PIPELINE, "pipeline") \ _(SW_ZEND_STR_USE_PIPELINE_READ, "usePipelineRead") \ _(SW_ZEND_STR_TRAILER, "trailer") \ _(SW_ZEND_STR_MASTER_PID, "master_pid") \ _(SW_ZEND_STR_CALLBACK, "callback") \ _(SW_ZEND_STR_OPCODE, "opcode") \ _(SW_ZEND_STR_CODE, "code") \ _(SW_ZEND_STR_REASON, "reason") \ _(SW_ZEND_STR_FLAGS, "flags") \ _(SW_ZEND_STR_FINISH, "finish") \ _(SW_ZEND_STR_IN_COROUTINE, "in_coroutine") \ _(SW_ZEND_STR_PRIVATE_DATA, "private_data") \ _(SW_ZEND_STR_CLASS_NAME_RESOLVER, "Swoole\\NameResolver") \ _(SW_ZEND_STR_SOCKET, "socket") \ _(SW_ZEND_STR_ADDR_LOOPBACK_V4, "127.0.0.1") \ _(SW_ZEND_STR_ADDR_LOOPBACK_V6, "::1") \ _(SW_ZEND_STR_REQUEST_METHOD2, "request_method") \ _(SW_ZEND_STR_REQUEST_URI, "request_uri") \ _(SW_ZEND_STR_PATH_INFO, "path_info") \ _(SW_ZEND_STR_REQUEST_TIME, "request_time") \ _(SW_ZEND_STR_REQUEST_TIME_FLOAT, "request_time_float") \ _(SW_ZEND_STR_SERVER_PROTOCOL, "server_protocol") \ _(SW_ZEND_STR_SERVER_PORT, "server_port") \ _(SW_ZEND_STR_SERVER_ADDR, "server_addr") \ _(SW_ZEND_STR_REMOTE_PORT, "remote_port") \ _(SW_ZEND_STR_REMOTE_ADDR, "remote_addr") \ _(SW_ZEND_STR_MASTER_TIME, "master_time") \ _(SW_ZEND_STR_QUERY_STRING, "query_string") \ _(SW_ZEND_STR_HTTP10, "HTTP/1.0") \ _(SW_ZEND_STR_HTTP11, "HTTP/1.1") \ _(SW_ZEND_STR_HTTP2, "HTTP/2") \ typedef enum sw_zend_known_string_id { #define _SW_ZEND_STR_ID(id, str) id, SW_ZEND_KNOWN_STRINGS(_SW_ZEND_STR_ID) #undef _SW_ZEND_STR_ID SW_ZEND_STR_LAST_KNOWN } sw_zend_known_string_id; // clang-format on #define SW_ZSTR_KNOWN(idx) sw_zend_known_strings[idx] extern zend_string **sw_zend_known_strings; //----------------------------------Swoole known string------------------------------------ #define SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(module) \ module##_ce->create_object = [](zend_class_entry *ce) { return sw_zend_create_object(ce, &module##_handlers); } /** * It is safe across coroutines, * add reference count, prevent the socket pointer being released */ #define SW_CLIENT_GET_SOCKET_SAFE(__sock, __zsocket) \ SocketImpl *__sock = nullptr; \ zend::Variable tmp_socket; \ if (ZVAL_IS_OBJECT(__zsocket)) { \ __sock = php_swoole_get_socket(__zsocket); \ tmp_socket.assign(__zsocket); \ } #define SW_CLIENT_PRESERVE_SOCKET(__zsocket) \ zend::Variable tmp_socket; \ if (ZVAL_IS_OBJECT(__zsocket)) { \ tmp_socket.assign(__zsocket); \ } SW_API bool php_swoole_is_enable_coroutine(); SW_API zend_object *php_swoole_create_socket(enum swSocketType type); SW_API zend_object *php_swoole_create_socket_from_fd(int fd, enum swSocketType type); SW_API zend_object *php_swoole_create_socket_from_fd(int fd, int _domain, int _type, int _protocol); SW_API bool php_swoole_export_socket(zval *zobject, SocketImpl *_socket); SW_API zend_object *php_swoole_dup_socket(int fd, enum swSocketType type); SW_API void php_swoole_init_socket_object(zval *zobject, SocketImpl *socket); SW_API SocketImpl *php_swoole_get_socket(const zval *zobject); SW_API bool php_swoole_socket_is_closed(const zval *zobject); SW_API bool php_swoole_socket_set_ssl(SocketImpl *sock, const zval *zset); SW_API bool php_swoole_socket_set_protocol(SocketImpl *sock, const zval *zset); SW_API bool php_swoole_socket_set(SocketImpl *cli, const zval *zset); SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code); SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code, const char *msg); SW_API void php_swoole_socket_set_error_properties(const zval *zobject, const SocketImpl *socket); #define php_swoole_client_set php_swoole_socket_set SW_API php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, int type, int protocol STREAMS_DC); SW_API php_stream *php_swoole_create_stream_from_pipe(int fd, const char *mode, const char *persistent_id STREAMS_DC); SW_API php_stream_ops *php_swoole_get_ori_php_stream_stdio_ops(); SW_API zif_handler php_swoole_get_original_handler(const char *name, size_t len); SW_API bool php_swoole_call_original_handler(const char *name, size_t len, INTERNAL_FUNCTION_PARAMETERS); // timer SW_API bool php_swoole_timer_clear(swoole::TimerNode *tnode); SW_API bool php_swoole_timer_clear_all(); static inline bool php_swoole_is_fatal_error() { return PG(last_error_message) && (PG(last_error_type) & E_FATAL_ERRORS); } ssize_t php_swoole_length_func(const swoole::Protocol *, NetSocket *, swoole::PacketLength *); SW_API zend_long php_swoole_parse_to_size(zval *zv); SW_API zend_string *php_swoole_serialize(zval *zdata); SW_API bool php_swoole_unserialize(const zend_string *data, zval *zv); #ifdef SW_HAVE_ZLIB int php_swoole_zlib_decompress(z_stream *stream, swoole::String *buffer, char *body, int length); #endif swoole::NameResolver::Context *php_swoole_name_resolver_get_context(zval *zobject); std::string php_swoole_name_resolver_lookup(const std::string &name, swoole::NameResolver::Context *ctx, void *_resolver); bool php_swoole_name_resolver_add(zval *zresolver); const swoole::Allocator *sw_php_allocator(); const swoole::Allocator *sw_zend_string_allocator(); #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) #define SOL_TCP IPPROTO_TCP #endif #ifdef __APPLE__ #define TCP_INFO TCP_CONNECTION_INFO using tcp_info = tcp_connection_info; #endif #ifdef TCP_INFO std::unordered_map sw_socket_parse_tcp_info(tcp_info *info); #endif static inline bool php_swoole_async(bool blocking, const std::function &fn) { if (!blocking && swoole_coroutine_is_in()) { return swoole::coroutine::async(fn); } else { fn(); return true; } } namespace zend { //-----------------------------------namespace begin-------------------------------------------- class String { public: String() { str = nullptr; } String(const char *_str, size_t len) { str = zend_string_init(_str, len, false); } String(const std::string &_str) { str = zend_string_init(_str.c_str(), _str.length(), false); } String(zval *v) { str = zval_get_string(v); } String(zend_string *v, bool copy) { if (copy) { str = zend_string_copy(v); } else { str = v; } } String(const String &o) { str = zend_string_copy(o.str); } String(String &&o) noexcept { str = o.str; o.str = nullptr; } String &operator=(zval *v) { release(); str = zval_get_string(v); return *this; } String &operator=(String &&o) noexcept { release(); str = o.str; o.str = nullptr; return *this; } String &operator=(const String &o) { if (&o == this) { return *this; } release(); str = zend_string_copy(o.str); return *this; } char *val() const { return ZSTR_VAL(str); } size_t len() const { return ZSTR_LEN(str); } zend_string *get() const { return str; } void rtrim() const { ZSTR_LEN(str) = swoole::rtrim(val(), len()); } std::string to_std_string() const { return {val(), len()}; } char *dup() const { return sw_likely(len() > 0) ? sw_strndup(val(), len()) : nullptr; } char *edup() const { return sw_likely(len() > 0) ? estrndup(val(), len()) : nullptr; } void release() { if (str) { zend_string_release(str); str = nullptr; } } ~String() { release(); } private: zend_string *str; }; class KeyValue { public: zend_ulong index; zend_string *key; zval zvalue; KeyValue(zend_ulong _index, zend_string *_key, zval *_zvalue) { index = _index; key = _key ? zend_string_copy(_key) : nullptr; ZVAL_DEREF(_zvalue); zvalue = *_zvalue; Z_TRY_ADDREF(zvalue); } void add_to(zval *zarray) { HashTable *ht = Z_ARRVAL_P(zarray); zval *dest_elem = !key ? zend_hash_index_update(ht, index, &zvalue) : zend_hash_update(ht, key, &zvalue); Z_TRY_ADDREF_P(dest_elem); } ~KeyValue() { if (key) { zend_string_release(key); } zval_ptr_dtor(&zvalue); } }; class ArrayIterator { public: explicit ArrayIterator(Bucket *p) { _ptr = p; _key = _ptr->key; _val = &_ptr->val; _index = _ptr->h; pe = p; } ArrayIterator(Bucket *p, Bucket *_pe) { _ptr = p; _key = _ptr->key; _val = &_ptr->val; _index = _ptr->h; pe = _pe; skipUndefBucket(); } void operator++(int i) { ++_ptr; skipUndefBucket(); } bool operator!=(ArrayIterator b) const { return b.ptr() != _ptr; } std::string key() const { return {_key->val, _key->len}; } zend_ulong index() const { return _index; } zval *value() const { return _val; } Bucket *ptr() const { return _ptr; } private: void skipUndefBucket() { while (_ptr != pe) { _val = &_ptr->val; if (_val && Z_TYPE_P(_val) == IS_INDIRECT) { _val = Z_INDIRECT_P(_val); } if (UNEXPECTED(Z_TYPE_P(_val) == IS_UNDEF)) { ++_ptr; continue; } if (_ptr->key) { _key = _ptr->key; _index = 0; } else { _index = _ptr->h; _key = nullptr; } break; } } zval *_val; zend_string *_key; Bucket *_ptr; Bucket *pe; zend_ulong _index; }; class Array { public: zval *arr; Array(zval *_arr) { assert(Z_TYPE_P(_arr) == IS_ARRAY); arr = _arr; } size_t count() const { return zend_hash_num_elements(Z_ARRVAL_P(arr)); } bool set(zend_ulong index, zval *value) const { return add_index_zval(arr, index, value) == SUCCESS; } bool append(zval *value) const { return add_next_index_zval(arr, value) == SUCCESS; } bool set(zend_ulong index, zend_resource *res) const { zval tmp; ZVAL_RES(&tmp, res); return set(index, &tmp); } ArrayIterator begin() const { return {Z_ARRVAL_P(arr)->arData, Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed}; } ArrayIterator end() const { return ArrayIterator(Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed); } }; class Variable { public: zval value; Variable() { value = {}; } Variable(zval *zvalue) { assign(zvalue); } Variable(const char *str, size_t l_str) { ZVAL_STRINGL(&value, str, l_str); } Variable(const char *str) { ZVAL_STRING(&value, str); } Variable(const std::string &str) { ZVAL_STRINGL(&value, str.c_str(), str.length()); } Variable(const Variable &&src) noexcept { value = src.value; add_ref(); } Variable(Variable &&src) noexcept { value = src.value; src.reset(); } Variable &operator=(zval *zvalue) { assign(zvalue); return *this; } Variable &operator=(const Variable &src) { value = src.value; add_ref(); return *this; } void assign(zval *zvalue) { value = *zvalue; add_ref(); } zval *ptr() { return &value; } void reset() { ZVAL_UNDEF(&value); } void add_ref() { Z_TRY_ADDREF_P(&value); } void del_ref() { Z_TRY_DELREF_P(&value); } ~Variable() { zval_ptr_dtor(&value); } }; class CharPtr { private: char *str_; public: CharPtr() { str_ = nullptr; } CharPtr(const char *str) { str_ = estrndup(str, strlen(str)); } CharPtr(const char *str, size_t len) { str_ = estrndup(str, len); } CharPtr &operator=(const char *str) { assign(str, strlen(str)); return *this; } void release() { if (str_) { efree(str_); str_ = nullptr; } } void assign(const char *str, size_t len) { release(); str_ = estrndup(str, len); } void assign_tolower(const char *str, size_t len) { release(); str_ = zend_str_tolower_dup(str, len); } ~CharPtr() { release(); } char *get() const { return str_; } }; class Callable { private: zval zfn; zend_fcall_info_cache fcc; char *fn_name = nullptr; Callable() {} public: Callable(zval *_zfn); ~Callable(); uint32_t refcount() const; zend_refcounted *refcount_ptr() { return sw_get_refcount_ptr(&zfn); } zend_fcall_info_cache *ptr() { return &fcc; } bool ready() const { return !ZVAL_IS_UNDEF(&zfn); } Callable *dup() const { auto copy = new Callable(); copy->fcc = fcc; copy->zfn = zfn; zval_add_ref(©->zfn); if (fn_name) { copy->fn_name = estrdup(fn_name); } return copy; } bool call(uint32_t argc, zval *argv, zval *retval) { return sw_zend_call_function_ex(&zfn, &fcc, argc, argv, retval) == SUCCESS; } }; #define SW_CONCURRENCY_HASHMAP_LOCK(code) \ if (locked_) { \ code; \ } else { \ lock_.lock(); \ code; \ lock_.unlock(); \ } template class ConcurrencyHashMap { std::unordered_map map_; std::mutex lock_; bool locked_; ValueT default_value_; public: ConcurrencyHashMap(ValueT _default_value) : map_(), lock_() { default_value_ = _default_value; locked_ = false; } void set(const KeyT &key, const ValueT &value) { SW_CONCURRENCY_HASHMAP_LOCK(map_[key] = value); } ValueT get(const KeyT &key) { ValueT value; auto fn = [&]() -> ValueT { auto iter = map_.find(key); if (iter == map_.end()) { return default_value_; } return iter->second; }; SW_CONCURRENCY_HASHMAP_LOCK(value = fn()); return value; } bool exists(const KeyT &key) { std::unique_lock _lock(lock_); return map_.find(key) != map_.end(); } void del(const KeyT &key) { SW_CONCURRENCY_HASHMAP_LOCK(map_.erase(key)); } void clear() { SW_CONCURRENCY_HASHMAP_LOCK(map_.clear()); } void each(const std::function &cb) { std::unique_lock _lock(lock_); locked_ = true; for (auto &iter : map_) { cb(iter.first, iter.second); } locked_ = false; } }; namespace function { /* must use this API to call event callbacks to ensure that exceptions are handled correctly */ bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine); Variable call(const std::string &func_name, int argc, zval *argv); static inline bool call(Callable *cb, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine) { return call(cb->ptr(), argc, argv, retval, enable_coroutine); } } // namespace function struct Function { zend_fcall_info fci; zend_fcall_info_cache fci_cache; bool call(zval *retval, const bool enable_coroutine) { return function::call(&fci_cache, fci.param_count, fci.params, retval, enable_coroutine); } }; void known_strings_init(); void known_strings_dtor(); void unserialize(zval *return_value, const char *buf, size_t buf_len, HashTable *options); void json_decode(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long zend_long); zend_function *get_function(const char *fname, size_t fname_len); zend_function *get_function(const std::string &fname); zend_function *get_function(const zend_string *fname); zend_function *get_function(const zend_array *function_table, const char *name, size_t name_len); static inline zend_string *fetch_zend_string_by_val(void *val) { return (zend_string *) ((char *) val - XtOffsetOf(zend_string, val)); } static inline void assign_zend_string_by_val(zval *zdata, char *addr, size_t length) { zend_string *zstr = fetch_zend_string_by_val(addr); addr[length] = 0; zstr->len = length; ZVAL_STR(zdata, zstr); } static inline void array_set(zval *arg, const char *key, size_t l_key, zval *zvalue) { Z_TRY_ADDREF_P(zvalue); add_assoc_zval_ex(arg, key, l_key, zvalue); } static inline void array_set(zval *arg, const char *key, size_t l_key, const char *value, size_t l_value) { zval ztmp; ZVAL_STRINGL(&ztmp, value, l_value); add_assoc_zval_ex(arg, key, l_key, &ztmp); } static inline void array_set(zval *arg, const char *key, size_t l_key, bool value) { add_assoc_bool_ex(arg, key, l_key, value); } static inline void array_set(zval *arg, const char *key, size_t l_key, const char *value) { add_assoc_string_ex(arg, key, l_key, value); } static inline void array_set(zval *arg, zend_ulong index, zval *zvalue) { Z_TRY_ADDREF_P(zvalue); zend_hash_index_add(Z_ARRVAL_P(arg), index, zvalue); } static inline void array_add(zval *arg, zval *zvalue) { Z_TRY_ADDREF_P(zvalue); add_next_index_zval(arg, zvalue); } /** * return reference */ static inline zval *array_get(zval *arg, const char *key, size_t l_key) { return zend_hash_str_find(Z_ARRVAL_P(arg), key, l_key); } static inline zval *array_get(zval *arg, zend_ulong index) { return zend_hash_index_find(Z_ARRVAL_P(arg), index); } static inline void array_unset(zval *arg, const char *key, size_t l_key) { zend_hash_str_del(Z_ARRVAL_P(arg), key, l_key); } /** * Add new element to the associative array or merge with existing elements. * If the key does not exist, add it to the array. * If the key already exists, merge all into a two-dimensional array. */ void array_add_or_merge(zval *zarray, const char *key, size_t key_len, zval *new_element); static inline zend_long object_get_long(zval *obj, zend_string *key) { static zval rv; zval *property = zend_read_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), key, true, &rv); return property ? zval_get_long(property) : 0; } static inline zend_long object_get_long(zval *obj, const char *key, size_t l_key) { static zval rv; zval *property = zend_read_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), key, l_key, true, &rv); return property ? zval_get_long(property) : 0; } static inline zend_long object_get_long(zend_object *obj, const char *key, size_t l_key) { static zval rv; zval *property = zend_read_property(obj->ce, obj, key, l_key, true, &rv); return property ? zval_get_long(property) : 0; } static inline void object_set(zval *obj, const char *name, size_t l_name, zval *zvalue) { zend_update_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, zvalue); } static inline void object_set(zval *obj, const char *name, size_t l_name, const char *value) { zend_update_property_string(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, value); } static inline void object_set(zval *obj, const char *name, size_t l_name, zend_long value) { zend_update_property_long(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, value); } static inline void object_set(zend_object *obj, zend_string *name, zval *zvalue) { zend_update_property_ex(obj->ce, obj, name, zvalue); } static inline void object_set(zend_object *obj, zend_string *name, zend_string *value) { zval tmp; ZVAL_STR(&tmp, value); zend_update_property_ex(obj->ce, obj, name, &tmp); } static inline void object_set(zend_object *obj, zend_string *name, zend_long value) { zval tmp; ZVAL_LONG(&tmp, value); zend_update_property_ex(obj->ce, obj, name, &tmp); } static inline zval *object_get(zval *obj, const char *name, size_t l_name) { static zval rv; return zend_read_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, true, &rv); } /** * print exception, The virtual machine will not be terminated. */ static inline void print_error(zend_object *exception, int severity) { zend_exception_error(exception, severity); } static inline void add_constant(const char *name, zend_long value) { zend_register_long_constant(name, strlen(name), value, CONST_CS | CONST_PERSISTENT, sw_module_number()); } static inline void add_constant(const char *name, const char *value) { zend_register_string_constant(name, strlen(name), value, CONST_CS | CONST_PERSISTENT, sw_module_number()); } //-----------------------------------namespace end-------------------------------------------- } // namespace zend /* use void* to match some C callback function pointers */ static inline void sw_callable_free(void *ptr) { delete (zend::Callable *) ptr; } static inline zend::Callable *sw_callable_create(zval *zfn) { auto fn = new zend::Callable(zfn); if (fn->ready()) { return fn; } else { delete fn; return nullptr; } } static inline zend::Callable *sw_callable_create_ex(zval *zfn, const char *fname, bool allow_null = true) { if (zfn == nullptr || ZVAL_IS_NULL(zfn)) { if (!allow_null) { zend_throw_exception_ex( swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "%s must be of type callable, null given", fname); } return nullptr; } auto cb = sw_callable_create(zfn); if (!cb) { zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "%s must be of type callable, %s given", fname, zend_zval_type_name(zfn)); return nullptr; } return cb; } ================================================ FILE: ext-src/php_swoole_firebird.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #ifndef SWOOLE_SRC_PHP_SWOOLE_FIREBIRD_H #define SWOOLE_SRC_PHP_SWOOLE_FIREBIRD_H #include "php_swoole.h" #ifdef SW_USE_FIREBIRD BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" #if PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h" #else #include "thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h" #endif extern const pdo_driver_t swoole_pdo_firebird_driver; void swoole_firebird_set_blocking(bool blocking); ISC_STATUS swoole_isc_attach_database( ISC_STATUS *, short, const ISC_SCHAR *, isc_db_handle *, short, const ISC_SCHAR *); ISC_STATUS swoole_isc_detach_database(ISC_STATUS *, isc_db_handle *); ISC_STATUS swoole_isc_dsql_execute(ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const XSQLDA *); ISC_STATUS swoole_isc_dsql_execute2( ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const XSQLDA *, const XSQLDA *); ISC_STATUS swoole_isc_dsql_sql_info(ISC_STATUS *, isc_stmt_handle *, short, const ISC_SCHAR *, short, ISC_SCHAR *); ISC_STATUS swoole_isc_dsql_free_statement(ISC_STATUS *, isc_stmt_handle *, unsigned short); ISC_STATUS swoole_isc_start_transaction(ISC_STATUS *, isc_tr_handle *, short, isc_db_handle *, size_t, char *); ISC_STATUS swoole_isc_commit_retaining(ISC_STATUS *, isc_tr_handle *); ISC_STATUS swoole_isc_commit_transaction(ISC_STATUS *, isc_tr_handle *); ISC_STATUS swoole_isc_rollback_transaction(ISC_STATUS *, isc_tr_handle *); ISC_STATUS swoole_isc_dsql_allocate_statement(ISC_STATUS *, isc_db_handle *, isc_stmt_handle *); ISC_STATUS swoole_isc_dsql_prepare( ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const ISC_SCHAR *, unsigned short, XSQLDA *); ISC_STATUS swoole_isc_dsql_fetch(ISC_STATUS *, isc_stmt_handle *, unsigned short, const XSQLDA *); ISC_STATUS swoole_isc_open_blob(ISC_STATUS *, isc_db_handle *, isc_tr_handle *, isc_blob_handle *, ISC_QUAD *); ISC_STATUS swoole_isc_blob_info(ISC_STATUS *, isc_blob_handle *, short, const ISC_SCHAR *, short, ISC_SCHAR *); ISC_STATUS swoole_isc_get_segment(ISC_STATUS *, isc_blob_handle *, unsigned short *, unsigned short, ISC_SCHAR *); ISC_STATUS swoole_isc_put_segment(ISC_STATUS *, isc_blob_handle *, unsigned short, const ISC_SCHAR *); ISC_STATUS swoole_isc_close_blob(ISC_STATUS *, isc_blob_handle *); ISC_STATUS swoole_isc_create_blob(ISC_STATUS *, isc_db_handle *, isc_tr_handle *, isc_blob_handle *, ISC_QUAD *); ISC_STATUS swoole_isc_dsql_set_cursor_name(ISC_STATUS *, isc_stmt_handle *, const ISC_SCHAR *, unsigned short); ISC_STATUS swoole_fb_ping(ISC_STATUS *, isc_db_handle *); int swoole_isc_version(isc_db_handle *, ISC_VERSION_CALLBACK, void *); #ifdef SW_USE_FIREBIRD_HOOK #define isc_attach_database swoole_isc_attach_database #define isc_detach_database swoole_isc_detach_database #define isc_dsql_execute swoole_isc_dsql_execute #define isc_dsql_execute2 swoole_isc_dsql_execute2 #define isc_dsql_sql_info swoole_isc_dsql_sql_info #define isc_dsql_free_statement swoole_isc_dsql_free_statement #define isc_start_transaction swoole_isc_start_transaction #define isc_commit_retaining swoole_isc_commit_retaining #define isc_commit_transaction swoole_isc_commit_transaction #define isc_rollback_transaction swoole_isc_rollback_transaction #define isc_dsql_allocate_statement swoole_isc_dsql_allocate_statement #define isc_dsql_prepare swoole_isc_dsql_prepare #define isc_dsql_fetch swoole_isc_dsql_fetch #define isc_open_blob swoole_isc_open_blob #define isc_blob_info swoole_isc_blob_info #define isc_get_segment swoole_isc_get_segment #define isc_put_segment swoole_isc_put_segment #define isc_create_blob swoole_isc_create_blob #define isc_close_blob swoole_isc_close_blob #define isc_dsql_set_cursor_name swoole_isc_dsql_set_cursor_name #define fb_ping swoole_fb_ping #define isc_version swoole_isc_version #endif END_EXTERN_C() #endif #endif ================================================ FILE: ext-src/php_swoole_ftp_def.h ================================================ #ifndef EXT_SRC_PHP_SWOOLE_FTP_DEF_H_ #define EXT_SRC_PHP_SWOOLE_FTP_DEF_H_ #include "php.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif BEGIN_EXTERN_C() ZEND_FUNCTION(ftp_connect); #if defined(SW_HAVE_FTP_SSL) ZEND_FUNCTION(ftp_ssl_connect); #endif ZEND_FUNCTION(ftp_login); ZEND_FUNCTION(ftp_pwd); ZEND_FUNCTION(ftp_cdup); ZEND_FUNCTION(ftp_chdir); ZEND_FUNCTION(ftp_exec); ZEND_FUNCTION(ftp_raw); ZEND_FUNCTION(ftp_mkdir); ZEND_FUNCTION(ftp_rmdir); ZEND_FUNCTION(ftp_chmod); ZEND_FUNCTION(ftp_alloc); ZEND_FUNCTION(ftp_nlist); ZEND_FUNCTION(ftp_rawlist); ZEND_FUNCTION(ftp_mlsd); ZEND_FUNCTION(ftp_systype); ZEND_FUNCTION(ftp_fget); ZEND_FUNCTION(ftp_nb_fget); ZEND_FUNCTION(ftp_pasv); ZEND_FUNCTION(ftp_get); ZEND_FUNCTION(ftp_nb_get); ZEND_FUNCTION(ftp_nb_continue); ZEND_FUNCTION(ftp_fput); ZEND_FUNCTION(ftp_nb_fput); ZEND_FUNCTION(ftp_put); ZEND_FUNCTION(ftp_append); ZEND_FUNCTION(ftp_nb_put); ZEND_FUNCTION(ftp_size); ZEND_FUNCTION(ftp_mdtm); ZEND_FUNCTION(ftp_rename); ZEND_FUNCTION(ftp_delete); ZEND_FUNCTION(ftp_site); ZEND_FUNCTION(ftp_close); ZEND_FUNCTION(ftp_set_option); ZEND_FUNCTION(ftp_get_option); PHP_MINIT_FUNCTION(ftp); PHP_MINFO_FUNCTION(ftp); END_EXTERN_C() #endif ================================================ FILE: ext-src/php_swoole_http.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_http.h" #include "swoole_http2.h" #include "swoole_llhttp.h" #include "swoole_websocket.h" #include "thirdparty/multipart_parser.h" #include #include #ifdef SW_HAVE_ZLIB #include #define SW_ZLIB_ENCODING_RAW -0xf #define SW_ZLIB_ENCODING_GZIP 0x1f #define SW_ZLIB_ENCODING_DEFLATE 0x0f #define SW_ZLIB_ENCODING_ANY 0x2f #if MAX_MEM_LEVEL >= 8 #define SW_ZLIB_DEF_MEM_LEVEL 8 #else #define SW_ZLIB_DEF_MEM_LEVEL MAX_MEM_LEVEL #endif #endif #ifdef SW_HAVE_BROTLI #include #include #endif #ifdef SW_HAVE_ZSTD #include #endif #define HTTP_API #include enum swHttpHeaderFlag { HTTP_HEADER_SERVER = 1u << 1, HTTP_HEADER_CONNECTION = 1u << 2, HTTP_HEADER_CONTENT_LENGTH = 1u << 3, HTTP_HEADER_DATE = 1u << 4, HTTP_HEADER_CONTENT_TYPE = 1u << 5, HTTP_HEADER_TRANSFER_ENCODING = 1u << 6, HTTP_HEADER_ACCEPT_ENCODING = 1u << 7, HTTP_HEADER_CONTENT_ENCODING = 1u << 8, }; enum swHttpCompressMethod { HTTP_COMPRESS_NONE, HTTP_COMPRESS_GZIP, HTTP_COMPRESS_DEFLATE, HTTP_COMPRESS_BR, HTTP_COMPRESS_ZSTD, }; enum swHttpErrorStatusCode { HTTP_ESTATUS_CONNECT_FAILED = -1, HTTP_ESTATUS_REQUEST_TIMEOUT = -2, HTTP_ESTATUS_SERVER_RESET = -3, HTTP_ESTATUS_SEND_FAILED = -4, }; namespace swoole { class Server; class Coroutine; namespace http2 { class Stream; class Session; } // namespace http2 namespace http { struct Request { int version; char *path; uint32_t path_len; const char *ext; uint32_t ext_len; uint8_t post_form_urlencoded; zval zdata; const char *body_at; size_t body_length; String *chunked_body; String *h2_data_buffer; // Notice: Do not change the order zval *zobject; zval _zobject; zval *zserver; zval _zserver; zval *zheader; zval _zheader; zval *zget; zval _zget; zval *zpost; zval _zpost; zval *zcookie; zval _zcookie; zval *zfiles; zval _zfiles; zval *ztmpfiles; zval _ztmpfiles; }; struct Response { enum llhttp_method method; int version; int status; char *reason; // Notice: Do not change the order zval *zobject; zval _zobject; zval *zheader; zval _zheader; zval *zcookie; zval _zcookie; zval *ztrailer; zval _ztrailer; }; struct Context { SessionId fd; uchar completed : 1; uchar end_ : 1; uchar send_header_ : 1; #ifdef SW_HAVE_COMPRESSION uchar enable_compression : 1; uchar accept_compression : 1; uchar content_compressed : 1; #endif uchar send_chunked : 1; uchar recv_chunked : 1; uchar send_trailer_ : 1; uchar keepalive : 1; uchar websocket : 1; uchar websocket_compression : 1; uchar upgrade : 1; uchar detached : 1; uchar parse_cookie : 1; uchar parse_body : 1; uchar parse_files : 1; uchar http2 : 1; zval zsocket; uint32_t stream_id; String *write_buffer; #ifdef SW_HAVE_COMPRESSION int8_t compression_level; int8_t compression_method; uint32_t compression_min_length; std::shared_ptr> compression_types; std::shared_ptr zlib_buffer; #endif std::shared_ptr continue_frame_buffer; WebSocketSettings websocket_settings; Request request; Response response; llhttp_t parser; multipart_parser *mt_parser; uint16_t input_var_num; const char *current_header_name; size_t current_header_name_len; char *current_input_name; size_t current_input_name_len; char *current_form_data_name; size_t current_form_data_name_len; zval *current_multipart_header; const char *tmp_content_type; size_t tmp_content_type_len; String *form_data_buffer; std::string upload_tmp_dir; // The `private_data` pointer is used to store Server or CoSocket object void *private_data; // The `private_data_2` pointer is used to save callback function void *private_data_2; bool (*send)(Context *ctx, const char *data, size_t length); bool (*sendfile)(Context *ctx, zend_string *file, off_t offset, size_t length); bool (*close)(Context *ctx); bool (*onBeforeRequest)(Context *ctx); void (*onAfterResponse)(Context *ctx); void init(Server *server); void init(zval *zsock); void bind(Server *server); void bind(zval *zsock); void copy(const Context *ctx); bool init_multipart_parser(const char *boundary_str, int boundary_len); bool get_multipart_boundary( const char *at, size_t length, size_t offset, char **out_boundary_str, int *out_boundary_len); size_t parse(const char *data, size_t length); bool parse_multipart_data(const char *at, size_t length) const; HTTP_API bool set_header(const char *, size_t, zval *, bool); HTTP_API bool set_header(const char *, size_t, const char *, size_t, bool); HTTP_API bool set_header(const char *, size_t, const std::string &, bool); HTTP_API void end(zend_string *sdata, zval *return_value); HTTP_API void write(zend_string *sdata, zval *return_value); HTTP_API bool send_file(zend_string *file, off_t offset, size_t length); String *get_write_buffer(); String *get_http2_data_buffer() { String *buffer = request.h2_data_buffer; if (!buffer) { buffer = new String(SW_HTTP2_DATA_BUFFER_SIZE); request.h2_data_buffer = buffer; } return buffer; } size_t get_http2_data_length() { if (request.h2_data_buffer) { return request.h2_data_buffer->length; } else { return 0; } } size_t get_content_length() const { return parser.content_length; } bool is_co_socket() const { return !ZVAL_IS_NULL(&zsocket); } Server *get_async_server() const { return static_cast(private_data); } SocketImpl *get_co_socket() const { return static_cast(private_data); } #ifdef SW_HAVE_COMPRESSION void set_compression_method(const char *accept_encoding, size_t length); const char *get_content_encoding() const; bool compress(const char *data, size_t length); #endif bool is_available() const; void free(); private: void build_header(String *http_buffer, const char *body, size_t length); ssize_t build_trailer(String *http_buffer) const; void send_trailer(zval *return_value); }; class Cookie { bool encode_; smart_str buffer_ = {}; protected: zend_string *name = nullptr; zend_string *value = nullptr; zend_string *path = nullptr; zend_string *domain = nullptr; zend_string *sameSite = nullptr; zend_string *priority = nullptr; zend_long expires = 0; zend_bool secure = false; zend_bool httpOnly = false; zend_bool partitioned = false; public: explicit Cookie(bool _encode = true) { encode_ = _encode; } Cookie *withName(zend_string *); Cookie *withExpires(zend_long); Cookie *withSecure(zend_bool); Cookie *withHttpOnly(zend_bool); Cookie *withPartitioned(zend_bool); Cookie *withValue(zend_string *); Cookie *withPath(zend_string *); Cookie *withDomain(zend_string *); Cookie *withSameSite(zend_string *); Cookie *withPriority(zend_string *); void reset(); void toArray(zval *return_value) const; zend_string *toString(); ~Cookie(); }; } // namespace http namespace http2 { class Stream { public: http::Context *ctx; // uint8_t priority; // useless now uint32_t id; // flow control uint32_t remote_window_size; uint32_t local_window_size; Coroutine *waiting_coroutine = nullptr; Stream(const Session *client, uint32_t _id); ~Stream(); bool send_header(const String *body, bool end_stream) const; bool send_body(const String *body, bool end_stream, const std::shared_ptr &session, off_t offset = 0, size_t length = 0); bool send_end_stream_data_frame() const; bool send_trailer() const; void reset(uint32_t error_code) const; }; class Session { public: SessionId fd; std::unordered_map> streams; nghttp2_hd_inflater *inflater = nullptr; nghttp2_hd_deflater *deflater = nullptr; Settings local_settings = {}; Settings remote_settings = {}; // flow control uint32_t remote_window_size; uint32_t local_window_size; uint32_t max_body_size; uint32_t last_stream_id; bool shutting_down; bool is_coro; http::Context *default_ctx = nullptr; void *private_data = nullptr; void (*handle)(const std::shared_ptr &, const std::shared_ptr &) = nullptr; explicit Session(SessionId _fd); ~Session(); std::shared_ptr create_stream(uint32_t stream_id); std::shared_ptr get_stream(uint32_t stream_id); bool remove_stream(uint32_t stream_id); }; } // namespace http2 } // namespace swoole extern zend_class_entry *swoole_http_request_ce; extern zend_class_entry *swoole_http_response_ce; extern zend_class_entry *swoole_http_cookie_ce; swoole::http::Context *swoole_http_context_new(swoole::SessionId fd); swoole::http::Cookie *php_swoole_http_get_cooke_safety(const zval *zobject); /** * These class properties cannot be modified by the user before assignment, such as Swoole\\Http\\Request. * So we can use this function to init property. */ static sw_inline zval *swoole_http_init_and_read_property(const zend_class_entry *ce, const zval *zobject, zval **zproperty_store_pp, zend_string *name, int size = HT_MIN_SIZE) { if (UNEXPECTED(!*zproperty_store_pp)) { zval *zv = zend_hash_find(&ce->properties_info, name); auto *property_info = (zend_property_info *) Z_PTR_P(zv); zval *property = OBJ_PROP(SW_Z8_OBJ_P(zobject), property_info->offset); array_init_size(property, size); *zproperty_store_pp = (zval *) (zproperty_store_pp + 1); **zproperty_store_pp = *property; } return *zproperty_store_pp; } static sw_inline zval *swoole_http_init_and_read_property( zend_class_entry *ce, const zval *zobject, zval **zproperty_store_pp, const char *name, size_t name_len) { if (UNEXPECTED(!*zproperty_store_pp)) { // Notice: swoole http server properties can not be unset anymore, so we can read it without checking zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(zobject), name, name_len, false, &rv); array_init(property); *zproperty_store_pp = (zval *) (zproperty_store_pp + 1); **zproperty_store_pp = *property; } return *zproperty_store_pp; } static sw_inline bool swoole_http_has_crlf(const char *value, size_t length) { /* new line/NUL character safety check */ for (size_t i = 0; i < length; i++) { /* RFC 7230 ch. 3.2.4 deprecates folding support */ if (value[i] == '\n' || value[i] == '\r') { php_swoole_error(E_WARNING, "Header may not contain more than a single header, new line detected"); return true; } if (value[i] == '\0') { php_swoole_error(E_WARNING, "Header may not contain NUL bytes"); return true; } } return false; } void swoole_http_parse_cookie(zval *array, const char *at, size_t length); bool swoole_http_token_list_contains_value(const char *at, size_t length, const char *value); swoole::http::Context *php_swoole_http_request_get_context(const zval *zobject); void php_swoole_http_request_set_context(const zval *zobject, swoole::http::Context *ctx); swoole::http::Context *php_swoole_http_response_get_context(const zval *zobject); void php_swoole_http_response_set_context(const zval *zobject, swoole::http::Context *ctx); zend_string *php_swoole_http_get_date(); #ifdef SW_HAVE_ZLIB voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size); void php_zlib_free(voidpf opaque, voidpf address); #endif #ifdef SW_HAVE_BROTLI void *php_brotli_alloc(void *opaque, size_t size); void php_brotli_free(void *opaque, void *address); #endif static sw_inline nghttp2_mem *php_nghttp2_mem() { static nghttp2_mem mem = {nullptr, [](size_t size, void *mem_user_data) { return emalloc(size); }, [](void *ptr, void *mem_user_data) { return efree(ptr); }, [](size_t nmemb, size_t size, void *mem_user_data) { return ecalloc(nmemb, size); }, [](void *ptr, size_t size, void *mem_user_data) { return erealloc(ptr, size); }}; return &mem; } namespace swoole { namespace http2 { //-----------------------------------namespace begin-------------------------------------------- class HeaderSet { public: explicit HeaderSet(size_t size) : size(size), index(0) { nvs = (nghttp2_nv *) ecalloc(size, sizeof(nghttp2_nv)); } nghttp2_nv *get() const { return nvs; } size_t len() const { return index; } void reserve_one() { index++; } void add(size_t index, const char *name, size_t name_len, const char *value, size_t value_len, const uint8_t flags = NGHTTP2_NV_FLAG_NONE) const { if (sw_likely(index < size || nvs[index].name == nullptr)) { nghttp2_nv *nv = &nvs[index]; name = zend_str_tolower_dup(name, name_len); // auto to lower nv->name = (uchar *) name; nv->namelen = name_len; nv->value = (uchar *) emalloc(value_len); memcpy(nv->value, value, value_len); nv->valuelen = value_len; nv->flags = flags | NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE; swoole_trace_log(SW_TRACE_HTTP2, "name=(%zu)[" SW_ECHO_LEN_BLUE "], value=(%zu)[" SW_ECHO_LEN_CYAN "]", name_len, (int) name_len, name, value_len, (int) value_len, value); } else { php_swoole_fatal_error( E_WARNING, "unexpect http2 header [%.*s] (duplicated or overflow)", (int) name_len, name); } } void add(const char *name, size_t name_len, const char *value, size_t value_len, const uint8_t flags = NGHTTP2_NV_FLAG_NONE) { add(index++, name, name_len, value, value_len, flags); } ~HeaderSet() { for (size_t i = 0; i < size; ++i) { if (sw_likely(nvs[i].name /* && nvs[i].value */)) { efree((void *) nvs[i].name); efree((void *) nvs[i].value); } } efree(nvs); } private: nghttp2_nv *nvs; size_t size; size_t index; }; //-----------------------------------namespace end-------------------------------------------- } // namespace http2 } // namespace swoole ================================================ FILE: ext-src/php_swoole_http_server.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_server.h" #include "php_swoole_http.h" #include "swoole_mime_type.h" bool swoole_http_server_onBeforeRequest(swoole::http::Context *ctx); void swoole_http_server_onAfterResponse(swoole::http::Context *ctx); void swoole_http_server_populate_ip_and_port( swoole::Server *server, HashTable *ht, swoole::Connection *conn, swoole::SessionId session_id, bool keepalive); int swoole_websocket_onMessage(swoole::Server *serv, swoole::RecvData *req); int swoole_websocket_onHandshake(swoole::Server *serv, swoole::ListenPort *port, swoole::http::Context *ctx); void swoole_websocket_onBeforeHandshakeResponse(swoole::http::Context *ctx); void swoole_websocket_onOpen(swoole::http::Context *ctx); void swoole_websocket_onRequest(swoole::http::Context *ctx); bool swoole_websocket_handshake(swoole::http::Context *ctx); int swoole_http2_server_parse(const std::shared_ptr &client, const char *buf); int swoole_http2_server_onReceive(swoole::Server *serv, swoole::Connection *conn, swoole::RecvData *req); std::shared_ptr swoole_http2_server_session_new(swoole::SessionId fd); void swoole_http2_server_session_free(swoole::SessionId fd); bool swoole_http2_server_end(swoole::http::Context *ctx, zend_string *sdata); bool swoole_http2_server_write(swoole::http::Context *ctx, zend_string *sdata); bool swoole_http2_server_send_file(swoole::http::Context *ctx, zend_string *file, off_t offset, size_t length); bool swoole_http2_server_ping(swoole::http::Context *ctx); bool swoole_http2_server_goaway(swoole::http::Context *ctx, zend_long error_code, zend_string *sdata); static inline void http_server_add_server_array(HashTable *ht, zend_string *key, const char *value) { zval tmp; ZVAL_STRING(&tmp, value); zend_hash_add_new(ht, key, &tmp); } static inline void http_server_add_server_array(HashTable *ht, zend_string *key, const char *value, size_t length) { zval tmp; ZVAL_STRINGL(&tmp, value, length); zend_hash_add_new(ht, key, &tmp); } static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zend_long value) { zval tmp; ZVAL_LONG(&tmp, value); zend_hash_add_new(ht, key, &tmp); } static inline void http_server_add_server_array(HashTable *ht, zend_string *key, double value) { zval tmp; ZVAL_DOUBLE(&tmp, value); zend_hash_add_new(ht, key, &tmp); } static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zend_string *value) { zval tmp; ZVAL_STR(&tmp, value); zend_hash_add_new(ht, key, &tmp); } static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zval *value) { zend_hash_add_new(ht, key, value); } static inline void http_server_set_object_fd_property(zend_object *object, const zend_class_entry *ce, long fd) { auto *zv = zend_hash_find(&ce->properties_info, SW_ZSTR_KNOWN(SW_ZEND_STR_FD)); auto *property_info = static_cast(Z_PTR_P(zv)); auto property = OBJ_PROP(object, property_info->offset); ZVAL_LONG(property, fd); } ================================================ FILE: ext-src/php_swoole_library.h ================================================ /** * ----------------------------------------------------------------------- * Generated by build-library.php, Please DO NOT modify! +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ /* $Id: 54ad6d15fb4330384673e6d6a8317c46d0ca78a3 */ #ifndef SWOOLE_LIBRARY_H #define SWOOLE_LIBRARY_H #if PHP_VERSION_ID < 80000 typedef zval zend_source_string_t; #else typedef zend_string zend_source_string_t; #endif #if PHP_VERSION_ID < 80200 #define ZEND_COMPILE_POSITION_DC #define ZEND_COMPILE_POSITION_RELAY_C #else #define ZEND_COMPILE_POSITION_DC , zend_compile_position position #define ZEND_COMPILE_POSITION_RELAY_C , position #endif #if PHP_VERSION_ID < 80000 #define ZEND_STR_CONST #else #define ZEND_STR_CONST const #endif static zend_op_array *(*old_compile_string)(zend_source_string_t *source_string, ZEND_STR_CONST char *filename ZEND_COMPILE_POSITION_DC); static inline zend_op_array *_compile_string(zend_source_string_t *source_string, ZEND_STR_CONST char *filename ZEND_COMPILE_POSITION_DC) { if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); return NULL; } zend_op_array *opa = old_compile_string(source_string, filename ZEND_COMPILE_POSITION_RELAY_C); opa->type = ZEND_USER_FUNCTION; return opa; } static inline zend_bool _eval(const char *code, const char *filename) { if (!old_compile_string) { old_compile_string = zend_compile_string; } // overwrite zend_compile_string = _compile_string; int ret = (zend_eval_stringl((char *) code, strlen(code), NULL, (char *) filename) == SUCCESS); // recover zend_compile_string = old_compile_string; return ret; } #endif static const char* swoole_library_source_constants = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "define('SWOOLE_LIBRARY', true);\n" "\n" "!defined('CURLOPT_HEADEROPT') && define('CURLOPT_HEADEROPT', 229);\n" "!defined('CURLOPT_PROXYHEADER') && define('CURLOPT_PROXYHEADER', 10228);\n" "!defined('CURLOPT_RESOLVE') && define('CURLOPT_RESOLVE', 10203);\n" "!defined('CURLOPT_UNIX_SOCKET_PATH') && define('CURLOPT_UNIX_SOCKET_PATH', 10231);\n"; static const char* swoole_library_source_std_exec = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "use Swoole\\Coroutine\\System;\n" "\n" "function swoole_exec(string $command, &$output = null, &$returnVar = null)\n" "{\n" " $result = System::exec($command);\n" " if ($result) {\n" " $outputList = explode(PHP_EOL, $result['output']);\n" " foreach ($outputList as &$value) {\n" " $value = rtrim($value);\n" " }\n" " if (($endLine = end($outputList)) === '') {\n" " array_pop($outputList);\n" " $endLine = end($outputList);\n" " }\n" " if ($output) {\n" " $output = array_merge($output, $outputList);\n" " } else {\n" " $output = $outputList;\n" " }\n" " $returnVar = $result['code'];\n" " return $endLine;\n" " }\n" " return false;\n" "}\n" "\n" "function swoole_shell_exec(string $cmd)\n" "{\n" " $result = System::exec($cmd);\n" " if ($result && $result['output'] !== '') {\n" " return $result['output'];\n" " }\n" " return null;\n" "}\n"; static const char* swoole_library_source_core_constant = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "class Constant\n" "{\n" " /* {{{ EVENT */\n" " public const EVENT_START = 'start';\n" "\n" " public const EVENT_BEFORE_SHUTDOWN = 'beforeShutdown';\n" "\n" " public const EVENT_SHUTDOWN = 'shutdown';\n" "\n" " public const EVENT_WORKER_START = 'workerStart';\n" "\n" " public const EVENT_WORKER_STOP = 'workerStop';\n" "\n" " public const EVENT_BEFORE_RELOAD = 'beforeReload';\n" "\n" " public const EVENT_AFTER_RELOAD = 'afterReload';\n" "\n" " public const EVENT_TASK = 'task';\n" "\n" " public const EVENT_FINISH = 'finish';\n" "\n" " public const EVENT_WORKER_EXIT = 'workerExit';\n" "\n" " public const EVENT_WORKER_ERROR = 'workerError';\n" "\n" " public const EVENT_MANAGER_START = 'managerStart';\n" "\n" " public const EVENT_MANAGER_STOP = 'managerStop';\n" "\n" " public const EVENT_PIPE_MESSAGE = 'pipeMessage';\n" "\n" " public const EVENT_CONNECT = 'connect';\n" "\n" " public const EVENT_RECEIVE = 'receive';\n" "\n" " public const EVENT_CLOSE = 'close';\n" "\n" " public const EVENT_PACKET = 'packet';\n" "\n" " public const EVENT_BUFFER_FULL = 'bufferFull';\n" "\n" " public const EVENT_BUFFER_EMPTY = 'bufferEmpty';\n" "\n" " public const EVENT_REQUEST = 'request';\n" "\n" " public const EVENT_HANDSHAKE = 'handshake';\n" "\n" " public const EVENT_BEFORE_HANDSHAKE_RESPONSE = 'beforeHandshakeResponse';\n" "\n" " public const EVENT_OPEN = 'open';\n" "\n" " public const EVENT_MESSAGE = 'message';\n" "\n" " public const EVENT_DISCONNECT = 'disconnect';\n" "\n" " /* }}} EVENT */\n" "\n" " public const EVENT_ERROR = 'error';\n" "\n" " /* {{{ OPTION */\n" " public const OPTION_DEBUG_MODE = 'debug_mode';\n" "\n" " public const OPTION_TRACE_FLAGS = 'trace_flags';\n" "\n" " public const OPTION_LOG_FILE = 'log_file';\n" "\n" " public const OPTION_LOG_LEVEL = 'log_level';\n" "\n" " public const OPTION_LOG_DATE_FORMAT = 'log_date_format';\n" "\n" " public const OPTION_LOG_DATE_WITH_MICROSECONDS = 'log_date_with_microseconds';\n" "\n" " public const OPTION_LOG_ROTATION = 'log_rotation';\n" "\n" " public const OPTION_DISPLAY_ERRORS = 'display_errors';\n" "\n" " public const OPTION_DNS_SERVER = 'dns_server';\n" "\n" " /**\n" " * Socket DNS timeout in seconds.\n" " */\n" " public const OPTION_SOCKET_DNS_TIMEOUT = 'socket_dns_timeout';\n" "\n" " /**\n" " * Default socket connect timeout in seconds.\n" " */\n" " public const OPTION_SOCKET_CONNECT_TIMEOUT = 'socket_connect_timeout';\n" "\n" " /**\n" " * Default socket write timeout in seconds.\n" " *\n" " * This one works the same as option \"socket_send_timeout\", but has higher priority.\n" " *\n" " * @see Constant::OPTION_SOCKET_SEND_TIMEOUT\n" " */\n" " public const OPTION_SOCKET_WRITE_TIMEOUT = 'socket_write_timeout';\n" "\n" " /**\n" " * Default socket write timeout in seconds.\n" " *\n" " * This one works the same as option \"socket_write_timeout\", but has lower priority.\n" " *\n" " * @see Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" " */\n" " public const OPTION_SOCKET_SEND_TIMEOUT = 'socket_send_timeout';\n" "\n" " /**\n" " * Default socket read timeout in seconds.\n" " *\n" " * This one works the same as option \"socket_recv_timeout\", but has higher priority.\n" " *\n" " * @see Constant::OPTION_SOCKET_RECV_TIMEOUT\n" " */\n" " public const OPTION_SOCKET_READ_TIMEOUT = 'socket_read_timeout';\n" "\n" " /**\n" " * Default socket read timeout in seconds.\n" " *\n" " * This one works the same as option \"socket_read_timeout\", but has lower priority.\n" " *\n" " * @see Constant::OPTION_SOCKET_READ_TIMEOUT\n" " */\n" " public const OPTION_SOCKET_RECV_TIMEOUT = 'socket_recv_timeout';\n" "\n" " /**\n" " * Default socket read/write timeout in seconds.\n" " *\n" " * This one has the highest priority than the other read/write timeout options:\n" " * - Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" " * - Constant::OPTION_SOCKET_SEND_TIMEOUT\n" " * - Constant::OPTION_SOCKET_READ_TIMEOUT\n" " * - Constant::OPTION_SOCKET_RECV_TIMEOUT\n" " *\n" " * @see Constant::OPTION_SOCKET_SEND_TIMEOUT\n" " * @see Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" " * @see Constant::OPTION_SOCKET_RECV_TIMEOUT\n" " * @see Constant::OPTION_SOCKET_READ_TIMEOUT\n" " */\n" " public const OPTION_SOCKET_TIMEOUT = 'socket_timeout';\n" "\n" " public const OPTION_SOCKET_BUFFER_SIZE = 'socket_buffer_size';\n" "\n" " public const OPTION_HTTP2_HEADER_TABLE_SIZE = 'http2_header_table_size';\n" "\n" " public const OPTION_HTTP2_ENABLE_PUSH = 'http2_enable_push';\n" "\n" " public const OPTION_HTTP2_MAX_CONCURRENT_STREAMS = 'http2_max_concurrent_streams';\n" "\n" " public const OPTION_HTTP2_INIT_WINDOW_SIZE = 'http2_init_window_size';\n" "\n" " public const OPTION_HTTP2_MAX_FRAME_SIZE = 'http2_max_frame_size';\n" "\n" " public const OPTION_HTTP2_MAX_HEADER_LIST_SIZE = 'http2_max_header_list_size';\n" "\n" " public const OPTION_AIO_CORE_WORKER_NUM = 'aio_core_worker_num';\n" "\n" " public const OPTION_AIO_WORKER_NUM = 'aio_worker_num';\n" "\n" " public const OPTION_AIO_MAX_WAIT_TIME = 'aio_max_wait_time';\n" "\n" " public const OPTION_AIO_MAX_IDLE_TIME = 'aio_max_idle_time';\n" "\n" " /**\n" " * @since 6.0.0-beta\n" " */\n" " public const OPTION_IOURING_ENTRIES = 'iouring_entries';\n" "\n" " /**\n" " * @since 6.0.0-rc1\n" " */\n" " public const OPTION_IOURING_WORKERS = 'iouring_workers';\n" "\n" " /**\n" " * @since 6.0.0-rc1\n" " */\n" " public const OPTION_IOURING_FLAG = 'iouring_flag';\n" "\n" " public const OPTION_ENABLE_SIGNALFD = 'enable_signalfd';\n" "\n" " /**\n" " * @since 6.1.0\n" " */\n" " public const OPTION_ENABLE_KQUEUE = 'enable_kqueue';\n" "\n" " public const OPTION_WAIT_SIGNAL = 'wait_signal';\n" "\n" " public const OPTION_DNS_CACHE_REFRESH_TIME = 'dns_cache_refresh_time';\n" "\n" " public const OPTION_THREAD_NUM = 'thread_num';\n" "\n" " public const OPTION_MIN_THREAD_NUM = 'min_thread_num';\n" "\n" " public const OPTION_MAX_THREAD_NUM = 'max_thread_num';\n" "\n" " /**\n" " * @removed 6.1.0\n" " */\n" " public const OPTION_SOCKET_DONTWAIT = 'socket_dontwait';\n" "\n" " public const OPTION_DNS_LOOKUP_RANDOM = 'dns_lookup_random';\n" "\n" " public const OPTION_USE_ASYNC_RESOLVER = 'use_async_resolver';\n" "\n" " public const OPTION_ENABLE_COROUTINE = 'enable_coroutine';\n" "\n" " public const OPTION_SSL_PROTOCOLS = 'ssl_protocols';\n" "\n" " public const OPTION_SSL_COMPRESS = 'ssl_compress';\n" "\n" " public const OPTION_SSL_CERT_FILE = 'ssl_cert_file';\n" "\n" " public const OPTION_SSL_KEY_FILE = 'ssl_key_file';\n" "\n" " public const OPTION_SSL_PASSPHRASE = 'ssl_passphrase';\n" "\n" " public const OPTION_SSL_HOST_NAME = 'ssl_host_name';\n" "\n" " public const OPTION_SSL_VERIFY_PEER = 'ssl_verify_peer';\n" "\n" " public const OPTION_SSL_ALLOW_SELF_SIGNED = 'ssl_allow_self_signed';\n" "\n" " public const OPTION_SSL_CAFILE = 'ssl_cafile';\n" "\n" " public const OPTION_SSL_CAPATH = 'ssl_capath';\n" "\n" " public const OPTION_SSL_VERIFY_DEPTH = 'ssl_verify_depth';\n" "\n" " public const OPTION_SSL_CIPHERS = 'ssl_ciphers';\n" "\n" " public const OPTION_OPEN_EOF_CHECK = 'open_eof_check';\n" "\n" " public const OPTION_OPEN_EOF_SPLIT = 'open_eof_split';\n" "\n" " public const OPTION_PACKAGE_EOF = 'package_eof';\n" "\n" " public const OPTION_OPEN_MQTT_PROTOCOL = 'open_mqtt_protocol';\n" "\n" " public const OPTION_OPEN_LENGTH_CHECK = 'open_length_check';\n" "\n" " public const OPTION_PACKAGE_LENGTH_TYPE = 'package_length_type';\n" "\n" " public const OPTION_PACKAGE_LENGTH_OFFSET = 'package_length_offset';\n" "\n" " public const OPTION_PACKAGE_BODY_OFFSET = 'package_body_offset';\n" "\n" " public const OPTION_PACKAGE_LENGTH_FUNC = 'package_length_func';\n" "\n" " public const OPTION_PACKAGE_MAX_LENGTH = 'package_max_length';\n" "\n" " public const OPTION_BUFFER_HIGH_WATERMARK = 'buffer_high_watermark';\n" "\n" " public const OPTION_BUFFER_LOW_WATERMARK = 'buffer_low_watermark';\n" "\n" " public const OPTION_BIND_PORT = 'bind_port';\n" "\n" " public const OPTION_BIND_ADDRESS = 'bind_address';\n" "\n" " public const OPTION_OPEN_TCP_NODELAY = 'open_tcp_nodelay';\n" "\n" " public const OPTION_SOCKS5_HOST = 'socks5_host';\n" "\n" " public const OPTION_SOCKS5_PORT = 'socks5_port';\n" "\n" " public const OPTION_SOCKS5_USERNAME = 'socks5_username';\n" "\n" " public const OPTION_SOCKS5_PASSWORD = 'socks5_password';\n" "\n" " public const OPTION_HTTP_PROXY_HOST = 'http_proxy_host';\n" "\n" " public const OPTION_HTTP_PROXY_PORT = 'http_proxy_port';\n" "\n" " public const OPTION_HTTP_PROXY_USERNAME = 'http_proxy_username';\n" "\n" " public const OPTION_HTTP_PROXY_USER = 'http_proxy_user';\n" "\n" " public const OPTION_HTTP_PROXY_PASSWORD = 'http_proxy_password';\n" "\n" " public const OPTION_MAX_CORO_NUM = 'max_coro_num';\n" "\n" " public const OPTION_MAX_COROUTINE = 'max_coroutine';\n" "\n" " public const OPTION_ENABLE_DEADLOCK_CHECK = 'enable_deadlock_check';\n" "\n" " public const OPTION_HOOK_FLAGS = 'hook_flags';\n" "\n" " public const OPTION_ENABLE_PREEMPTIVE_SCHEDULER = 'enable_preemptive_scheduler';\n" "\n" " public const OPTION_C_STACK_SIZE = 'c_stack_size';\n" "\n" " public const OPTION_STACK_SIZE = 'stack_size';\n" "\n" " public const OPTION_NAME_RESOLVER = 'name_resolver';\n" "\n" " public const OPTION_DNS_CACHE_EXPIRE = 'dns_cache_expire';\n" "\n" " public const OPTION_DNS_CACHE_CAPACITY = 'dns_cache_capacity';\n" "\n" " public const OPTION_CONNECT_TIMEOUT = 'connect_timeout';\n" "\n" " public const OPTION_TIMEOUT = 'timeout';\n" "\n" " public const OPTION_MAX_RETRIES = 'max_retries';\n" "\n" " public const OPTION_DEFER = 'defer';\n" "\n" " public const OPTION_LOWERCASE_HEADER = 'lowercase_header';\n" "\n" " public const OPTION_KEEP_ALIVE = 'keep_alive';\n" "\n" " public const OPTION_WEBSOCKET_MASK = 'websocket_mask';\n" "\n" " public const OPTION_HTTP_COMPRESSION = 'http_compression';\n" "\n" " public const OPTION_BODY_DECOMPRESSION = 'body_decompression';\n" "\n" " public const OPTION_WEBSOCKET_COMPRESSION = 'websocket_compression';\n" "\n" " public const OPTION_WRITE_FUNC = 'write_func';\n" "\n" " public const OPTION_HTTP_PARSE_COOKIE = 'http_parse_cookie';\n" "\n" " public const OPTION_HTTP_PARSE_POST = 'http_parse_post';\n" "\n" " public const OPTION_HTTP_PARSE_FILES = 'http_parse_files';\n" "\n" " public const OPTION_HTTP_COMPRESSION_LEVEL = 'http_compression_level';\n" "\n" " public const OPTION_COMPRESSION_LEVEL = 'compression_level';\n" "\n" " public const OPTION_HTTP_GZIP_LEVEL = 'http_gzip_level';\n" "\n" " public const OPTION_HTTP_COMPRESSION_MIN_LENGTH = 'http_compression_min_length';\n" "\n" " public const OPTION_COMPRESSION_MIN_LENGTH = 'compression_min_length';\n" "\n" " public const OPTION_HTTP_COMPRESSION_TYPES = 'http_compression_types';\n" "\n" " public const OPTION_COMPRESSION_TYPES = 'compression_types';\n" "\n" " public const OPTION_UPLOAD_TMP_DIR = 'upload_tmp_dir';\n" "\n" " public const OPTION_ENABLE_MESSAGE_BUS = 'enable_message_bus';\n" "\n" " public const OPTION_MAX_PACKAGE_SIZE = 'max_package_size';\n" "\n" " public const OPTION_SSL = 'ssl';\n" "\n" " public const OPTION_CHROOT = 'chroot';\n" "\n" " public const OPTION_USER = 'user';\n" "\n" " public const OPTION_GROUP = 'group';\n" "\n" " public const OPTION_DAEMONIZE = 'daemonize';\n" "\n" " public const OPTION_PID_FILE = 'pid_file';\n" "\n" " public const OPTION_REACTOR_NUM = 'reactor_num';\n" "\n" " public const OPTION_SINGLE_THREAD = 'single_thread';\n" "\n" " public const OPTION_WORKER_NUM = 'worker_num';\n" "\n" " public const OPTION_MAX_WAIT_TIME = 'max_wait_time';\n" "\n" " public const OPTION_MAX_QUEUED_BYTES = 'max_queued_bytes';\n" "\n" " public const OPTION_MAX_CONCURRENCY = 'max_concurrency';\n" "\n" " public const OPTION_WORKER_MAX_CONCURRENCY = 'worker_max_concurrency';\n" "\n" " public const OPTION_SEND_TIMEOUT = 'send_timeout';\n" "\n" " public const OPTION_DISPATCH_MODE = 'dispatch_mode';\n" "\n" " public const OPTION_SEND_YIELD = 'send_yield';\n" "\n" " public const OPTION_DISPATCH_FUNC = 'dispatch_func';\n" "\n" " public const OPTION_DISCARD_TIMEOUT_REQUEST = 'discard_timeout_request';\n" "\n" " public const OPTION_ENABLE_UNSAFE_EVENT = 'enable_unsafe_event';\n" "\n" " public const OPTION_ENABLE_DELAY_RECEIVE = 'enable_delay_receive';\n" "\n" " public const OPTION_ENABLE_REUSE_PORT = 'enable_reuse_port';\n" "\n" " public const OPTION_TASK_USE_OBJECT = 'task_use_object';\n" "\n" " public const OPTION_TASK_OBJECT = 'task_object';\n" "\n" " public const OPTION_EVENT_OBJECT = 'event_object';\n" "\n" " public const OPTION_TASK_ENABLE_COROUTINE = 'task_enable_coroutine';\n" "\n" " public const OPTION_TASK_WORKER_NUM = 'task_worker_num';\n" "\n" " public const OPTION_TASK_IPC_MODE = 'task_ipc_mode';\n" "\n" " public const OPTION_TASK_TMPDIR = 'task_tmpdir';\n" "\n" " public const OPTION_TASK_MAX_REQUEST = 'task_max_request';\n" "\n" " public const OPTION_TASK_MAX_REQUEST_GRACE = 'task_max_request_grace';\n" "\n" " public const OPTION_MAX_CONNECTION = 'max_connection';\n" "\n" " public const OPTION_MAX_CONN = 'max_conn';\n" "\n" " public const OPTION_START_SESSION_ID = 'start_session_id';\n" "\n" " public const OPTION_HEARTBEAT_CHECK_INTERVAL = 'heartbeat_check_interval';\n" "\n" " public const OPTION_HEARTBEAT_IDLE_TIME = 'heartbeat_idle_time';\n" "\n" " public const OPTION_MAX_REQUEST = 'max_request';\n" "\n" " public const OPTION_MAX_REQUEST_GRACE = 'max_request_grace';\n" "\n" " public const OPTION_RELOAD_ASYNC = 'reload_async';\n" "\n" " public const OPTION_OPEN_CPU_AFFINITY = 'open_cpu_affinity';\n" "\n" " public const OPTION_CPU_AFFINITY_IGNORE = 'cpu_affinity_ignore';\n" "\n" " public const OPTION_UPLOAD_MAX_FILESIZE = 'upload_max_filesize';\n" "\n" " public const OPTION_ENABLE_STATIC_HANDLER = 'enable_static_handler';\n" "\n" " public const OPTION_DOCUMENT_ROOT = 'document_root';\n" "\n" " public const OPTION_HTTP_AUTOINDEX = 'http_autoindex';\n" "\n" " public const OPTION_HTTP_INDEX_FILES = 'http_index_files';\n" "\n" " public const OPTION_STATIC_HANDLER_LOCATIONS = 'static_handler_locations';\n" "\n" " /**\n" " * @since 6.2.0\n" " */\n" " public const OPTION_URL_REWRITE_RULES = 'url_rewrite_rules';\n" "\n" " public const OPTION_INPUT_BUFFER_SIZE = 'input_buffer_size';\n" "\n" " public const OPTION_BUFFER_INPUT_SIZE = 'buffer_input_size';\n" "\n" " public const OPTION_OUTPUT_BUFFER_SIZE = 'output_buffer_size';\n" "\n" " public const OPTION_BUFFER_OUTPUT_SIZE = 'buffer_output_size';\n" "\n" " public const OPTION_MESSAGE_QUEUE_KEY = 'message_queue_key';\n" "\n" " /**\n" " * @since 6.0.0-beta\n" " */\n" " public const OPTION_BOOTSTRAP = 'bootstrap';\n" "\n" " /**\n" " * @since 6.0.0-beta\n" " */\n" " public const OPTION_INIT_ARGUMENTS = 'init_arguments';\n" "\n" " public const OPTION_BACKLOG = 'backlog';\n" "\n" " public const OPTION_KERNEL_SOCKET_RECV_BUFFER_SIZE = 'kernel_socket_recv_buffer_size';\n" "\n" " public const OPTION_KERNEL_SOCKET_SEND_BUFFER_SIZE = 'kernel_socket_send_buffer_size';\n" "\n" " public const OPTION_TCP_DEFER_ACCEPT = 'tcp_defer_accept';\n" "\n" " public const OPTION_OPEN_TCP_KEEPALIVE = 'open_tcp_keepalive';\n" "\n" " public const OPTION_OPEN_HTTP_PROTOCOL = 'open_http_protocol';\n" "\n" " public const OPTION_OPEN_WEBSOCKET_PROTOCOL = 'open_websocket_protocol';\n" "\n" " public const OPTION_WEBSOCKET_SUBPROTOCOL = 'websocket_subprotocol';\n" "\n" " public const OPTION_OPEN_WEBSOCKET_CLOSE_FRAME = 'open_websocket_close_frame';\n" "\n" " public const OPTION_OPEN_WEBSOCKET_PING_FRAME = 'open_websocket_ping_frame';\n" "\n" " public const OPTION_OPEN_WEBSOCKET_PONG_FRAME = 'open_websocket_pong_frame';\n" "\n" " public const OPTION_OPEN_HTTP2_PROTOCOL = 'open_http2_protocol';\n" "\n" " public const OPTION_OPEN_REDIS_PROTOCOL = 'open_redis_protocol';\n" "\n" " public const OPTION_MAX_IDLE_TIME = 'max_idle_time';\n" "\n" " public const OPTION_TCP_KEEPIDLE = 'tcp_keepidle';\n" "\n" " public const OPTION_TCP_KEEPINTERVAL = 'tcp_keepinterval';\n" "\n" " public const OPTION_TCP_KEEPCOUNT = 'tcp_keepcount';\n" "\n" " public const OPTION_TCP_USER_TIMEOUT = 'tcp_user_timeout';\n" "\n" " public const OPTION_TCP_FASTOPEN = 'tcp_fastopen';\n" "\n" " public const OPTION_PACKAGE_BODY_START = 'package_body_start';\n" "\n" " public const OPTION_SSL_CLIENT_CERT_FILE = 'ssl_client_cert_file';\n" "\n" " public const OPTION_SSL_PREFER_SERVER_CIPHERS = 'ssl_prefer_server_ciphers';\n" "\n" " public const OPTION_SSL_ECDH_CURVE = 'ssl_ecdh_curve';\n" "\n" " public const OPTION_SSL_DHPARAM = 'ssl_dhparam';\n" "\n" " public const OPTION_SSL_SNI_CERTS = 'ssl_sni_certs';\n" "\n" " public const OPTION_OPEN_SSL = 'open_ssl';\n" "\n" " public const OPTION_OPEN_FASTCGI_PROTOCOL = 'open_fastcgi_protocol';\n" "\n" " public const OPTION_READ_TIMEOUT = 'read_timeout';\n" "\n" " public const OPTION_WRITE_TIMEOUT = 'write_timeout';\n" "\n" " public const OPTION_SSL_DISABLE_COMPRESSION = 'ssl_disable_compression';\n" "\n" " public const OPTION_SSL_GREASE = 'ssl_grease';\n" "\n" " public const OPTION_EXIT_CONDITION = 'exit_condition';\n" "\n" " public const OPTION_DEADLOCK_CHECK_DISABLE_TRACE = 'deadlock_check_disable_trace';\n" "\n" " public const OPTION_DEADLOCK_CHECK_LIMIT = 'deadlock_check_limit';\n" "\n" " public const OPTION_DEADLOCK_CHECK_DEPTH = 'deadlock_check_depth';\n" "\n" " public const OPTION_STATS_FILE = 'stats_file';\n" "\n" " public const OPTION_STATS_TIMER_INTERVAL = 'stats_timer_interval';\n" "\n" " public const OPTION_ADMIN_SERVER = 'admin_server';\n" "\n" " /* }}} OPTION */\n" "\n" " public const OPTION_HTTP_CLIENT_DRIVER = 'http_client_driver';\n" "}\n"; static const char* swoole_library_source_core_string_object = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "class StringObject implements \\Stringable\n" "{\n" " /**\n" " * StringObject constructor.\n" " */\n" " public function __construct(protected string $string = '')\n" " {\n" " }\n" "\n" " public function __toString(): string\n" " {\n" " return $this->string;\n" " }\n" "\n" " public static function from(string $string = ''): static\n" " {\n" " return new static($string); // @phpstan-ignore new.static\n" " }\n" "\n" " public function length(): int\n" " {\n" " return strlen($this->string);\n" " }\n" "\n" " public function indexOf(string $needle, int $offset = 0): false|int\n" " {\n" " return strpos($this->string, $needle, $offset);\n" " }\n" "\n" " public function lastIndexOf(string $needle, int $offset = 0): false|int\n" " {\n" " return strrpos($this->string, $needle, $offset);\n" " }\n" "\n" " public function pos(string $needle, int $offset = 0): false|int\n" " {\n" " return strpos($this->string, $needle, $offset);\n" " }\n" "\n" " public function rpos(string $needle, int $offset = 0): false|int\n" " {\n" " return strrpos($this->string, $needle, $offset);\n" " }\n" "\n" " public function reverse(): static\n" " {\n" " return new static(strrev($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @return false|int\n" " */\n" " public function ipos(string $needle)\n" " {\n" " return stripos($this->string, $needle);\n" " }\n" "\n" " public function lower(): static\n" " {\n" " return new static(strtolower($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function upper(): static\n" " {\n" " return new static(strtoupper($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function trim(string $characters = ''): static\n" " {\n" " if ($characters) {\n" " return new static(trim($this->string, $characters)); // @phpstan-ignore new.static\n" " }\n" " return new static(trim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @return static\n" " */\n" " public function ltrim(): self\n" " {\n" " return new static(ltrim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @return static\n" " */\n" " public function rtrim(): self\n" " {\n" " return new static(rtrim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @return static\n" " */\n" " public function substr(int $offset, ?int $length = null)\n" " {\n" " return new static(substr($this->string, $offset, $length)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function repeat(int $times): static\n" " {\n" " return new static(str_repeat($this->string, $times)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function append(mixed $str): static\n" " {\n" " return new static($this->string .= $str); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @param int|null $count\n" " */\n" " public function replace(string $search, string $replace, &$count = null): static\n" " {\n" " return new static(str_replace($search, $replace, $this->string, $count)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function startsWith(string $needle): bool\n" " {\n" " return str_starts_with($this->string, $needle);\n" " }\n" "\n" " public function endsWith(string $needle): bool\n" " {\n" " return strrpos($this->string, $needle) === (strlen($this->string) - strlen($needle));\n" " }\n" "\n" " public function equals($str, bool $strict = false): bool\n" " {\n" " if ($str instanceof StringObject) {\n" " $str = strval($str);\n" " }\n" " if ($strict) {\n" " return $this->string === $str;\n" " }\n" " return $this->string == $str;\n" " }\n" "\n" " public function contains(string $subString): bool\n" " {\n" " return str_contains($this->string, $subString);\n" " }\n" "\n" " public function split(string $delimiter, int $limit = PHP_INT_MAX): ArrayObject\n" " {\n" " return static::detectArrayType(explode($delimiter, $this->string, $limit));\n" " }\n" "\n" " public function char(int $index): string\n" " {\n" " if ($index > strlen($this->string)) {\n" " return '';\n" " }\n" " return $this->string[$index];\n" " }\n" "\n" " /**\n" " * Get a new string object by splitting the string of current object into smaller chunks.\n" " *\n" " * @param int $length The chunk length.\n" " * @param string $separator The line ending sequence.\n" " * @see https://www.php.net/chunk_split\n" " */\n" " public function chunkSplit(int $length = 76, string $separator = \"\\r\\n\"): static\n" " {\n" " return new static(chunk_split($this->string, $length, $separator)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * Convert a string to an array object of class \\Swoole\\ArrayObject.\n" " *\n" " * @param int $length Maximum length of the chunk.\n" " * @see https://www.php.net/str_split\n" " */\n" " public function chunk(int $length = 1): ArrayObject\n" " {\n" " return static::detectArrayType(str_split($this->string, $length));\n" " }\n" "\n" " public function toString(): string\n" " {\n" " return $this->string;\n" " }\n" "\n" " protected static function detectArrayType(array $value): ArrayObject\n" " {\n" " return new ArrayObject($value);\n" " }\n" "}\n"; static const char* swoole_library_source_core_multibyte_string_object = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "class MultibyteStringObject extends StringObject\n" "{\n" " public function length(): int\n" " {\n" " return mb_strlen($this->string);\n" " }\n" "\n" " public function indexOf(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" " return mb_strpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" " public function lastIndexOf(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" " return mb_strrpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" " public function pos(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" " return mb_strpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" " public function rpos(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" " return mb_strrpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" " public function ipos(string $needle, int $offset = 0, ?string $encoding = null): int|false\n" " {\n" " return mb_stripos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" " /**\n" " * @see https://www.php.net/mb_substr\n" " */\n" " public function substr(int $start, ?int $length = null, ?string $encoding = null): static\n" " {\n" " return new static(mb_substr($this->string, $start, $length, $encoding)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * {@inheritDoc}\n" " * @see https://www.php.net/mb_str_split\n" " */\n" " public function chunk(int $length = 1): ArrayObject\n" " {\n" " return static::detectArrayType(mb_str_split($this->string, $length));\n" " }\n" "}\n"; static const char* swoole_library_source_core_exception_array_key_not_exists = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Exception;\n" "\n" "class ArrayKeyNotExists extends \\RuntimeException\n" "{\n" "}\n"; static const char* swoole_library_source_core_array_object = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "use Swoole\\Exception\\ArrayKeyNotExists;\n" "\n" "class ArrayObject implements \\ArrayAccess, \\Serializable, \\Countable, \\Iterator\n" "{\n" " /**\n" " * @var array\n" " */\n" " protected $array;\n" "\n" " /**\n" " * ArrayObject constructor.\n" " */\n" " public function __construct(array $array = [])\n" " {\n" " $this->array = $array;\n" " }\n" "\n" " public function __toArray(): array\n" " {\n" " return $this->array;\n" " }\n" "\n" " public function __serialize(): array\n" " {\n" " return $this->array;\n" " }\n" "\n" " public function __unserialize(array $data): void\n" " {\n" " $this->array = $data;\n" " }\n" "\n" " public static function from(array $array = []): static\n" " {\n" " return new static($array); // @phpstan-ignore new.static\n" " }\n" "\n" " public function toArray(): array\n" " {\n" " return $this->array;\n" " }\n" "\n" " public function isEmpty(): bool\n" " {\n" " return empty($this->array);\n" " }\n" "\n" " public function count(): int\n" " {\n" " return count($this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function current()\n" " {\n" " return current($this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function key()\n" " {\n" " return key($this->array);\n" " }\n" "\n" " public function valid(): bool\n" " {\n" " return array_key_exists($this->key(), $this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function rewind()\n" " {\n" " return reset($this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function next()\n" " {\n" " return next($this->array);\n" " }\n" "\n" " /**\n" " * @return ArrayObject|StringObject\n" " */\n" " public function get(mixed $key)\n" " {\n" " if (!$this->exists($key)) {\n" " throw new ArrayKeyNotExists($key);\n" " }\n" " return static::detectType($this->array[$key]);\n" " }\n" "\n" " /**\n" " * @return ArrayObject|StringObject\n" " */\n" " public function getOr(mixed $key, mixed $default = null)\n" " {\n" " if (!$this->exists($key)) {\n" " return $default;\n" " }\n" " return static::detectType($this->array[$key]);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function last()\n" " {\n" " $key = array_key_last($this->array);\n" " if ($key === null) {\n" " return null;\n" " }\n" " return $this->get($key);\n" " }\n" "\n" " /**\n" " * @return int|string|null\n" " */\n" " public function firstKey()\n" " {\n" " return array_key_first($this->array);\n" " }\n" "\n" " /**\n" " * @return int|string|null\n" " */\n" " public function lastKey()\n" " {\n" " return array_key_last($this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function first()\n" " {\n" " $key = array_key_first($this->array);\n" " if ($key === null) {\n" " return null;\n" " }\n" " return $this->get($key);\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function set(mixed $key, mixed $value): self\n" " {\n" " $this->array[$key] = $value;\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function delete(mixed $key): self\n" " {\n" " unset($this->array[$key]);\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function remove(mixed $value, bool $strict = true, bool $loop = false): self\n" " {\n" " do {\n" " $key = $this->search($value, $strict);\n" " if ($key === false) {\n" " break;\n" " }\n" " unset($this->array[$key]);\n" " } while ($loop);\n" "\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function clear(): self\n" " {\n" " $this->array = [];\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return mixed|null\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function offsetGet(mixed $key)\n" " {\n" " if (!array_key_exists($key, $this->array)) {\n" " return null;\n" " }\n" " return $this->array[$key];\n" " }\n" "\n" " public function offsetSet(mixed $key, mixed $value): void\n" " {\n" " $this->array[$key] = $value;\n" " }\n" "\n" " public function offsetUnset(mixed $key): void\n" " {\n" " unset($this->array[$key]);\n" " }\n" "\n" " /**\n" " * @return bool\n" " */\n" " #[\\ReturnTypeWillChange]\n" " public function offsetExists(mixed $key)\n" " {\n" " return isset($this->array[$key]);\n" " }\n" "\n" " public function exists(mixed $key): bool\n" " {\n" " return array_key_exists($key, $this->array);\n" " }\n" "\n" " public function contains(mixed $value, bool $strict = true): bool\n" " {\n" " return in_array($value, $this->array, $strict);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function indexOf(mixed $value, bool $strict = true)\n" " {\n" " return $this->search($value, $strict);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function lastIndexOf(mixed $value, bool $strict = true)\n" " {\n" " $array = $this->array;\n" " for (end($array); ($currentKey = key($array)) !== null; prev($array)) {\n" " $currentValue = current($array);\n" " if ($currentValue == $value) {\n" " if ($strict && $currentValue !== $value) {\n" " continue;\n" " }\n" " break;\n" " }\n" " }\n" " return $currentKey;\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function search(mixed $needle, bool $strict = true)\n" " {\n" " return array_search($needle, $this->array, $strict);\n" " }\n" "\n" " public function join(string $glue = ''): StringObject\n" " {\n" " return self::detectStringType(implode($glue, $this->array));\n" " }\n" "\n" " public function serialize(): string\n" " {\n" " return serialize($this->array);\n" " }\n" "\n" " public function unserialize(string|\\Stringable|StringObject $string): self\n" " {\n" " $this->array = (array) unserialize((string) $string);\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return float|int\n" " */\n" " public function sum()\n" " {\n" " return array_sum($this->array);\n" " }\n" "\n" " /**\n" " * @return float|int\n" " */\n" " public function product()\n" " {\n" " return array_product($this->array);\n" " }\n" "\n" " /**\n" " * @return int\n" " */\n" " public function push(mixed $value)\n" " {\n" " return $this->pushBack($value);\n" " }\n" "\n" " /**\n" " * @return int\n" " */\n" " public function pushFront(mixed $value)\n" " {\n" " return array_unshift($this->array, $value);\n" " }\n" "\n" " public function append(...$values): ArrayObject\n" " {\n" " array_push($this->array, ...$values);\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return int\n" " */\n" " public function pushBack(mixed $value)\n" " {\n" " return array_push($this->array, $value);\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function insert(int $offset, mixed $value): self\n" " {\n" " if (is_array($value) || is_object($value) || is_null($value)) {\n" " $value = [$value];\n" " }\n" " array_splice($this->array, $offset, 0, $value);\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function pop()\n" " {\n" " return $this->popBack();\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function popFront()\n" " {\n" " return array_shift($this->array);\n" " }\n" "\n" " /**\n" " * @return mixed\n" " */\n" " public function popBack()\n" " {\n" " return array_pop($this->array);\n" " }\n" "\n" " public function slice(int $offset, ?int $length = null, bool $preserve_keys = false): static\n" " {\n" " return new static(array_slice($this->array, $offset, $length, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @return ArrayObject|mixed|StringObject\n" " */\n" " public function randomGet()\n" " {\n" " return static::detectType($this->array[array_rand($this->array, 1)]);\n" " }\n" "\n" " public function each(callable $fn): self\n" " {\n" " array_walk($this->array, $fn);\n" "\n" " return $this;\n" " }\n" "\n" " /**\n" " * @param array $args\n" " */\n" " public function map(callable $fn, ...$args): static\n" " {\n" " return new static(array_map($fn, $this->array, ...$args)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * @param null $initial\n" " * @return mixed\n" " */\n" " public function reduce(callable $fn, $initial = null)\n" " {\n" " return array_reduce($this->array, $fn, $initial);\n" " }\n" "\n" " /**\n" " * @param array $args\n" " */\n" " public function keys(...$args): static\n" " {\n" " return new static(array_keys($this->array, ...$args)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function values(): static\n" " {\n" " return new static(array_values($this->array)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function column(mixed $column_key, mixed $index = null): static\n" " {\n" " return new static(array_column($this->array, $column_key, $index)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function unique(int $sort_flags = SORT_STRING): static\n" " {\n" " return new static(array_unique($this->array, $sort_flags)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function reverse(bool $preserve_keys = false): static\n" " {\n" " return new static(array_reverse($this->array, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function chunk(int $size, bool $preserve_keys = false): static\n" " {\n" " return new static(array_chunk($this->array, $size, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * Swap keys and values in an array.\n" " */\n" " public function flip(): static\n" " {\n" " return new static(array_flip($this->array)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function filter(callable $fn, int $flag = 0): static\n" " {\n" " return new static(array_filter($this->array, $fn, $flag)); // @phpstan-ignore new.static\n" " }\n" "\n" " /**\n" " * | Function name | Sorts by | Maintains key association | Order of sort | Related functions |\n" " * | :---------------- | :------- | :-------------------------- | :-------------------------- | :---------------- |\n" " * | array_multisort() | value | associative yes, numeric no | first array or sort options | array_walk() |\n" " * | asort() | value | yes | low to high | arsort() |\n" " * | arsort() | value | yes | high to low | asort() |\n" " * | krsort() | key | yes | high to low | ksort() |\n" " * | ksort() | key | yes | low to high | asort() |\n" " * | natcasesort() | value | yes | natural, case insensitive | natsort() |\n" " * | natsort() | value | yes | natural | natcasesort() |\n" " * | rsort() | value | no | high to low | sort() |\n" " * | shuffle() | value | no | random | array_rand() |\n" " * | sort() | value | no | low to high | rsort() |\n" " * | uasort() | value | yes | user defined | uksort() |\n" " * | uksort() | key | yes | user defined | uasort() |\n" " * | usort() | value | no | user defined | uasort() |\n" " */\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function asort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " asort($this->array, $sort_flags);\n" "\n" " return $this;\n" " }\n" "\n" " public function arsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " arsort($this->array, $sort_flags);\n" "\n" " return $this;\n" " }\n" "\n" " public function krsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " krsort($this->array, $sort_flags);\n" "\n" " return $this;\n" " }\n" "\n" " public function ksort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " ksort($this->array, $sort_flags);\n" "\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function natcasesort(): self\n" " {\n" " if (natcasesort($this->array) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" " throw new \\RuntimeException('natcasesort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function natsort(): self\n" " {\n" " if (natsort($this->array) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" " throw new \\RuntimeException('natsort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return $this\n" " */\n" " public function rsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " if (rsort($this->array, $sort_flags) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" " throw new \\RuntimeException('rsort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" " public function shuffle(): self\n" " {\n" " shuffle($this->array);\n" "\n" " return $this;\n" " }\n" "\n" " public function sort(int $sort_flags = SORT_REGULAR): self\n" " {\n" " sort($this->array, $sort_flags);\n" "\n" " return $this;\n" " }\n" "\n" " public function uasort(callable $value_compare_func): self\n" " {\n" " uasort($this->array, $value_compare_func);\n" "\n" " return $this;\n" " }\n" "\n" " public function uksort(callable $value_compare_func): self\n" " {\n" " uksort($this->array, $value_compare_func);\n" "\n" " return $this;\n" " }\n" "\n" " public function usort(callable $value_compare_func): self\n" " {\n" " usort($this->array, $value_compare_func);\n" "\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return ArrayObject|mixed|StringObject\n" " */\n" " protected static function detectType(mixed $value)\n" " {\n" " if (is_string($value)) {\n" " return static::detectStringType($value);\n" " }\n" " if (is_array($value)) {\n" " return static::detectArrayType($value);\n" " }\n" " return $value;\n" " }\n" "\n" " protected static function detectStringType(string $value): StringObject\n" " {\n" " return new StringObject($value);\n" " }\n" "\n" " protected static function detectArrayType(array $value): static\n" " {\n" " return new static($value); // @phpstan-ignore new.static\n" " }\n" "}\n"; static const char* swoole_library_source_core_object_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "class ObjectProxy\n" "{\n" " /** @var object */\n" " protected $__object;\n" "\n" " public function __construct(object $object)\n" " {\n" " $this->__object = $object;\n" " }\n" "\n" " public function __getObject()\n" " {\n" " return $this->__object;\n" " }\n" "\n" " public function __get(string $name)\n" " {\n" " return $this->__object->{$name};\n" " }\n" "\n" " public function __set(string $name, $value): void\n" " {\n" " $this->__object->{$name} = $value;\n" " }\n" "\n" " public function __isset($name)\n" " {\n" " return isset($this->__object->{$name});\n" " }\n" "\n" " public function __unset(string $name): void\n" " {\n" " unset($this->__object->{$name});\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" " return $this->__object->{$name}(...$arguments);\n" " }\n" "\n" " public function __invoke(...$arguments)\n" " {\n" " /** @var mixed $object */\n" " $object = $this->__object;\n" " return $object(...$arguments);\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_wait_group = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" "class WaitGroup\n" "{\n" " protected Channel $chan;\n" "\n" " protected int $count = 0;\n" "\n" " protected bool $waiting = false;\n" "\n" " public function __construct(int $delta = 0)\n" " {\n" " $this->chan = new Channel(1);\n" " if ($delta > 0) {\n" " $this->add($delta);\n" " }\n" " }\n" "\n" " public function add(int $delta = 1): void\n" " {\n" " if ($this->waiting) {\n" " throw new \\BadMethodCallException('WaitGroup misuse: add called concurrently with wait');\n" " }\n" " $count = $this->count + $delta;\n" " if ($count < 0) {\n" " throw new \\InvalidArgumentException('WaitGroup misuse: negative counter');\n" " }\n" " $this->count = $count;\n" " }\n" "\n" " public function done(): void\n" " {\n" " $count = $this->count - 1;\n" " if ($count < 0) {\n" " throw new \\BadMethodCallException('WaitGroup misuse: negative counter');\n" " }\n" " $this->count = $count;\n" " if ($count === 0 && $this->waiting) {\n" " $this->chan->push(true);\n" " }\n" " }\n" "\n" " public function wait(float $timeout = -1): bool\n" " {\n" " if ($this->waiting) {\n" " throw new \\BadMethodCallException('WaitGroup misuse: reused before previous wait has returned');\n" " }\n" " if ($this->count > 0) {\n" " $this->waiting = true;\n" " $done = $this->chan->pop($timeout);\n" " $this->waiting = false;\n" " return $done;\n" " }\n" " return true;\n" " }\n" "\n" " public function count(): int\n" " {\n" " return $this->count;\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_server = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" "use Swoole\\Constant;\n" "use Swoole\\Coroutine;\n" "use Swoole\\Coroutine\\Server\\Connection;\n" "use Swoole\\Exception;\n" "\n" "class Server\n" "{\n" " /** @var string */\n" " public $host = '';\n" "\n" " /** @var int */\n" " public $port = 0;\n" "\n" " /** @var int */\n" " public $type = AF_INET;\n" "\n" " /** @var int */\n" " public $fd = -1;\n" "\n" " /** @var int */\n" " public $errCode = 0;\n" "\n" " /** @var array */\n" " public $setting = [];\n" "\n" " /** @var bool */\n" " protected $running = false;\n" "\n" " /** @var callable|null */\n" " protected $fn;\n" "\n" " /** @var Socket */\n" " protected $socket;\n" "\n" " /**\n" " * Server constructor.\n" " * @throws Exception\n" " */\n" " public function __construct(string $host, int $port = 0, bool $ssl = false, bool $reuse_port = false)\n" " {\n" " $_host = swoole_string($host);\n" " if ($_host->contains('::')) {\n" " $this->type = AF_INET6;\n" " } elseif ($_host->startsWith('unix:/')) {\n" " $host = $_host->substr(5)->__toString();\n" " $this->type = AF_UNIX;\n" " } else {\n" " $this->type = AF_INET;\n" " }\n" " $this->host = $host;\n" "\n" " $socket = new Socket($this->type, SOCK_STREAM, 0);\n" " if ($reuse_port and defined('SO_REUSEPORT')) {\n" " $socket->setOption(SOL_SOCKET, SO_REUSEPORT, true);\n" " }\n" " if (!$socket->bind($this->host, $port)) {\n" " throw new Exception(\"bind({$this->host}:{$port}) failed\", $socket->errCode);\n" " }\n" " if (!$socket->listen()) {\n" " throw new Exception('listen() failed', $socket->errCode);\n" " }\n" " $this->port = $socket->getsockname()['port'] ?? 0;\n" " $this->fd = $socket->fd;\n" " $this->socket = $socket;\n" " $this->setting['open_ssl'] = $ssl;\n" " }\n" "\n" " public function set(array $setting): void\n" " {\n" " $this->setting = array_merge($this->setting, $setting);\n" " }\n" "\n" " public function handle(callable $fn): void\n" " {\n" " $this->fn = $fn;\n" " }\n" "\n" " public function shutdown(): bool\n" " {\n" " $this->running = false;\n" " return $this->socket->cancel();\n" " }\n" "\n" " public function start(): bool\n" " {\n" " $this->running = true;\n" " if ($this->fn === null) {\n" " $this->errCode = SOCKET_EINVAL;\n" " return false;\n" " }\n" " $socket = $this->socket;\n" " if (!$socket->setProtocol($this->setting)) {\n" " $this->errCode = SOCKET_EINVAL;\n" " return false;\n" " }\n" "\n" " while ($this->running) { // @phpstan-ignore while.alwaysTrue\n" " $conn = null;\n" " /** @var Socket $conn */\n" " $conn = $socket->accept();\n" " if ($conn) { // @phpstan-ignore if.alwaysTrue\n" " $conn->setProtocol($this->setting);\n" " if (!empty($this->setting[Constant::OPTION_OPEN_SSL])) {\n" " $fn = static function ($fn, $connection) {\n" " /* @var $connection Connection */\n" " if (!$connection->exportSocket()->sslHandshake()) {\n" " return;\n" " }\n" " $fn($connection);\n" " };\n" " $arguments = [$this->fn, new Connection($conn)];\n" " } else {\n" " $fn = $this->fn;\n" " $arguments = [new Connection($conn)];\n" " }\n" " if (Coroutine::create($fn, ...$arguments) < 0) {\n" " goto _wait;\n" " }\n" " } else {\n" " if ($socket->errCode == SOCKET_EMFILE or $socket->errCode == SOCKET_ENFILE) {\n" " _wait:\n" " Coroutine::sleep(1);\n" " continue;\n" " }\n" " if ($socket->errCode == SOCKET_ETIMEDOUT) {\n" " continue;\n" " }\n" " if ($socket->errCode == SOCKET_ECANCELED) {\n" " break;\n" " }\n" " trigger_error(\"accept failed, Error: {$socket->errMsg}[{$socket->errCode}]\", E_USER_WARNING);\n" " break;\n" " }\n" " }\n" "\n" " return true; // @phpstan-ignore deadCode.unreachable\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_server_connection = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\Server;\n" "\n" "use Swoole\\Coroutine\\Socket;\n" "\n" "class Connection\n" "{\n" " protected $socket;\n" "\n" " public function __construct(Socket $conn)\n" " {\n" " $this->socket = $conn;\n" " }\n" "\n" " public function recv(float $timeout = 0)\n" " {\n" " return $this->socket->recvPacket($timeout);\n" " }\n" "\n" " public function send(string $data)\n" " {\n" " return $this->socket->sendAll($data);\n" " }\n" "\n" " public function close(): bool\n" " {\n" " return $this->socket->close();\n" " }\n" "\n" " public function exportSocket(): Socket\n" " {\n" " return $this->socket;\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_barrier = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" "use Swoole\\Coroutine;\n" "use Swoole\\Exception;\n" "use Swoole\\Timer;\n" "\n" "class Barrier\n" "{\n" " private int $cid = -1;\n" "\n" " private $timer = -1;\n" "\n" " private static array $cancel_list = [];\n" "\n" " public function __destruct()\n" " {\n" " if ($this->timer !== -1) {\n" " Timer::clear($this->timer);\n" " if (isset(self::$cancel_list[$this->cid])) {\n" " unset(self::$cancel_list[$this->cid]);\n" " return;\n" " }\n" " }\n" " if ($this->cid !== -1 && $this->cid !== Coroutine::getCid()) {\n" " Coroutine::resume($this->cid);\n" " } else {\n" " self::$cancel_list[$this->cid] = true;\n" " }\n" " }\n" "\n" " public static function make(): self\n" " {\n" " return new self();\n" " }\n" "\n" " /**\n" " * @param-out null $barrier\n" " */\n" " public static function wait(Barrier &$barrier, float $timeout = -1): void\n" " {\n" " if ($barrier->cid !== -1) {\n" " throw new Exception('The barrier is waiting, cannot wait again.');\n" " }\n" " $cid = Coroutine::getCid();\n" " $barrier->cid = $cid;\n" " if ($timeout > 0 && ($timeout_ms = (int) ($timeout * 1000)) > 0) {\n" " $barrier->timer = Timer::after($timeout_ms, function () use ($cid) {\n" " self::$cancel_list[$cid] = true;\n" " Coroutine::resume($cid);\n" " });\n" " }\n" " $barrier = null;\n" " if (!isset(self::$cancel_list[$cid])) {\n" " Coroutine::yield();\n" " } else {\n" " unset(self::$cancel_list[$cid]);\n" " }\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_http_client_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\Http;\n" "\n" "class ClientProxy\n" "{\n" " private array $headers;\n" "\n" " private array $cookies;\n" "\n" " public function __construct(private string $body, private int $statusCode, ?array $headers, ?array $cookies)\n" " {\n" " $this->headers = $headers ?? [];\n" " $this->cookies = $cookies ?? [];\n" " }\n" "\n" " public function getBody(): string\n" " {\n" " return $this->body;\n" " }\n" "\n" " public function getStatusCode(): int\n" " {\n" " return $this->statusCode;\n" " }\n" "\n" " public function getHeaders(): array\n" " {\n" " return $this->headers;\n" " }\n" "\n" " public function getCookies(): array\n" " {\n" " return $this->cookies;\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_http_functions = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\Http;\n" "\n" "use Swoole\\Coroutine\\Http\\Client\\Exception;\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function request(\n" " string $url,\n" " string $method,\n" " mixed $data = null,\n" " ?array $options = null,\n" " ?array $headers = null,\n" " ?array $cookies = null,\n" "): ClientProxy {\n" " $driver = swoole_library_get_option('http_client_driver');\n" " return match ($driver) {\n" " 'curl' => request_with_curl($url, $method, $data, $options, $headers, $cookies),\n" " 'stream' => request_with_stream($url, $method, $data, $options, $headers, $cookies),\n" " default => request_with_http_client($url, $method, $data, $options, $headers, $cookies),\n" " };\n" "}\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function request_with_http_client(\n" " string $url,\n" " string $method,\n" " mixed $data = null,\n" " ?array $options = null,\n" " ?array $headers = null,\n" " ?array $cookies = null,\n" "): ClientProxy {\n" " $info = parse_url($url);\n" " if (empty($info['scheme'])) {\n" " throw new Exception('The URL given is illegal [no scheme]');\n" " }\n" " if ($info['scheme'] == 'http') {\n" " $client = new Client($info['host'], swoole_array_default_value($info, 'port', 80), false);\n" " } elseif ($info['scheme'] == 'https') {\n" " $client = new Client($info['host'], swoole_array_default_value($info, 'port', 443), true);\n" " } else {\n" " throw new Exception('unknown scheme \"' . $info['scheme'] . '\"');\n" " }\n" " $client->setMethod($method);\n" " if ($data) {\n" " $client->setData($data);\n" " }\n" " $client->set($options ?: []);\n" " $client->setHeaders($headers ?: []);\n" " $client->setCookies($cookies ?: []);\n" " $request_url = swoole_array_default_value($info, 'path', '/');\n" " if (!empty($info['query'])) {\n" " $request_url .= '?' . $info['query'];\n" " }\n" " if ($client->execute($request_url)) {\n" " return new ClientProxy(\n" " $client->getBody(),\n" " $client->getStatusCode(),\n" " $client->getHeaders() ?: [],\n" " $client->getCookies() ?: []\n" " );\n" " }\n" " throw new Exception($client->errMsg, $client->errCode);\n" "}\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function request_with_curl(\n" " string $url,\n" " string $method,\n" " mixed $data = null,\n" " ?array $options = null,\n" " ?array $headers = null,\n" " ?array $cookies = null,\n" "): ClientProxy {\n" " $ch = curl_init($url);\n" " if (empty($ch)) {\n" " throw new Exception('failed to curl_init');\n" " }\n" " curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n" " curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));\n" " $responseHeaders = $responseCookies = [];\n" " curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$responseHeaders, &$responseCookies) {\n" " $len = strlen($header);\n" " $header = explode(':', $header, 2);\n" " if (count($header) < 2) {\n" " return $len;\n" " }\n" " $headerKey = strtolower(trim($header[0]));\n" " if ($headerKey == 'set-cookie') {\n" " [$k, $v] = explode('=', $header[1]);\n" " $responseCookies[$k] = $v;\n" " } else {\n" " $responseHeaders[$headerKey][] = trim($header[1]);\n" " }\n" " return $len;\n" " });\n" " if ($data) {\n" " curl_setopt($ch, CURLOPT_POSTFIELDS, $data);\n" " }\n" " if ($headers) {\n" " curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);\n" " }\n" " if ($cookies) {\n" " $cookie_str = '';\n" " foreach ($cookies as $k => $v) {\n" " $cookie_str .= \"{$k}={$v}; \";\n" " }\n" " curl_setopt($ch, CURLOPT_COOKIE, $cookie_str);\n" " }\n" " if (isset($options['timeout'])) {\n" " if (is_float($options['timeout'])) {\n" " curl_setopt($ch, CURLOPT_TIMEOUT_MS, intval($options['timeout'] * 1000));\n" " curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, intval($options['timeout'] * 1000));\n" " } else {\n" " curl_setopt($ch, CURLOPT_TIMEOUT, intval($options['timeout']));\n" " curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, intval($options['timeout']));\n" " }\n" " }\n" " if (isset($options['connect_timeout'])) {\n" " if (is_float($options['connect_timeout'])) {\n" " curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, intval($options['connect_timeout'] * 1000));\n" " } else {\n" " curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, intval($options['connect_timeout']));\n" " }\n" " }\n" " $body = curl_exec($ch);\n" " if ($body !== false) {\n" " return new ClientProxy($body, curl_getinfo($ch, CURLINFO_RESPONSE_CODE), $responseHeaders, $responseCookies);\n" " }\n" " throw new Exception(curl_error($ch), curl_errno($ch));\n" "}\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function request_with_stream(\n" " string $url,\n" " string $method,\n" " mixed $data = null,\n" " ?array $options = null,\n" " ?array $headers = null,\n" " ?array $cookies = null,\n" "): ClientProxy {\n" " $stream_options = [\n" " 'http' => [\n" " 'method' => $method,\n" " ],\n" " ];\n" " $headerStr = '';\n" " if ($headers) {\n" " foreach ($headers as $k => $v) {\n" " $headerStr .= \"{$k}: {$v}\\r\\n\";\n" " }\n" " }\n" " if ($cookies) {\n" " foreach ($cookies as $k => $v) {\n" " $headerStr .= \"Cookie: {$k}={$v}\\r\\n\";\n" " }\n" " }\n" " if (isset($options['timeout'])) {\n" " $stream_options['http']['timeout'] = intval($options['timeout']);\n" " }\n" " if ($data) {\n" " if (is_array($data)) {\n" " $headerStr .= \"Content-type: application/x-www-form-urlencoded\\r\\n\";\n" " $stream_options['http']['content'] = http_build_query($data);\n" " } else {\n" " $stream_options['http']['content'] = strval($data);\n" " }\n" " }\n" " if ($headerStr) {\n" " $stream_options['http']['header'] = $headerStr;\n" " }\n" " $body = file_get_contents($url, false, stream_context_create($stream_options));\n" " if ($body) {\n" " return new ClientProxy($body, 200, [], []);\n" " }\n" " $error = error_get_last();\n" " throw new Exception($error['message']);\n" "}\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function post(string $url, mixed $data, ?array $options = null, ?array $headers = null, ?array $cookies = null): ClientProxy\n" "{\n" " return request($url, 'POST', $data, $options, $headers, $cookies);\n" "}\n" "\n" "/**\n" " * @throws Exception\n" " */\n" "function get(string $url, ?array $options = null, ?array $headers = null, ?array $cookies = null): ClientProxy\n" "{\n" " return request($url, 'GET', null, $options, $headers, $cookies);\n" "}\n"; static const char* swoole_library_source_core_connection_pool = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "use Swoole\\Coroutine\\Channel;\n" "\n" "class ConnectionPool\n" "{\n" " public const DEFAULT_SIZE = 64;\n" "\n" " protected ?Channel $pool;\n" "\n" " /** @var callable */\n" " protected $constructor;\n" "\n" " protected int $size;\n" "\n" " protected int $num = 0;\n" "\n" " public function __construct(callable $constructor, int $size = self::DEFAULT_SIZE, protected ?string $proxy = null)\n" " {\n" " $this->pool = new Channel($this->size = $size);\n" " $this->constructor = $constructor;\n" " }\n" "\n" " public function fill(): void\n" " {\n" " while ($this->size > $this->num) {\n" " $this->make();\n" " }\n" " }\n" "\n" " /**\n" " * Get a connection from the pool.\n" " *\n" " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" " * @return mixed|false Returns a connection object from the pool, or false if the pool is full and the timeout is reached.\n" " */\n" " public function get(float $timeout = -1)\n" " {\n" " if ($this->pool === null) {\n" " throw new \\RuntimeException('Pool has been closed');\n" " }\n" " if ($this->pool->isEmpty() && $this->num < $this->size) {\n" " $this->make();\n" " }\n" " return $this->pool->pop($timeout);\n" " }\n" "\n" " public function put($connection): void\n" " {\n" " if ($this->pool === null) {\n" " return;\n" " }\n" " if ($connection !== null) {\n" " $this->pool->push($connection);\n" " } else {\n" " /* connection broken */\n" " $this->num -= 1;\n" " $this->make();\n" " }\n" " }\n" "\n" " public function close(): void\n" " {\n" " $this->pool->close();\n" " $this->pool = null;\n" " $this->num = 0;\n" " }\n" "\n" " protected function make(): void\n" " {\n" " $this->num++;\n" " try {\n" " if ($this->proxy) {\n" " $connection = new $this->proxy($this->constructor);\n" " } else {\n" " $constructor = $this->constructor;\n" " $connection = $constructor();\n" " }\n" " } catch (\\Throwable $throwable) {\n" " $this->num--;\n" " throw $throwable;\n" " }\n" " $this->put($connection);\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_object_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class ObjectProxy extends \\Swoole\\ObjectProxy\n" "{\n" " final public function __clone(): void\n" " {\n" " throw new \\Error('Trying to clone an uncloneable database proxy object');\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_config = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class MysqliConfig\n" "{\n" " protected string $host = '127.0.0.1';\n" "\n" " protected int $port = 3306;\n" "\n" " protected ?string $unixSocket;\n" "\n" " protected string $dbname = 'test';\n" "\n" " protected string $charset = 'utf8mb4';\n" "\n" " protected string $username = 'root';\n" "\n" " protected string $password = 'root';\n" "\n" " protected array $options = [];\n" "\n" " public function getHost(): string\n" " {\n" " return $this->host;\n" " }\n" "\n" " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" " }\n" "\n" " public function getPort(): int\n" " {\n" " return $this->port;\n" " }\n" "\n" " public function getUnixSocket(): ?string\n" " {\n" " return $this->unixSocket ?? null;\n" " }\n" "\n" " public function withUnixSocket(?string $unixSocket): self\n" " {\n" " $this->unixSocket = $unixSocket;\n" " return $this;\n" " }\n" "\n" " public function withPort(int $port): self\n" " {\n" " $this->port = $port;\n" " return $this;\n" " }\n" "\n" " public function getDbname(): string\n" " {\n" " return $this->dbname;\n" " }\n" "\n" " public function withDbname(string $dbname): self\n" " {\n" " $this->dbname = $dbname;\n" " return $this;\n" " }\n" "\n" " public function getCharset(): string\n" " {\n" " return $this->charset;\n" " }\n" "\n" " public function withCharset(string $charset): self\n" " {\n" " $this->charset = $charset;\n" " return $this;\n" " }\n" "\n" " public function getUsername(): string\n" " {\n" " return $this->username;\n" " }\n" "\n" " public function withUsername(string $username): self\n" " {\n" " $this->username = $username;\n" " return $this;\n" " }\n" "\n" " public function getPassword(): string\n" " {\n" " return $this->password;\n" " }\n" "\n" " public function withPassword(string $password): self\n" " {\n" " $this->password = $password;\n" " return $this;\n" " }\n" "\n" " public function getOptions(): array\n" " {\n" " return $this->options;\n" " }\n" "\n" " public function withOptions(array $options): self\n" " {\n" " $this->options = $options;\n" " return $this;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_exception = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class MysqliException extends \\Exception\n" "{\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_pool = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "use mysqli;\n" "use Swoole\\ConnectionPool;\n" "\n" "/**\n" " * @method \\mysqli|MysqliProxy get()\n" " * @method void put(mysqli|MysqliProxy $connection)\n" " */\n" "class MysqliPool extends ConnectionPool\n" "{\n" " public function __construct(protected MysqliConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" " parent::__construct(function () {\n" " $mysqli = new \\mysqli();\n" " foreach ($this->config->getOptions() as $option => $value) {\n" " $mysqli->set_opt($option, $value);\n" " }\n" " $mysqli->real_connect(\n" " $this->config->getHost(),\n" " $this->config->getUsername(),\n" " $this->config->getPassword(),\n" " $this->config->getDbname(),\n" " $this->config->getPort(),\n" " $this->config->getUnixSocket()\n" " );\n" " if ($mysqli->connect_errno) {\n" " throw new MysqliException($mysqli->connect_error, $mysqli->connect_errno);\n" " }\n" " $mysqli->set_charset($this->config->getCharset());\n" " return $mysqli;\n" " }, $size, MysqliProxy::class);\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "/**\n" " * @method \\mysqli __getObject()\n" " */\n" "class MysqliProxy extends ObjectProxy\n" "{\n" " public const IO_METHOD_REGEX = '/^autocommit|begin_transaction|change_user|close|commit|kill|multi_query|ping|prepare|query|real_connect|real_query|reap_async_query|refresh|release_savepoint|rollback|savepoint|select_db|send_query|set_charset|ssl_set$/i';\n" "\n" " public const IO_ERRORS = [\n" " 2002, // MYSQLND_CR_CONNECTION_ERROR\n" " 2006, // MYSQLND_CR_SERVER_GONE_ERROR\n" " 2013, // MYSQLND_CR_SERVER_LOST\n" " ];\n" "\n" " /** @var \\mysqli */\n" " protected $__object;\n" "\n" " protected string $charsetContext;\n" "\n" " protected array $setOptContext = [];\n" "\n" " protected array $changeUserContext;\n" "\n" " /** @var callable */\n" " protected $constructor;\n" "\n" " protected int $round = 0;\n" "\n" " public function __construct(callable $constructor)\n" " {\n" " parent::__construct($constructor());\n" " $this->constructor = $constructor;\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" " for ($n = 3; $n--;) {\n" " $ret = @$this->__object->{$name}(...$arguments);\n" " if ($ret === false) {\n" " /* non-IO method */\n" " if (!preg_match(static::IO_METHOD_REGEX, $name)) {\n" " break;\n" " }\n" " /* no more chances or non-IO failures */\n" " if (!in_array($this->__object->errno, static::IO_ERRORS, true) || ($n === 0)) {\n" " throw new MysqliException($this->__object->error, $this->__object->errno);\n" " }\n" " $this->reconnect();\n" " continue;\n" " }\n" " if (strcasecmp($name, 'prepare') === 0) {\n" " $ret = new MysqliStatementProxy($ret, $arguments[0], $this);\n" " } elseif (strcasecmp($name, 'stmt_init') === 0) {\n" " $ret = new MysqliStatementProxy($ret, null, $this);\n" " }\n" " break;\n" " }\n" " /* @noinspection PhpUndefinedVariableInspection */\n" " return $ret;\n" " }\n" "\n" " public function getRound(): int\n" " {\n" " return $this->round;\n" " }\n" "\n" " public function reconnect(): void\n" " {\n" " $constructor = $this->constructor;\n" " parent::__construct($constructor());\n" " $this->round++;\n" " /* restore context */\n" " if (!empty($this->charsetContext)) {\n" " $this->__object->set_charset($this->charsetContext);\n" " }\n" " foreach ($this->setOptContext as $opt => $val) {\n" " $this->__object->set_opt($opt, $val);\n" " }\n" " if (!empty($this->changeUserContext)) {\n" " $this->__object->change_user(...$this->changeUserContext);\n" " }\n" " }\n" "\n" " public function options(int $option, $value): bool\n" " {\n" " $this->setOptContext[$option] = $value;\n" " return $this->__object->options($option, $value);\n" " }\n" "\n" " public function set_opt(int $option, $value): bool\n" " {\n" " return $this->options($option, $value);\n" " }\n" "\n" " public function set_charset(string $charset): bool\n" " {\n" " $this->charsetContext = $charset;\n" " return $this->__object->set_charset($charset);\n" " }\n" "\n" " public function change_user(string $user, string $password, ?string $database): bool\n" " {\n" " $this->changeUserContext = [$user, $password, $database];\n" " return $this->__object->change_user($user, $password, $database);\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_statement_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class MysqliStatementProxy extends ObjectProxy\n" "{\n" " public const IO_METHOD_REGEX = '/^close|execute|fetch|prepare$/i';\n" "\n" " /** @var \\mysqli_stmt */\n" " protected $__object;\n" "\n" " protected ?string $queryString;\n" "\n" " protected array $attrSetContext = [];\n" "\n" " protected array $bindParamContext;\n" "\n" " protected array $bindResultContext;\n" "\n" " protected MysqliProxy $parent;\n" "\n" " protected int $parentRound;\n" "\n" " public function __construct(\\mysqli_stmt $object, ?string $queryString, MysqliProxy $parent)\n" " {\n" " parent::__construct($object);\n" " $this->queryString = $queryString;\n" " $this->parent = $parent;\n" " $this->parentRound = $parent->getRound();\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" " for ($n = 3; $n--;) {\n" " $ret = @$this->__object->{$name}(...$arguments);\n" " if ($ret === false) {\n" " /* non-IO method */\n" " if (!preg_match(static::IO_METHOD_REGEX, $name)) {\n" " break;\n" " }\n" " /* no more chances or non-IO failures or in transaction */\n" " if (!in_array($this->__object->errno, $this->parent::IO_ERRORS, true) || ($n === 0)) {\n" " throw new MysqliException($this->__object->error, $this->__object->errno);\n" " }\n" " if ($this->parent->getRound() === $this->parentRound) {\n" " /* if not equal, parent has reconnected */\n" " $this->parent->reconnect();\n" " }\n" " $parent = $this->parent->__getObject();\n" " $this->__object = $this->queryString ? @$parent->prepare($this->queryString) : @$parent->stmt_init();\n" " if ($this->__object === false) {\n" " throw new MysqliException($parent->error, $parent->errno);\n" " }\n" " if (!empty($this->bindParamContext)) {\n" " $this->__object->bind_param($this->bindParamContext[0], ...$this->bindParamContext[1]);\n" " }\n" " if (!empty($this->bindResultContext)) {\n" " $this->__object->bind_result($this->bindResultContext);\n" " }\n" " foreach ($this->attrSetContext as $attr => $value) {\n" " $this->__object->attr_set($attr, $value);\n" " }\n" " continue;\n" " }\n" " if (strcasecmp($name, 'prepare') === 0) {\n" " $this->queryString = $arguments[0];\n" " }\n" " break;\n" " }\n" " /* @noinspection PhpUndefinedVariableInspection */\n" " return $ret;\n" " }\n" "\n" " public function attr_set($attr, $mode): bool\n" " {\n" " $this->attrSetContext[$attr] = $mode;\n" " return $this->__object->attr_set($attr, $mode);\n" " }\n" "\n" " public function bind_param($types, &...$arguments): bool\n" " {\n" " $this->bindParamContext = [$types, $arguments];\n" " return $this->__object->bind_param($types, ...$arguments);\n" " }\n" "\n" " public function bind_result(&...$arguments): bool\n" " {\n" " $this->bindResultContext = $arguments;\n" " return $this->__object->bind_result(...$arguments);\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_detects_lost_connections = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class DetectsLostConnections\n" "{\n" " /**\n" " * @var array\n" " */\n" " private const ERROR_MESSAGES = [\n" " 'server has gone away',\n" " 'no connection to the server',\n" " 'Lost connection',\n" " 'is dead or not enabled',\n" " 'Error while sending',\n" " 'decryption failed or bad record mac',\n" " 'server closed the connection unexpectedly',\n" " 'SSL connection has been closed unexpectedly',\n" " 'Error writing data to the connection',\n" " 'Resource deadlock avoided',\n" " 'Transaction() on null',\n" " 'child connection forced to terminate due to client_idle_limit',\n" " 'query_wait_timeout',\n" " 'reset by peer',\n" " 'Physical connection is not usable',\n" " 'TCP Provider: Error code 0x68',\n" " 'ORA-03113',\n" " 'ORA-03114',\n" " 'Packets out of order. Expected',\n" " 'Adaptive Server connection failed',\n" " 'Communication link failure',\n" " 'connection is no longer usable',\n" " 'Login timeout expired',\n" " 'SQLSTATE[HY000] [2002] Connection refused',\n" " 'running with the --read-only option so it cannot execute this statement',\n" " 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',\n" " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',\n" " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',\n" " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for',\n" " 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',\n" " 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',\n" " 'Temporary failure in name resolution',\n" " 'SQLSTATE[08S01]: Communication link failure',\n" " 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',\n" " 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',\n" " 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',\n" " 'SQLSTATE[08006] [7] could not translate host name',\n" " 'TCP Provider: Error code 0x274C',\n" " 'SQLSTATE[HY000] [2002] No such file or directory',\n" " 'Reason: Server is in script upgrade mode. Only administrator can connect at this time.',\n" " 'Unknown $curl_error_code: 77',\n" " 'SQLSTATE[08006] [7] SSL error: sslv3 alert unexpected message',\n" " 'SQLSTATE[08006] [7] unrecognized SSL error code:',\n" " 'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it',\n" " 'Broken pipe',\n" " // PDO::prepare(): Send of 77 bytes failed with errno=110 Operation timed out\n" " // SSL: Handshake timed out\n" " // SSL: Operation timed out\n" " // SSL: Connection timed out\n" " // SQLSTATE[HY000] [2002] Connection timed out\n" " 'timed out',\n" " 'Error reading result',\n" " ];\n" "\n" " public static function causedByLostConnection(\\Throwable $e): bool\n" " {\n" " $message = $e->getMessage();\n" " foreach (self::ERROR_MESSAGES as $needle) {\n" " if (mb_strpos($message, $needle) !== false) {\n" " return true;\n" " }\n" " }\n" "\n" " return false;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_config = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class PDOConfig\n" "{\n" " public const DRIVER_MYSQL = 'mysql';\n" "\n" " protected string $driver = self::DRIVER_MYSQL;\n" "\n" " protected string $host = '127.0.0.1';\n" "\n" " protected int $port = 3306;\n" "\n" " protected ?string $unixSocket;\n" "\n" " protected string $dbname = 'test';\n" "\n" " protected string $charset = 'utf8mb4';\n" "\n" " protected string $username = 'root';\n" "\n" " protected string $password = 'root';\n" "\n" " protected array $options = [];\n" "\n" " public function getDriver(): string\n" " {\n" " return $this->driver;\n" " }\n" "\n" " public function withDriver(string $driver): self\n" " {\n" " $this->driver = $driver;\n" " return $this;\n" " }\n" "\n" " public function getHost(): string\n" " {\n" " return $this->host;\n" " }\n" "\n" " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" " }\n" "\n" " public function getPort(): int\n" " {\n" " return $this->port;\n" " }\n" "\n" " public function hasUnixSocket(): bool\n" " {\n" " return !empty($this->unixSocket);\n" " }\n" "\n" " public function getUnixSocket(): ?string\n" " {\n" " return $this->unixSocket ?? null;\n" " }\n" "\n" " public function withUnixSocket(?string $unixSocket): self\n" " {\n" " $this->unixSocket = $unixSocket;\n" " return $this;\n" " }\n" "\n" " public function withPort(int $port): self\n" " {\n" " $this->port = $port;\n" " return $this;\n" " }\n" "\n" " public function getDbname(): string\n" " {\n" " return $this->dbname;\n" " }\n" "\n" " public function withDbname(string $dbname): self\n" " {\n" " $this->dbname = $dbname;\n" " return $this;\n" " }\n" "\n" " public function getCharset(): string\n" " {\n" " return $this->charset;\n" " }\n" "\n" " public function withCharset(string $charset): self\n" " {\n" " $this->charset = $charset;\n" " return $this;\n" " }\n" "\n" " public function getUsername(): string\n" " {\n" " return $this->username;\n" " }\n" "\n" " public function withUsername(string $username): self\n" " {\n" " $this->username = $username;\n" " return $this;\n" " }\n" "\n" " public function getPassword(): string\n" " {\n" " return $this->password;\n" " }\n" "\n" " public function withPassword(string $password): self\n" " {\n" " $this->password = $password;\n" " return $this;\n" " }\n" "\n" " public function getOptions(): array\n" " {\n" " return $this->options;\n" " }\n" "\n" " public function withOptions(array $options): self\n" " {\n" " $this->options = $options;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Returns the list of available drivers\n" " *\n" " * @return string[]\n" " */\n" " public static function getAvailableDrivers(): array\n" " {\n" " return [\n" " self::DRIVER_MYSQL,\n" " ];\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_pool = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "use PDO;\n" "use Swoole\\ConnectionPool;\n" "\n" "/**\n" " * @method void put(PDO|PDOProxy $connection)\n" " */\n" "class PDOPool extends ConnectionPool\n" "{\n" " public function __construct(protected PDOConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" " parent::__construct(function () {\n" " $driver = $this->config->getDriver();\n" " if ($driver === 'sqlite') {\n" " return new \\PDO($this->createDSN('sqlite'));\n" " }\n" "\n" " return new \\PDO($this->createDSN($driver), $this->config->getUsername(), $this->config->getPassword(), $this->config->getOptions());\n" " }, $size, PDOProxy::class);\n" " }\n" "\n" " /**\n" " * Get a PDO connection from the pool. The PDO connection (a PDO object) is wrapped in a PDOProxy object returned.\n" " *\n" " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" " * @return PDOProxy|false Returns a PDOProxy object from the pool, or false if the pool is full and the timeout is reached.\n" " * {@inheritDoc}\n" " */\n" " public function get(float $timeout = -1)\n" " {\n" " /* @var \\Swoole\\Database\\PDOProxy|false $pdo */\n" " $pdo = parent::get($timeout);\n" " if ($pdo === false) {\n" " return false;\n" " }\n" "\n" " $pdo->reset();\n" "\n" " return $pdo;\n" " }\n" "\n" " /**\n" " * @purpose create DSN\n" " * @throws \\Exception\n" " */\n" " private function createDSN(string $driver): string\n" " {\n" " switch ($driver) {\n" " case 'mysql':\n" " if ($this->config->hasUnixSocket()) {\n" " $dsn = \"mysql:unix_socket={$this->config->getUnixSocket()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}\";\n" " } else {\n" " $dsn = \"mysql:host={$this->config->getHost()};port={$this->config->getPort()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}\";\n" " }\n" " break;\n" " case 'pgsql':\n" " $dsn = 'pgsql:host=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . \";port={$this->config->getPort()};dbname={$this->config->getDbname()}\";\n" " break;\n" " case 'oci':\n" " $dsn = 'oci:dbname=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . ':' . $this->config->getPort() . '/' . $this->config->getDbname() . ';charset=' . $this->config->getCharset();\n" " break;\n" " case 'sqlite':\n" " // There are three types of SQLite databases: databases on disk, databases in memory, and temporary\n" " // databases (which are deleted when the connections are closed). It doesn't make sense to use\n" " // connection pool for the latter two types of databases, because each connection connects to a\n" " //different in-memory or temporary SQLite database.\n" " if ($this->config->getDbname() === '') {\n" " throw new \\Exception('Connection pool in Swoole does not support temporary SQLite databases.');\n" " }\n" " if ($this->config->getDbname() === ':memory:') {\n" " throw new \\Exception('Connection pool in Swoole does not support creating SQLite databases in memory.');\n" " }\n" " $dsn = 'sqlite:' . $this->config->getDbname();\n" " break;\n" " default:\n" " throw new \\Exception('Unsupported Database Driver:' . $driver);\n" " }\n" " return $dsn;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "/**\n" " * @method \\PDO __getObject()\n" " */\n" "class PDOProxy extends ObjectProxy\n" "{\n" " /** @var \\PDO */\n" " protected $__object;\n" "\n" " protected array $setAttributeContext = [];\n" "\n" " /** @var callable */\n" " protected $constructor;\n" "\n" " protected int $round = 0;\n" "\n" " protected int $inTransaction = 0;\n" "\n" " public function __construct(callable $constructor)\n" " {\n" " parent::__construct($constructor());\n" " $this->__object->setAttribute(\\PDO::ATTR_ERRMODE, \\PDO::ERRMODE_EXCEPTION);\n" " $this->constructor = $constructor;\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" " try {\n" " $ret = $this->__object->{$name}(...$arguments);\n" " } catch (\\PDOException $e) {\n" " if (!$this->__object->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {\n" " $this->reconnect();\n" " $ret = $this->__object->{$name}(...$arguments);\n" " } else {\n" " throw $e;\n" " }\n" " }\n" "\n" " if (strcasecmp($name, 'beginTransaction') === 0) {\n" " $this->inTransaction++;\n" " }\n" "\n" " if ((strcasecmp($name, 'commit') === 0 || strcasecmp($name, 'rollback') === 0) && $this->inTransaction > 0) {\n" " $this->inTransaction--;\n" " }\n" "\n" " if ((strcasecmp($name, 'prepare') === 0) || (strcasecmp($name, 'query') === 0)) {\n" " $ret = new PDOStatementProxy($ret, $this);\n" " }\n" "\n" " return $ret;\n" " }\n" "\n" " public function getRound(): int\n" " {\n" " return $this->round;\n" " }\n" "\n" " public function reconnect(): void\n" " {\n" " $constructor = $this->constructor;\n" " parent::__construct($constructor());\n" " $this->__object->setAttribute(\\PDO::ATTR_ERRMODE, \\PDO::ERRMODE_EXCEPTION);\n" " $this->round++;\n" " /* restore context */\n" " foreach ($this->setAttributeContext as $attribute => $value) {\n" " $this->__object->setAttribute($attribute, $value);\n" " }\n" " }\n" "\n" " public function setAttribute(int $attribute, $value): bool\n" " {\n" " $this->setAttributeContext[$attribute] = $value;\n" " return $this->__object->setAttribute($attribute, $value);\n" " }\n" "\n" " public function inTransaction(): bool\n" " {\n" " return $this->inTransaction > 0;\n" " }\n" "\n" " public function reset(): void\n" " {\n" " $this->inTransaction = 0;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_statement_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "/**\n" " * The proxy class for PHP class PDOStatement.\n" " *\n" " * @see https://www.php.net/PDOStatement The PDOStatement class\n" " */\n" "class PDOStatementProxy extends ObjectProxy\n" "{\n" " /** @var \\PDOStatement */\n" " protected $__object;\n" "\n" " protected array $setAttributeContext = [];\n" "\n" " protected array $setFetchModeContext;\n" "\n" " protected array $bindParamContext = [];\n" "\n" " protected array $bindColumnContext = [];\n" "\n" " protected array $bindValueContext = [];\n" "\n" " protected PDOProxy $parent;\n" "\n" " /** @var int */\n" " protected $parentRound;\n" "\n" " public function __construct(\\PDOStatement $object, PDOProxy $parent)\n" " {\n" " parent::__construct($object);\n" " $this->parent = $parent;\n" " $this->parentRound = $parent->getRound();\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" " try {\n" " $ret = $this->__object->{$name}(...$arguments);\n" " } catch (\\PDOException $e) {\n" " if (!$this->parent->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {\n" " if ($this->parent->getRound() === $this->parentRound) {\n" " /* if not equal, parent has reconnected */\n" " $this->parent->reconnect();\n" " }\n" " $parent = $this->parent->__getObject();\n" " $this->__object = $parent->prepare($this->__object->queryString);\n" "\n" " foreach ($this->setAttributeContext as $attribute => $value) {\n" " $this->__object->setAttribute($attribute, $value);\n" " }\n" " if (!empty($this->setFetchModeContext)) {\n" " $this->__object->setFetchMode(...$this->setFetchModeContext);\n" " }\n" " foreach ($this->bindParamContext as $param => $item) {\n" " $this->__object->bindParam($param, ...$item);\n" " }\n" " foreach ($this->bindColumnContext as $column => $item) {\n" " $this->__object->bindColumn($column, ...$item);\n" " }\n" " foreach ($this->bindValueContext as $value => $item) {\n" " $this->__object->bindParam($value, ...$item);\n" " }\n" " $ret = $this->__object->{$name}(...$arguments);\n" " } else {\n" " throw $e;\n" " }\n" " }\n" "\n" " return $ret;\n" " }\n" "\n" " public function setAttribute(int $attribute, $value): bool\n" " {\n" " $this->setAttributeContext[$attribute] = $value;\n" " return $this->__object->setAttribute($attribute, $value);\n" " }\n" "\n" " /**\n" " * Set the default fetch mode for this statement.\n" " *\n" " * @see https://www.php.net/manual/en/pdostatement.setfetchmode.php\n" " */\n" " public function setFetchMode(int $mode, ...$params): bool\n" " {\n" " $this->setFetchModeContext = func_get_args();\n" " return $this->__object->setFetchMode(...$this->setFetchModeContext);\n" " }\n" "\n" " public function bindParam($parameter, &$variable, $data_type = \\PDO::PARAM_STR, $length = 0, $driver_options = null): bool\n" " {\n" " $this->bindParamContext[$parameter] = [$variable, $data_type, $length, $driver_options];\n" " return $this->__object->bindParam($parameter, $variable, $data_type, $length, $driver_options);\n" " }\n" "\n" " public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null): bool\n" " {\n" " $this->bindColumnContext[$column] = [$param, $type, $maxlen, $driverdata];\n" " return $this->__object->bindColumn($column, $param, $type, $maxlen, $driverdata);\n" " }\n" "\n" " public function bindValue($parameter, $value, $data_type = \\PDO::PARAM_STR): bool\n" " {\n" " $this->bindValueContext[$parameter] = [$value, $data_type];\n" " return $this->__object->bindValue($parameter, $value, $data_type);\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_redis_config = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "class RedisConfig\n" "{\n" " protected string $host = '127.0.0.1';\n" "\n" " protected int $port = 6379;\n" "\n" " protected float $timeout = 0.0;\n" "\n" " protected string $reserved = '';\n" "\n" " protected int $retry_interval = 0;\n" "\n" " protected float $read_timeout = 0.0;\n" "\n" " protected string $auth = '';\n" "\n" " protected int $dbIndex = 0;\n" "\n" " /**\n" " * @var array\n" " */\n" " protected array $options = [];\n" "\n" " public function getHost(): string\n" " {\n" " return $this->host;\n" " }\n" "\n" " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" " }\n" "\n" " public function getPort(): int\n" " {\n" " return $this->port;\n" " }\n" "\n" " public function withPort(int $port): self\n" " {\n" " $this->port = $port;\n" " return $this;\n" " }\n" "\n" " public function getTimeout(): float\n" " {\n" " return $this->timeout;\n" " }\n" "\n" " public function withTimeout(float $timeout): self\n" " {\n" " $this->timeout = $timeout;\n" " return $this;\n" " }\n" "\n" " public function getReserved(): string\n" " {\n" " return $this->reserved;\n" " }\n" "\n" " public function withReserved(string $reserved): self\n" " {\n" " $this->reserved = $reserved;\n" " return $this;\n" " }\n" "\n" " public function getRetryInterval(): int\n" " {\n" " return $this->retry_interval;\n" " }\n" "\n" " public function withRetryInterval(int $retry_interval): self\n" " {\n" " $this->retry_interval = $retry_interval;\n" " return $this;\n" " }\n" "\n" " public function getReadTimeout(): float\n" " {\n" " return $this->read_timeout;\n" " }\n" "\n" " public function withReadTimeout(float $read_timeout): self\n" " {\n" " $this->read_timeout = $read_timeout;\n" " return $this;\n" " }\n" "\n" " public function getAuth(): string\n" " {\n" " return $this->auth;\n" " }\n" "\n" " public function withAuth(string $auth): self\n" " {\n" " $this->auth = $auth;\n" " return $this;\n" " }\n" "\n" " public function getDbIndex(): int\n" " {\n" " return $this->dbIndex;\n" " }\n" "\n" " public function withDbIndex(int $dbIndex): self\n" " {\n" " $this->dbIndex = $dbIndex;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Add a configurable option.\n" " */\n" " public function withOption(int $option, mixed $value): self\n" " {\n" " $this->options[$option] = $value;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Add/override configurable options.\n" " *\n" " * @param array $options\n" " */\n" " public function setOptions(array $options): self\n" " {\n" " $this->options = $options;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Get configurable options.\n" " *\n" " * @return array\n" " */\n" " public function getOptions(): array\n" " {\n" " return $this->options;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_redis_pool = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" "use Redis;\n" "use Swoole\\ConnectionPool;\n" "\n" "/**\n" " * @method \\Redis get(float $timeout = -1)\n" " * @method void put(Redis $connection)\n" " */\n" "class RedisPool extends ConnectionPool\n" "{\n" " public function __construct(protected RedisConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" " parent::__construct(function () {\n" " $redis = new \\Redis();\n" " /* Compatible with different versions of Redis extension as much as possible */\n" " $arguments = [\n" " $this->config->getHost(),\n" " $this->config->getPort(),\n" " ];\n" " if ($this->config->getTimeout() !== 0.0) {\n" " $arguments[] = $this->config->getTimeout();\n" " }\n" " if ($this->config->getRetryInterval() !== 0) {\n" " /* reserved should always be NULL */\n" " $arguments[] = null;\n" " $arguments[] = $this->config->getRetryInterval();\n" " }\n" " if ($this->config->getReadTimeout() !== 0.0) {\n" " $arguments[] = $this->config->getReadTimeout();\n" " }\n" " $redis->connect(...$arguments);\n" " if ($this->config->getAuth()) {\n" " $redis->auth($this->config->getAuth());\n" " }\n" " if ($this->config->getDbIndex() !== 0) {\n" " $redis->select($this->config->getDbIndex());\n" " }\n" "\n" " /* Set Redis options. */\n" " foreach ($this->config->getOptions() as $key => $value) {\n" " $redis->setOption($key, $value);\n" " }\n" "\n" " return $redis;\n" " }, $size);\n" " }\n" "}\n"; static const char* swoole_library_source_core_http_status = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Http;\n" "\n" "abstract class Status\n" "{\n" " public const CONTINUE = 100;\n" "\n" " public const SWITCHING_PROTOCOLS = 101;\n" "\n" " public const PROCESSING = 102;\n" "\n" " public const OK = 200;\n" "\n" " public const CREATED = 201;\n" "\n" " public const ACCEPTED = 202;\n" "\n" " public const NON_AUTHORITATIVE_INFORMATION = 203;\n" "\n" " public const NO_CONTENT = 204;\n" "\n" " public const RESET_CONTENT = 205;\n" "\n" " public const PARTIAL_CONTENT = 206;\n" "\n" " public const MULTI_STATUS = 207;\n" "\n" " public const ALREADY_REPORTED = 208;\n" "\n" " public const IM_USED = 226;\n" "\n" " public const MULTIPLE_CHOICES = 300;\n" "\n" " public const MOVED_PERMANENTLY = 301;\n" "\n" " public const FOUND = 302;\n" "\n" " public const SEE_OTHER = 303;\n" "\n" " public const NOT_MODIFIED = 304;\n" "\n" " public const USE_PROXY = 305;\n" "\n" " public const SWITCH_PROXY = 306;\n" "\n" " public const TEMPORARY_REDIRECT = 307;\n" "\n" " public const PERMANENT_REDIRECT = 308;\n" "\n" " public const BAD_REQUEST = 400;\n" "\n" " public const UNAUTHORIZED = 401;\n" "\n" " public const PAYMENT_REQUIRED = 402;\n" "\n" " public const FORBIDDEN = 403;\n" "\n" " public const NOT_FOUND = 404;\n" "\n" " public const METHOD_NOT_ALLOWED = 405;\n" "\n" " public const NOT_ACCEPTABLE = 406;\n" "\n" " public const PROXY_AUTHENTICATION_REQUIRED = 407;\n" "\n" " public const REQUEST_TIME_OUT = 408;\n" "\n" " public const CONFLICT = 409;\n" "\n" " public const GONE = 410;\n" "\n" " public const LENGTH_REQUIRED = 411;\n" "\n" " public const PRECONDITION_FAILED = 412;\n" "\n" " public const REQUEST_ENTITY_TOO_LARGE = 413;\n" "\n" " public const REQUEST_URI_TOO_LARGE = 414;\n" "\n" " public const UNSUPPORTED_MEDIA_TYPE = 415;\n" "\n" " public const REQUESTED_RANGE_NOT_SATISFIABLE = 416;\n" "\n" " public const EXPECTATION_FAILED = 417;\n" "\n" " public const MISDIRECTED_REQUEST = 421;\n" "\n" " public const UNPROCESSABLE_ENTITY = 422;\n" "\n" " public const LOCKED = 423;\n" "\n" " public const FAILED_DEPENDENCY = 424;\n" "\n" " public const UNORDERED_COLLECTION = 425;\n" "\n" " public const UPGRADE_REQUIRED = 426;\n" "\n" " public const PRECONDITION_REQUIRED = 428;\n" "\n" " public const TOO_MANY_REQUESTS = 429;\n" "\n" " public const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;\n" "\n" " public const UNAVAILABLE_FOR_LEGAL_REASONS = 451;\n" "\n" " public const INTERNAL_SERVER_ERROR = 500;\n" "\n" " public const NOT_IMPLEMENTED = 501;\n" "\n" " public const BAD_GATEWAY = 502;\n" "\n" " public const SERVICE_UNAVAILABLE = 503;\n" "\n" " public const GATEWAY_TIME_OUT = 504;\n" "\n" " public const HTTP_VERSION_NOT_SUPPORTED = 505;\n" "\n" " public const VARIANT_ALSO_NEGOTIATES = 506;\n" "\n" " public const INSUFFICIENT_STORAGE = 507;\n" "\n" " public const LOOP_DETECTED = 508;\n" "\n" " public const NOT_EXTENDED = 510;\n" "\n" " public const NETWORK_AUTHENTICATION_REQUIRED = 511;\n" "\n" " protected static $reasonPhrases = [\n" " self::CONTINUE => 'Continue',\n" " self::SWITCHING_PROTOCOLS => 'Switching Protocols',\n" " self::PROCESSING => 'Processing',\n" " self::OK => 'OK',\n" " self::CREATED => 'Created',\n" " self::ACCEPTED => 'Accepted',\n" " self::NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information',\n" " self::NO_CONTENT => 'No Content',\n" " self::RESET_CONTENT => 'Reset Content',\n" " self::PARTIAL_CONTENT => 'Partial Content',\n" " self::MULTI_STATUS => 'Multi-status',\n" " self::ALREADY_REPORTED => 'Already Reported',\n" " self::IM_USED => 'IM Used',\n" " self::MULTIPLE_CHOICES => 'Multiple Choices',\n" " self::MOVED_PERMANENTLY => 'Moved Permanently',\n" " self::FOUND => 'Found',\n" " self::SEE_OTHER => 'See Other',\n" " self::NOT_MODIFIED => 'Not Modified',\n" " self::USE_PROXY => 'Use Proxy',\n" " self::SWITCH_PROXY => 'Switch Proxy',\n" " self::TEMPORARY_REDIRECT => 'Temporary Redirect',\n" " self::PERMANENT_REDIRECT => 'Permanent Redirect',\n" " self::BAD_REQUEST => 'Bad Request',\n" " self::UNAUTHORIZED => 'Unauthorized',\n" " self::PAYMENT_REQUIRED => 'Payment Required',\n" " self::FORBIDDEN => 'Forbidden',\n" " self::NOT_FOUND => 'Not Found',\n" " self::METHOD_NOT_ALLOWED => 'Method Not Allowed',\n" " self::NOT_ACCEPTABLE => 'Not Acceptable',\n" " self::PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required',\n" " self::REQUEST_TIME_OUT => 'Request Time-out',\n" " self::CONFLICT => 'Conflict',\n" " self::GONE => 'Gone',\n" " self::LENGTH_REQUIRED => 'Length Required',\n" " self::PRECONDITION_FAILED => 'Precondition Failed',\n" " self::REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large',\n" " self::REQUEST_URI_TOO_LARGE => 'Request-URI Too Large',\n" " self::UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type',\n" " self::REQUESTED_RANGE_NOT_SATISFIABLE => 'Requested range not satisfiable',\n" " self::EXPECTATION_FAILED => 'Expectation Failed',\n" " self::MISDIRECTED_REQUEST => 'Misdirected Request',\n" " self::UNPROCESSABLE_ENTITY => 'Unprocessable Entity',\n" " self::LOCKED => 'Locked',\n" " self::FAILED_DEPENDENCY => 'Failed Dependency',\n" " self::UNORDERED_COLLECTION => 'Unordered Collection',\n" " self::UPGRADE_REQUIRED => 'Upgrade Required',\n" " self::PRECONDITION_REQUIRED => 'Precondition Required',\n" " self::TOO_MANY_REQUESTS => 'Too Many Requests',\n" " self::REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large',\n" " self::UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons',\n" " self::INTERNAL_SERVER_ERROR => 'Internal Server Error',\n" " self::NOT_IMPLEMENTED => 'Not Implemented',\n" " self::BAD_GATEWAY => 'Bad Gateway',\n" " self::SERVICE_UNAVAILABLE => 'Service Unavailable',\n" " self::GATEWAY_TIME_OUT => 'Gateway Time-out',\n" " self::HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported',\n" " self::VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates',\n" " self::INSUFFICIENT_STORAGE => 'Insufficient Storage',\n" " self::LOOP_DETECTED => 'Loop Detected',\n" " self::NOT_EXTENDED => 'Not Extended',\n" " self::NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required',\n" " ];\n" "\n" " public static function getReasonPhrases(): array\n" " {\n" " return static::$reasonPhrases;\n" " }\n" "\n" " public static function getReasonPhrase(int $value): string\n" " {\n" " return static::$reasonPhrases[$value] ?? 'Unknown';\n" " }\n" "}\n"; static const char* swoole_library_source_core_curl_exception = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Curl;\n" "\n" "use Swoole;\n" "\n" "class Exception extends Swoole\\Exception\n" "{\n" "}\n"; static const char* swoole_library_source_core_curl_handler = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "/* @noinspection PhpComposerExtensionStubsInspection, PhpDuplicateSwitchCaseBodyInspection, PhpInconsistentReturnPointsInspection */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Curl;\n" "\n" "use Swoole\\Constant;\n" "use Swoole\\Coroutine\\Http\\Client;\n" "use Swoole\\Coroutine\\System;\n" "use Swoole\\Curl\\Exception as CurlException;\n" "use Swoole\\Http\\Status;\n" "\n" "final class Handler implements \\Stringable\n" "{\n" " /**\n" " * @var Client|null\n" " */\n" " private $client;\n" "\n" " private $info = [\n" " 'url' => '',\n" " 'content_type' => '',\n" " 'http_code' => 0,\n" " 'header_size' => 0,\n" " 'request_size' => 0,\n" " 'filetime' => -1,\n" " 'ssl_verify_result' => 0,\n" " 'redirect_count' => 0,\n" " 'total_time' => 5.3E-5,\n" " 'namelookup_time' => 0.0,\n" " 'connect_time' => 0.0,\n" " 'pretransfer_time' => 0.0,\n" " 'size_upload' => 0.0,\n" " 'size_download' => 0.0,\n" " 'speed_download' => 0.0,\n" " 'speed_upload' => 0.0,\n" " 'download_content_length' => -1.0,\n" " 'upload_content_length' => -1.0,\n" " 'starttransfer_time' => 0.0,\n" " 'redirect_time' => 0.0,\n" " 'redirect_url' => '',\n" " 'primary_ip' => '',\n" " 'certinfo' => [],\n" " 'primary_port' => 0,\n" " 'local_ip' => '',\n" " 'local_port' => 0,\n" " 'http_version' => 0,\n" " 'protocol' => 0,\n" " 'ssl_verifyresult' => 0,\n" " 'scheme' => '',\n" " 'private' => '',\n" " ];\n" "\n" " private $withHeaderOut = false;\n" "\n" " private $withFileTime = false;\n" "\n" " private $urlInfo;\n" "\n" " private $postData;\n" "\n" " private $infile;\n" "\n" " private $infileSize = PHP_INT_MAX;\n" "\n" " private $outputStream;\n" "\n" " private $proxyType;\n" "\n" " private $proxy;\n" "\n" " private $proxyPort = 1080;\n" "\n" " private $proxyUsername;\n" "\n" " private $proxyPassword;\n" "\n" " private $clientOptions = [];\n" "\n" " private $followLocation = false;\n" "\n" " private $autoReferer = false;\n" "\n" " private $maxRedirects;\n" "\n" " private $withHeader = false;\n" "\n" " private $nobody = false;\n" "\n" " /** @var callable|null */\n" " private $headerFunction;\n" "\n" " /** @var callable|null */\n" " private $readFunction;\n" "\n" " /** @var callable|null */\n" " private $writeFunction;\n" "\n" " private $noProgress = true;\n" "\n" " /** @var callable */\n" " private $progressFunction;\n" "\n" " private $returnTransfer = false;\n" "\n" " private $method = '';\n" "\n" " private $headers = [];\n" "\n" " private $headerMap = [];\n" "\n" " private $transfer;\n" "\n" " private $errCode = 0;\n" "\n" " private $errMsg = '';\n" "\n" " private $failOnError = false;\n" "\n" " private $closed = false;\n" "\n" " private $cookieJar = '';\n" "\n" " private $resolve = [];\n" "\n" " private $unix_socket_path = '';\n" "\n" " public function __construct(string $url = '')\n" " {\n" " if ($url) {\n" " $this->setUrl($url);\n" " }\n" " }\n" "\n" " public function __toString(): string\n" " {\n" " $id = spl_object_id($this);\n" " return \"Object({$id}) of type (curl)\";\n" " }\n" "\n" " /* ====== Public APIs ====== */\n" "\n" " public function isAvailable(): bool\n" " {\n" " if ($this->closed) {\n" " trigger_error('supplied resource is not a valid cURL handle resource', E_USER_WARNING);\n" " return false;\n" " }\n" " return true;\n" " }\n" "\n" " public function setOpt(int $opt, $value): bool\n" " {\n" " return $this->isAvailable() and $this->setOption($opt, $value);\n" " }\n" "\n" " public function exec()\n" " {\n" " if (!$this->isAvailable()) {\n" " return false;\n" " }\n" " return $this->execute();\n" " }\n" "\n" " public function getInfo()\n" " {\n" " return $this->isAvailable() ? $this->info : false;\n" " }\n" "\n" " public function errno(): int\n" " {\n" " return $this->isAvailable() ? $this->errCode : 0;\n" " }\n" "\n" " public function error(): string\n" " {\n" " return $this->isAvailable() ? $this->errMsg : '';\n" " }\n" "\n" " public function reset()\n" " {\n" " if (!$this->isAvailable()) {\n" " return false;\n" " }\n" " foreach ((new \\ReflectionClass(self::class))->getDefaultProperties() as $name => $value) {\n" " $this->{$name} = $value;\n" " }\n" " }\n" "\n" " public function getContent()\n" " {\n" " if (!$this->isAvailable()) {\n" " return false;\n" " }\n" " return $this->transfer;\n" " }\n" "\n" " public function close(): void\n" " {\n" " if (!$this->isAvailable()) {\n" " return;\n" " }\n" " foreach ($this as &$property) { // @phpstan-ignore foreach.nonIterable\n" " $property = null;\n" " }\n" " $this->closed = true;\n" " }\n" "\n" " private function create(?array $urlInfo = null): void\n" " {\n" " if ($urlInfo === null) {\n" " $urlInfo = $this->urlInfo;\n" " }\n" " $host = $urlInfo['host'];\n" " $port = $urlInfo['port'];\n" " if (isset($this->resolve[$host])) {\n" " if (!$this->hasHeader('Host')) {\n" " $this->setHeader('Host', $host);\n" " }\n" " $this->urlInfo['host'] = $host = $this->resolve[$host][$port] ?? null ?: $host;\n" " }\n" " if ($this->unix_socket_path) {\n" " $host = $this->unix_socket_path;\n" " $port = 0;\n" " if (stripos($host, 'unix:/') !== 0) {\n" " $host = \"unix:/{$host}\";\n" " }\n" " }\n" " $this->client = new Client($host, $port, $urlInfo['scheme'] === 'https');\n" " }\n" "\n" " private function getUrl(): string\n" " {\n" " if (empty($this->urlInfo['path'])) {\n" " $url = '/';\n" " } else {\n" " $url = $this->urlInfo['path'];\n" " }\n" " if (!empty($this->urlInfo['query'])) {\n" " $url .= '?' . $this->urlInfo['query'];\n" " }\n" " if (!empty($this->urlInfo['fragment'])) {\n" " $url .= '#' . $this->urlInfo['fragment'];\n" " }\n" " return $url;\n" " }\n" "\n" " private function setUrl(string $url, bool $setInfo = true): bool\n" " {\n" " if (strlen($url) === 0) {\n" " $this->setError(CURLE_URL_MALFORMAT, 'No URL set!');\n" " return false;\n" " }\n" " if (!str_contains($url, '://') && $this->unix_socket_path === '') {\n" " $url = 'http://' . $url;\n" " }\n" " if ($setInfo) {\n" " $urlInfo = parse_url($url);\n" " if ($this->unix_socket_path) {\n" " if (empty($urlInfo['host']) && !empty($urlInfo['path'])) {\n" " $urlInfo['host'] = explode('/', $urlInfo['path'])[1] ?? null;\n" " }\n" " if (!$this->hasHeader('Host') && !empty($urlInfo['host'])) {\n" " $this->setHeader('Host', $urlInfo['host']);\n" " }\n" " }\n" " if (!is_array($urlInfo)) {\n" " $this->setError(CURLE_URL_MALFORMAT, \"URL[{$url}] using bad/illegal format\");\n" " return false;\n" " }\n" " if (!$this->setUrlInfo($urlInfo)) {\n" " return false;\n" " }\n" " }\n" " $this->info['url'] = $url;\n" " return true;\n" " }\n" "\n" " private function setUrlInfo(array $urlInfo): bool\n" " {\n" " if (empty($urlInfo['scheme'])) {\n" " $urlInfo['scheme'] = 'http';\n" " }\n" " $scheme = $urlInfo['scheme'];\n" " if ($scheme !== 'http' and $scheme !== 'https') {\n" " $this->setError(CURLE_UNSUPPORTED_PROTOCOL, \"Protocol \\\"{$scheme}\\\" not supported or disabled in libcurl\");\n" " return false;\n" " }\n" " $host = $urlInfo['host'];\n" " if ($this->info['primary_port'] !== 0) {\n" " /* keep same with cURL, primary_port has the highest priority */\n" " $urlInfo['port'] = $this->info['primary_port'];\n" " } elseif (empty($urlInfo['port'])) {\n" " $urlInfo['port'] = $scheme === 'https' ? 443 : 80;\n" " } else {\n" " $urlInfo['port'] = intval($urlInfo['port']);\n" " }\n" " $port = $urlInfo['port'];\n" " if (isset($this->client)) {\n" " $oldUrlInfo = $this->urlInfo;\n" " if (($host !== $oldUrlInfo['host']) || ($port !== $oldUrlInfo['port']) || ($scheme !== $oldUrlInfo['scheme'])) {\n" " /* target changed */\n" " $this->create($urlInfo);\n" " }\n" " }\n" " $this->urlInfo = $urlInfo;\n" " return true;\n" " }\n" "\n" " private function setPort(int $port): void\n" " {\n" " $this->info['primary_port'] = $port;\n" " if (!isset($this->urlInfo['port']) || $this->urlInfo['port'] !== $port) {\n" " $this->urlInfo['port'] = $port;\n" " if (isset($this->client)) {\n" " /* target changed */\n" " $this->create();\n" " }\n" " }\n" " }\n" "\n" " private function setError($code, $msg = ''): void\n" " {\n" " $this->errCode = $code;\n" " $this->errMsg = $msg ?: curl_strerror($code);\n" " }\n" "\n" " private function hasHeader(string $headerName): bool\n" " {\n" " return isset($this->headerMap[strtolower($headerName)]);\n" " }\n" "\n" " private function setHeader(string $headerName, string $value): void\n" " {\n" " $lowerCaseHeaderName = strtolower($headerName);\n" "\n" " if (isset($this->headerMap[$lowerCaseHeaderName])) {\n" " unset($this->headers[$this->headerMap[$lowerCaseHeaderName]]);\n" " }\n" "\n" " if ($value !== '') {\n" " $this->headers[$headerName] = $value;\n" " $this->headerMap[$lowerCaseHeaderName] = $headerName;\n" " } else {\n" " // remove empty headers (keep same with raw cURL)\n" " unset($this->headerMap[$lowerCaseHeaderName]);\n" " }\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function setOption(int $opt, mixed $value): bool\n" " {\n" " switch ($opt) {\n" " // case CURLOPT_STDERR:\n" " // case CURLOPT_WRITEHEADER:\n" " case CURLOPT_FILE:\n" " case CURLOPT_INFILE:\n" " if (!is_resource($value)) {\n" " trigger_error('swoole_curl_setopt(): supplied argument is not a valid File-Handle resource', E_USER_WARNING);\n" " return false;\n" " }\n" " break;\n" " }\n" "\n" " switch ($opt) {\n" " /*\n" " * Basic\n" " */\n" " case CURLOPT_URL:\n" " return $this->setUrl((string) $value);\n" " case CURLOPT_PORT:\n" " $this->setPort((int) $value);\n" " break;\n" " case CURLOPT_FORBID_REUSE:\n" " $this->clientOptions[Constant::OPTION_KEEP_ALIVE] = !$value;\n" " break;\n" " case CURLOPT_RETURNTRANSFER:\n" " $this->returnTransfer = (bool) $value;\n" " $this->transfer = '';\n" " break;\n" " case CURLOPT_ENCODING:\n" " if (empty($value)) {\n" " if (defined('SWOOLE_HAVE_ZLIB')) {\n" " $value = 'gzip, deflate';\n" " }\n" " if (defined('SWOOLE_HAVE_BROTLI')) {\n" " if (!empty($value)) {\n" " $value = 'br, ' . $value;\n" " } else {\n" " $value = 'br';\n" " }\n" " }\n" " if (empty($value)) {\n" " break;\n" " }\n" " }\n" " $this->setHeader('Accept-Encoding', $value);\n" " break;\n" " case CURLOPT_PROXYTYPE:\n" " if ($value !== CURLPROXY_HTTP and $value !== CURLPROXY_SOCKS5) {\n" " throw new CurlException('swoole_curl_setopt(): Only support following CURLOPT_PROXYTYPE values: CURLPROXY_HTTP, CURLPROXY_SOCKS5');\n" " }\n" " $this->proxyType = $value;\n" " break;\n" " case CURLOPT_PROXY:\n" " $this->proxy = $value;\n" " break;\n" " case CURLOPT_PROXYPORT:\n" " $this->proxyPort = $value;\n" " break;\n" " case CURLOPT_PROXYUSERNAME:\n" " $this->proxyUsername = $value;\n" " break;\n" " case CURLOPT_PROXYPASSWORD:\n" " $this->proxyPassword = $value;\n" " break;\n" " case CURLOPT_PROXYUSERPWD:\n" " $usernamePassword = explode(':', $value);\n" " $this->proxyUsername = urldecode($usernamePassword[0]);\n" " $this->proxyPassword = urldecode($usernamePassword[1] ?? null);\n" " break;\n" " case CURLOPT_PROXYAUTH:\n" " /* ignored temporarily */\n" " break;\n" " case CURLOPT_UNIX_SOCKET_PATH:\n" " $realpath = realpath((string) $value);\n" " if ($realpath) {\n" " $this->unix_socket_path = $realpath;\n" " } else {\n" " $this->setError(CURLE_COULDNT_CONNECT);\n" " }\n" " break;\n" " case CURLOPT_NOBODY:\n" " $this->nobody = boolval($value);\n" " $this->method = 'HEAD';\n" " break;\n" " case CURLOPT_RESOLVE:\n" " foreach ((array) $value as $resolve) {\n" " $flag = substr($resolve, 0, 1);\n" " if ($flag === '+' || $flag === '-') {\n" " // TODO: [+]HOST:PORT:ADDRESS\n" " $resolve = substr($resolve, 1);\n" " }\n" " $tmpResolve = explode(':', $resolve, 3);\n" " $host = $tmpResolve[0];\n" " $port = $tmpResolve[1] ?? 0;\n" " $ip = $tmpResolve[2] ?? '';\n" " if ($flag === '-') {\n" " unset($this->resolve[$host][$port]);\n" " } else {\n" " // TODO: HOST:PORT:ADDRESS[,ADDRESS]...\n" " $this->resolve[$host][$port] = explode(',', $ip)[0];\n" " }\n" " }\n" " break;\n" " case CURLOPT_IPRESOLVE:\n" " if ($value !== CURL_IPRESOLVE_WHATEVER and $value !== CURL_IPRESOLVE_V4) {\n" " throw new CurlException('swoole_curl_setopt(): Only support following CURLOPT_IPRESOLVE values: CURL_IPRESOLVE_WHATEVER, CURL_IPRESOLVE_V4');\n" " }\n" " break;\n" " case CURLOPT_TCP_NODELAY:\n" " $this->clientOptions[Constant::OPTION_OPEN_TCP_NODELAY] = boolval($value);\n" " break;\n" " case CURLOPT_PRIVATE:\n" " $this->info['private'] = $value;\n" " break;\n" " /*\n" " * Ignore options\n" " */\n" " case CURLOPT_VERBOSE:\n" " // trigger_error('swoole_curl_setopt(): CURLOPT_VERBOSE is not supported', E_USER_WARNING);\n" " case CURLOPT_SSLVERSION:\n" " case CURLOPT_NOSIGNAL:\n" " case CURLOPT_FRESH_CONNECT:\n" " case CURLOPT_DNS_USE_GLOBAL_CACHE:\n" " case CURLOPT_DNS_CACHE_TIMEOUT:\n" " case CURLOPT_STDERR:\n" " case CURLOPT_WRITEHEADER:\n" " case CURLOPT_BUFFERSIZE:\n" " case CURLOPT_SSLCERTTYPE:\n" " case CURLOPT_SSLKEYTYPE:\n" " case CURLOPT_NOPROXY:\n" " case CURLOPT_CERTINFO:\n" " case CURLOPT_HEADEROPT:\n" " case CURLOPT_PROXYHEADER:\n" " case CURLOPT_HTTPPROXYTUNNEL:\n" " break;\n" " /*\n" " * SSL\n" " */\n" " case CURLOPT_SSL_VERIFYHOST:\n" " break;\n" " case CURLOPT_SSL_VERIFYPEER:\n" " $this->clientOptions[Constant::OPTION_SSL_VERIFY_PEER] = $value;\n" " break;\n" " case CURLOPT_SSLCERT:\n" " $this->clientOptions[Constant::OPTION_SSL_CERT_FILE] = $value;\n" " break;\n" " case CURLOPT_SSLKEY:\n" " $this->clientOptions[Constant::OPTION_SSL_KEY_FILE] = $value;\n" " break;\n" " case CURLOPT_CAINFO:\n" " $this->clientOptions[Constant::OPTION_SSL_CAFILE] = $value;\n" " break;\n" " case CURLOPT_CAPATH:\n" " $this->clientOptions[Constant::OPTION_SSL_CAPATH] = $value;\n" " break;\n" " case CURLOPT_KEYPASSWD:\n" " case CURLOPT_SSLCERTPASSWD:\n" " case CURLOPT_SSLKEYPASSWD:\n" " $this->clientOptions[Constant::OPTION_SSL_PASSPHRASE] = $value;\n" " break;\n" " /*\n" " * Http POST\n" " */\n" " case CURLOPT_POST:\n" " $this->method = 'POST';\n" " break;\n" " case CURLOPT_POSTFIELDS:\n" " $this->postData = $value;\n" " if (!$this->method) {\n" " $this->method = 'POST';\n" " }\n" " break;\n" " /*\n" " * Upload\n" " */\n" " case CURLOPT_SAFE_UPLOAD:\n" " if (!$value) {\n" " trigger_error('swoole_curl_setopt(): Disabling safe uploads is no longer supported', E_USER_WARNING);\n" " return false;\n" " }\n" " break;\n" " /*\n" " * Http Header\n" " */\n" " case CURLOPT_HTTPHEADER:\n" " if (!is_array($value) and !is_iterable($value)) {\n" " trigger_error('swoole_curl_setopt(): You must pass either an object or an array with the CURLOPT_HTTPHEADER argument', E_USER_WARNING);\n" " return false;\n" " }\n" " foreach ($value as $header) {\n" " $header = explode(':', $header, 2);\n" " $headerName = $header[0];\n" " $headerValue = trim($header[1] ?? '');\n" " $this->setHeader($headerName, $headerValue);\n" " }\n" " break;\n" " case CURLOPT_REFERER:\n" " $this->setHeader('Referer', $value);\n" " break;\n" " case CURLINFO_HEADER_OUT:\n" " $this->withHeaderOut = boolval($value);\n" " break;\n" " case CURLOPT_FILETIME:\n" " $this->withFileTime = boolval($value);\n" " break;\n" " case CURLOPT_USERAGENT:\n" " $this->setHeader('User-Agent', $value);\n" " break;\n" " case CURLOPT_CUSTOMREQUEST:\n" " $this->method = (string) $value;\n" " break;\n" " case CURLOPT_PROTOCOLS:\n" " if (($value & ~(CURLPROTO_HTTP | CURLPROTO_HTTPS)) != 0) {\n" " throw new CurlException(\"swoole_curl_setopt(): CURLOPT_PROTOCOLS[{$value}] is not supported\");\n" " }\n" " break;\n" " case CURLOPT_REDIR_PROTOCOLS:\n" " if (($value & ~(CURLPROTO_HTTP | CURLPROTO_HTTPS)) != 0) {\n" " throw new CurlException(\"swoole_curl_setopt(): CURLOPT_REDIR_PROTOCOLS[{$value}] is not supported\");\n" " }\n" " break;\n" " case CURLOPT_HTTP_VERSION:\n" " if ($value != CURL_HTTP_VERSION_1_1) {\n" " trigger_error(\"swoole_curl_setopt(): CURLOPT_HTTP_VERSION[{$value}] is not supported\", E_USER_WARNING);\n" " return false;\n" " }\n" " break;\n" " case CURLOPT_FAILONERROR:\n" " $this->failOnError = $value;\n" " break;\n" " /*\n" " * Http Cookie\n" " */\n" " case CURLOPT_COOKIE:\n" " $this->setHeader('Cookie', $value);\n" " break;\n" " case CURLOPT_COOKIEJAR:\n" " $this->cookieJar = (string) $value;\n" " break;\n" " case CURLOPT_COOKIEFILE:\n" " if (is_file((string) $value)) {\n" " $this->setHeader('Cookie', file_get_contents($value));\n" " }\n" " break;\n" " case CURLOPT_CONNECTTIMEOUT:\n" " $this->clientOptions[Constant::OPTION_CONNECT_TIMEOUT] = $value;\n" " break;\n" " case CURLOPT_CONNECTTIMEOUT_MS:\n" " $this->clientOptions[Constant::OPTION_CONNECT_TIMEOUT] = $value / 1000;\n" " break;\n" " case CURLOPT_TIMEOUT:\n" " $this->clientOptions[Constant::OPTION_TIMEOUT] = $value;\n" " break;\n" " case CURLOPT_TIMEOUT_MS:\n" " $this->clientOptions[Constant::OPTION_TIMEOUT] = $value / 1000;\n" " break;\n" " case CURLOPT_FILE:\n" " $this->outputStream = $value;\n" " break;\n" " case CURLOPT_HEADER:\n" " $this->withHeader = $value;\n" " break;\n" " case CURLOPT_HEADERFUNCTION:\n" " $this->headerFunction = $value;\n" " break;\n" " case CURLOPT_READFUNCTION:\n" " $this->readFunction = $value;\n" " break;\n" " case CURLOPT_WRITEFUNCTION:\n" " if (SWOOLE_VERSION_ID >= 50100) {\n" " $this->clientOptions[Constant::OPTION_WRITE_FUNC] = function ($client, $data) use ($value) {\n" " return $value($this, $data);\n" " };\n" " } else {\n" " $this->writeFunction = $value;\n" " }\n" " break;\n" " case CURLOPT_NOPROGRESS:\n" " $this->noProgress = $value;\n" " break;\n" " case CURLOPT_PROGRESSFUNCTION:\n" " $this->progressFunction = $value;\n" " break;\n" " case CURLOPT_HTTPAUTH:\n" " if (!($value & CURLAUTH_BASIC)) {\n" " trigger_error(\"swoole_curl_setopt(): CURLOPT_HTTPAUTH[{$value}] is not supported\", E_USER_WARNING);\n" " return false;\n" " }\n" " break;\n" " case CURLOPT_USERPWD:\n" " $this->setHeader('Authorization', 'Basic ' . base64_encode($value));\n" " break;\n" " case CURLOPT_FOLLOWLOCATION:\n" " $this->followLocation = $value;\n" " break;\n" " case CURLOPT_AUTOREFERER:\n" " $this->autoReferer = $value;\n" " break;\n" " case CURLOPT_MAXREDIRS:\n" " $this->maxRedirects = $value;\n" " break;\n" " case CURLOPT_PUT:\n" " case CURLOPT_UPLOAD:\n" " /* after libcurl 7.12, CURLOPT_PUT is replaced by CURLOPT_UPLOAD */\n" " $this->method = 'PUT';\n" " break;\n" " case CURLOPT_INFILE:\n" " $this->infile = $value;\n" " break;\n" " case CURLOPT_INFILESIZE:\n" " $this->infileSize = $value;\n" " break;\n" " case CURLOPT_HTTPGET:\n" " /* Since GET is the default, this is only necessary if the request method has been changed. */\n" " $this->method = 'GET';\n" " break;\n" " default:\n" " throw new CurlException(\"swoole_curl_setopt(): option[{$opt}] is not supported\");\n" " }\n" " return true;\n" " }\n" "\n" " private function execute()\n" " {\n" " $this->info['redirect_count'] = $this->info['starttransfer_time'] = 0;\n" " $this->info['redirect_url'] = '';\n" " $timeBegin = microtime(true);\n" " /*\n" " * Socket\n" " */\n" " if (!$this->urlInfo) {\n" " $this->setError(CURLE_URL_MALFORMAT, 'No URL set or URL using bad/illegal format');\n" " return false;\n" " }\n" " if (!isset($this->client)) {\n" " $this->create();\n" " }\n" " while (true) {\n" " $client = $this->client;\n" " /*\n" " * Http Proxy\n" " */\n" " if ($this->proxy) {\n" " $parse = parse_url($this->proxy);\n" " $proxy = $parse['host'] ?? $parse['path'];\n" " $proxyPort = $parse['port'] ?? $this->proxyPort;\n" " $proxyUsername = $parse['user'] ?? $this->proxyUsername;\n" " $proxyPassword = $parse['pass'] ?? $this->proxyPassword;\n" " $proxyType = $parse['scheme'] ?? $this->proxyType;\n" " if (is_string($proxyType)) {\n" " if ($proxyType === 'socks5') {\n" " $proxyType = CURLPROXY_SOCKS5;\n" " } else {\n" " $proxyType = CURLPROXY_HTTP;\n" " }\n" " }\n" "\n" " if (!filter_var($proxy, FILTER_VALIDATE_IP)) {\n" " $ip = System::gethostbyname($proxy, AF_INET, $this->clientOptions['connect_timeout'] ?? -1);\n" " if (!$ip) {\n" " $this->setError(CURLE_COULDNT_RESOLVE_PROXY, 'Could not resolve proxy: ' . $proxy);\n" " return false;\n" " }\n" " $this->proxy = $proxy = $ip;\n" " }\n" " $proxyOptions = match ($proxyType) {\n" " CURLPROXY_HTTP => [\n" " 'http_proxy_host' => $proxy,\n" " 'http_proxy_port' => $proxyPort,\n" " 'http_proxy_username' => $proxyUsername,\n" " 'http_proxy_password' => $proxyPassword,\n" " ],\n" " CURLPROXY_SOCKS5 => [\n" " 'socks5_host' => $proxy,\n" " 'socks5_port' => $proxyPort,\n" " 'socks5_username' => $proxyUsername,\n" " 'socks5_password' => $proxyPassword,\n" " ],\n" " default => throw new CurlException(\"Unexpected proxy type [{$proxyType}]\"),\n" " };\n" " }\n" " /*\n" " * Client Options\n" " */\n" " $client->set(\n" " $this->clientOptions +\n" " ($proxyOptions ?? [])\n" " );\n" " /*\n" " * Method\n" " */\n" " if ($this->method) {\n" " $client->setMethod($this->method);\n" " }\n" " /*\n" " * Data\n" " */\n" " if ($this->infile) {\n" " // Infile\n" " // Notice: we make its priority higher than postData but raw cURL will send both of them\n" " $data = '';\n" " while (true) {\n" " $nLength = $this->infileSize - strlen($data);\n" " if ($nLength === 0) {\n" " break;\n" " }\n" " if (feof($this->infile)) {\n" " break;\n" " }\n" " $data .= fread($this->infile, $nLength);\n" " }\n" " $client->setData($data);\n" " // Notice: although we reset it, raw cURL never do this\n" " $this->infile = null;\n" " $this->infileSize = PHP_INT_MAX;\n" " } else {\n" " // POST data\n" " if ($this->postData) {\n" " if (is_string($this->postData)) {\n" " if (!$this->hasHeader('content-type')) {\n" " $this->setHeader('Content-Type', 'application/x-www-form-urlencoded');\n" " }\n" " } elseif (is_array($this->postData)) {\n" " foreach ($this->postData as $k => $v) {\n" " if ($v instanceof \\CURLFile) {\n" " $client->addFile($v->getFilename(), $k, $v->getMimeType() ?: 'application/octet-stream', $v->getPostFilename());\n" " unset($this->postData[$k]);\n" " }\n" " }\n" " }\n" " $client->setData($this->postData);\n" " }\n" " }\n" " /*\n" " * Headers\n" " */\n" " // Notice: setHeaders must be placed last, because headers may be changed by other parts\n" " // As much as possible to ensure that Host is the first header.\n" " // See: http://tools.ietf.org/html/rfc7230#section-5.4\n" " $client->setHeaders($this->headers);\n" " /**\n" " * Execute.\n" " */\n" " $executeResult = $client->execute($this->getUrl());\n" " if (!$executeResult) {\n" " $errCode = $client->errCode;\n" " if ($errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED or $errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT) {\n" " $this->setError(CURLE_COULDNT_RESOLVE_HOST, 'Could not resolve host: ' . $client->host);\n" " } else {\n" " $this->setError($errCode, $client->errMsg);\n" " }\n" " $this->info['total_time'] = microtime(true) - $timeBegin;\n" " return false;\n" " }\n" " if ($client->statusCode >= 300 and $client->statusCode < 400 and isset($client->headers['location'])) {\n" " $redirectParsedUrl = $this->getRedirectUrl($client->headers['location']);\n" " $redirectUrl = self::unparseUrl($redirectParsedUrl);\n" " if ($this->followLocation and ($this->maxRedirects === null or $this->info['redirect_count'] < $this->maxRedirects)) {\n" " if ($this->info['redirect_count'] === 0) {\n" " $this->info['starttransfer_time'] = microtime(true) - $timeBegin;\n" " $redirectBeginTime = microtime(true);\n" " }\n" " // force GET\n" " if (in_array($client->statusCode, [Status::MOVED_PERMANENTLY, Status::FOUND, Status::SEE_OTHER])) {\n" " $this->method = 'GET';\n" " }\n" " if ($this->autoReferer) {\n" " $this->setHeader('Referer', $this->info['url']);\n" " }\n" " $this->setUrl($redirectUrl, false);\n" " $this->setUrlInfo($redirectParsedUrl);\n" " $this->info['redirect_count']++;\n" " } else {\n" " $this->info['redirect_url'] = $redirectUrl;\n" " break;\n" " }\n" " } elseif ($this->failOnError && $client->statusCode >= 400) {\n" " $this->setError(CURLE_HTTP_RETURNED_ERROR, \"The requested URL returned error: {$client->statusCode} \" . Status::getReasonPhrase($client->statusCode));\n" " return false;\n" " } else {\n" " break;\n" " }\n" " }\n" " $this->info['total_time'] = microtime(true) - $timeBegin;\n" " $this->info['http_code'] = $client->statusCode;\n" " $this->info['content_type'] = $client->headers['content-type'] ?? '';\n" " $this->info['size_download'] = $this->info['download_content_length'] = strlen($client->body);\n" " $this->info['speed_download'] = 1 / $this->info['total_time'] * $this->info['size_download'];\n" " if (isset($redirectBeginTime)) {\n" " $this->info['redirect_time'] = microtime(true) - $redirectBeginTime;\n" " }\n" "\n" " if (filter_var($this->urlInfo['host'], FILTER_VALIDATE_IP)) {\n" " $this->info['primary_ip'] = $this->urlInfo['host'];\n" " }\n" "\n" " if ($this->unix_socket_path) {\n" " $this->info['primary_ip'] = $this->unix_socket_path;\n" " $this->info['primary_port'] = $this->urlInfo['port'];\n" " }\n" "\n" " $headerContent = '';\n" " if ($client->headers) {\n" " $cb = $this->headerFunction;\n" " if ($client->statusCode > 0) {\n" " $row = \"HTTP/1.1 {$client->statusCode} \" . Status::getReasonPhrase($client->statusCode) . \"\\r\\n\";\n" " if ($cb) {\n" " $cb($this, $row);\n" " }\n" " $headerContent .= $row;\n" " }\n" " foreach ($client->headers as $k => $v) {\n" " $list = is_array($v) ? $v : [$v];\n" " foreach ($list as $_v) {\n" " $row = \"{$k}: {$_v}\\r\\n\";\n" " if ($cb) {\n" " $cb($this, $row);\n" " }\n" " $headerContent .= $row;\n" " }\n" " }\n" " $headerContent .= \"\\r\\n\";\n" " $this->info['header_size'] = strlen($headerContent);\n" " if ($cb) {\n" " $cb($this, '');\n" " }\n" " } else {\n" " $this->info['header_size'] = 0;\n" " }\n" "\n" " if ($client->body and $this->readFunction) {\n" " $cb = $this->readFunction;\n" " $cb($this, $this->outputStream, strlen($client->body));\n" " }\n" "\n" " if ($this->withHeader) {\n" " $transfer = $headerContent . $client->body;\n" " } else {\n" " $transfer = $client->body;\n" " }\n" "\n" " if ($this->withHeaderOut) {\n" " $headerOutContent = $client->getHeaderOut();\n" " $this->info['request_header'] = $headerOutContent ? $headerOutContent . \"\\r\\n\\r\\n\" : '';\n" " }\n" " if ($this->withFileTime) {\n" " if (isset($client->headers['last-modified'])) {\n" " $this->info['filetime'] = strtotime($client->headers['last-modified']);\n" " } else {\n" " $this->info['filetime'] = -1;\n" " }\n" " }\n" "\n" " if (!empty($this->cookieJar)) {\n" " if ($this->cookieJar === '-') {\n" " foreach ((array) $client->set_cookie_headers as $cookie) {\n" " echo $cookie . PHP_EOL;\n" " }\n" " } else {\n" " $cookies = '';\n" " foreach ((array) $client->set_cookie_headers as $cookie) {\n" " $cookies .= \"{$cookie};\";\n" " }\n" " file_put_contents($this->cookieJar, $cookies);\n" " }\n" " }\n" "\n" " if ($this->writeFunction) {\n" " if (!is_callable($this->writeFunction)) { // @phpstan-ignore booleanNot.alwaysFalse\n" " trigger_error('curl_exec(): Could not call the CURLOPT_WRITEFUNCTION', E_USER_WARNING);\n" " $this->setError(CURLE_WRITE_ERROR, 'Failure writing output to destination');\n" " return false;\n" " }\n" " call_user_func($this->writeFunction, $this, $transfer);\n" " return true;\n" " }\n" "\n" " if ($this->returnTransfer) {\n" " return $this->transfer = $transfer;\n" " }\n" " if ($this->outputStream) {\n" " return fwrite($this->outputStream, $transfer) === strlen($transfer);\n" " }\n" " echo $transfer;\n" "\n" " return true;\n" " }\n" "\n" " /* ====== Redirect helper ====== */\n" "\n" " private static function unparseUrl(array $parsedUrl): string\n" " {\n" " $scheme = ($parsedUrl['scheme'] ?? 'http') . '://';\n" " $host = $parsedUrl['host'] ?? '';\n" " $port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';\n" " $user = $parsedUrl['user'] ?? '';\n" " $pass = isset($parsedUrl['pass']) ? ':' . $parsedUrl['pass'] : '';\n" " $pass = ($user or $pass) ? \"{$pass}@\" : '';\n" " $path = $parsedUrl['path'] ?? '';\n" " $query = (isset($parsedUrl['query']) and $parsedUrl['query'] !== '') ? '?' . $parsedUrl['query'] : '';\n" " $fragment = isset($parsedUrl['fragment']) ? '#' . $parsedUrl['fragment'] : '';\n" " return $scheme . $user . $pass . $host . $port . $path . $query . $fragment;\n" " }\n" "\n" " private function getRedirectUrl(string $location): array\n" " {\n" " $uri = parse_url($location);\n" " if (isset($uri['host'])) {\n" " $redirectUri = $uri;\n" " } else {\n" " if (!isset($location[0])) {\n" " return [];\n" " }\n" " $redirectUri = $this->urlInfo;\n" " $redirectUri['query'] = '';\n" " if ($location[0] === '/') {\n" " $redirectUri['path'] = $location;\n" " } else {\n" " $path = dirname($redirectUri['path'] ?? '');\n" " if ($path === '.') {\n" " $path = '/';\n" " }\n" " if (isset($location[1]) and str_starts_with($location, './')) {\n" " $location = substr($location, 2);\n" " }\n" " $redirectUri['path'] = $path . $location;\n" " }\n" " if (is_array($uri)) {\n" " foreach ($uri as $k => $v) {\n" " if (!in_array($k, ['path', 'query'])) {\n" " $redirectUri[$k] = $v;\n" " }\n" " }\n" " }\n" " }\n" " return $redirectUri;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "/**\n" " * FastCGI constants.\n" " */\n" "class FastCGI\n" "{\n" " /**\n" " * Number of bytes in a FCGI_Header. Future versions of the protocol\n" " * will not reduce this number.\n" " */\n" " public const HEADER_LEN = 8;\n" "\n" " /**\n" " * Format of FCGI_HEADER for unpacking in PHP\n" " */\n" " public const HEADER_FORMAT = 'Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved';\n" "\n" " /**\n" " * Max content length of a record\n" " */\n" " public const MAX_CONTENT_LENGTH = 65535;\n" "\n" " /**\n" " * Value for version component of FCGI_Header\n" " */\n" " public const VERSION_1 = 1;\n" "\n" " /**\n" " * Values for type component of FCGI_Header\n" " */\n" " public const BEGIN_REQUEST = 1;\n" "\n" " public const ABORT_REQUEST = 2;\n" "\n" " public const END_REQUEST = 3;\n" "\n" " public const PARAMS = 4;\n" "\n" " public const STDIN = 5;\n" "\n" " public const STDOUT = 6;\n" "\n" " public const STDERR = 7;\n" "\n" " public const DATA = 8;\n" "\n" " public const GET_VALUES = 9;\n" "\n" " public const GET_VALUES_RESULT = 10;\n" "\n" " public const UNKNOWN_TYPE = 11;\n" "\n" " /**\n" " * Value for requestId component of FCGI_Header\n" " */\n" " public const DEFAULT_REQUEST_ID = 1;\n" "\n" " /**\n" " * Mask for flags component of FCGI_BeginRequestBody\n" " */\n" " public const KEEP_CONN = 1;\n" "\n" " /**\n" " * Values for role component of FCGI_BeginRequestBody\n" " */\n" " public const RESPONDER = 1;\n" "\n" " public const AUTHORIZER = 2;\n" "\n" " public const FILTER = 3;\n" "\n" " /**\n" " * Values for protocolStatus component of FCGI_EndRequestBody\n" " */\n" " public const REQUEST_COMPLETE = 0;\n" "\n" " public const CANT_MPX_CONN = 1;\n" "\n" " public const OVERLOADED = 2;\n" "\n" " public const UNKNOWN_ROLE = 3;\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "use Swoole\\FastCGI;\n" "\n" "/**\n" " * FastCGI record.\n" " */\n" "class Record implements \\Stringable\n" "{\n" " /**\n" " * Identifies the FastCGI protocol version.\n" " */\n" " protected int $version = FastCGI::VERSION_1;\n" "\n" " /**\n" " * Identifies the FastCGI record type, i.e. the general function that the record performs.\n" " */\n" " protected int $type = FastCGI::UNKNOWN_TYPE;\n" "\n" " /**\n" " * Identifies the FastCGI request to which the record belongs.\n" " */\n" " protected int $requestId = FastCGI::DEFAULT_REQUEST_ID;\n" "\n" " /**\n" " * Reserved byte for future proposes\n" " */\n" " protected int $reserved = 0;\n" "\n" " /**\n" " * The number of bytes in the contentData component of the record.\n" " */\n" " private int $contentLength = 0;\n" "\n" " /**\n" " * The number of bytes in the paddingData component of the record.\n" " */\n" " private int $paddingLength = 0;\n" "\n" " /**\n" " * Binary data, between 0 and 65535 bytes of data, interpreted according to the record type.\n" " */\n" " private string $contentData = '';\n" "\n" " /**\n" " * Padding data, between 0 and 255 bytes of data, which are ignored.\n" " */\n" " private string $paddingData = '';\n" "\n" " /**\n" " * Returns the binary message representation of record\n" " */\n" " final public function __toString(): string\n" " {\n" " $headerPacket = pack(\n" " 'CCnnCC',\n" " $this->version,\n" " $this->type,\n" " $this->requestId,\n" " $this->contentLength,\n" " $this->paddingLength,\n" " $this->reserved\n" " );\n" "\n" " $payloadPacket = $this->packPayload();\n" " $paddingPacket = pack(\"a{$this->paddingLength}\", $this->paddingData);\n" "\n" " return $headerPacket . $payloadPacket . $paddingPacket;\n" " }\n" "\n" " /**\n" " * Unpacks the message from the binary data buffer\n" " */\n" " final public static function unpack(string $binaryData): static\n" " {\n" " /** @var static $self */\n" " $self = (new \\ReflectionClass(static::class))->newInstanceWithoutConstructor();\n" "\n" " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int, reserved: int} */\n" " $packet = unpack(FastCGI::HEADER_FORMAT, $binaryData);\n" " if ($packet === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [\n" " $self->version,\n" " $self->type,\n" " $self->requestId,\n" " $self->contentLength,\n" " $self->paddingLength,\n" " $self->reserved,\n" " ] = array_values($packet);\n" "\n" " $payload = substr($binaryData, FastCGI::HEADER_LEN);\n" " self::unpackPayload($self, $payload);\n" " if (static::class !== self::class && $self->contentLength > 0) {\n" " static::unpackPayload($self, $payload);\n" " }\n" "\n" " return $self;\n" " }\n" "\n" " /**\n" " * Sets the content data and adjusts the length fields\n" " *\n" " * @return static\n" " */\n" " public function setContentData(string $data): self\n" " {\n" " $this->contentLength = strlen($data);\n" " if ($this->contentLength > FastCGI::MAX_CONTENT_LENGTH) {\n" " $this->contentLength = FastCGI::MAX_CONTENT_LENGTH;\n" " $this->contentData = substr($data, 0, FastCGI::MAX_CONTENT_LENGTH);\n" " } else {\n" " $this->contentData = $data;\n" " }\n" " $extraLength = $this->contentLength % 8;\n" " $this->paddingLength = $extraLength ? (8 - $extraLength) : 0;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Returns the context data from the record\n" " */\n" " public function getContentData(): string\n" " {\n" " return $this->contentData;\n" " }\n" "\n" " /**\n" " * Returns the version of record\n" " */\n" " public function getVersion(): int\n" " {\n" " return $this->version;\n" " }\n" "\n" " /**\n" " * Returns record type\n" " */\n" " public function getType(): int\n" " {\n" " return $this->type;\n" " }\n" "\n" " /**\n" " * Returns request ID\n" " */\n" " public function getRequestId(): int\n" " {\n" " return $this->requestId;\n" " }\n" "\n" " /**\n" " * Sets request ID\n" " *\n" " * There should be only one unique ID for all active requests,\n" " * use random number or preferably resetting auto-increment.\n" " *\n" " * @return static\n" " */\n" " public function setRequestId(int $requestId): self\n" " {\n" " $this->requestId = $requestId;\n" " return $this;\n" " }\n" "\n" " /**\n" " * Returns the size of content length\n" " */\n" " final public function getContentLength(): int\n" " {\n" " return $this->contentLength;\n" " }\n" "\n" " /**\n" " * Returns the size of padding length\n" " */\n" " final public function getPaddingLength(): int\n" " {\n" " return $this->paddingLength;\n" " }\n" "\n" " /**\n" " * Method to unpack the payload for the record.\n" " *\n" " * NB: Default implementation will be always called\n" " */\n" " protected static function unpackPayload(self $self, string $binaryData): void\n" " {\n" " /** @phpstan-var false|array{contentData: string, paddingData: string} */\n" " $payload = unpack(\"a{$self->contentLength}contentData/a{$self->paddingLength}paddingData\", $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [\n" " $self->contentData,\n" " $self->paddingData,\n" " ] = array_values($payload);\n" " }\n" "\n" " /**\n" " * Implementation of packing the payload\n" " */\n" " protected function packPayload(): string\n" " {\n" " return pack(\"a{$this->contentLength}\", $this->contentData);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_params = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Params request record\n" " */\n" "class Params extends Record\n" "{\n" " /**\n" " * List of params\n" " *\n" " * @var string[]\n" " * @phpstan-var array\n" " */\n" " protected array $values = [];\n" "\n" " /**\n" " * Constructs a param request\n" " *\n" " * @phpstan-param array $values\n" " */\n" " public function __construct(array $values)\n" " {\n" " $this->type = FastCGI::PARAMS;\n" " $this->values = $values;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" " /**\n" " * Returns an associative list of parameters\n" " *\n" " * @phpstan-return array\n" " */\n" " public function getValues(): array\n" " {\n" " return $this->values;\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " * @param static $self\n" " */\n" " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" " $currentOffset = 0;\n" " do {\n" " /** @phpstan-var false|array{nameLengthHigh: int} */\n" " $payload = unpack('CnameLengthHigh', $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [$nameLengthHigh] = array_values($payload);\n" " $isLongName = ($nameLengthHigh >> 7 == 1);\n" " $valueOffset = $isLongName ? 4 : 1;\n" "\n" " /** @phpstan-var false|array{valueLengthHigh: int} */\n" " $payload = unpack('CvalueLengthHigh', substr($binaryData, $valueOffset));\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [$valueLengthHigh] = array_values($payload);\n" " $isLongValue = ($valueLengthHigh >> 7 == 1);\n" " $dataOffset = $valueOffset + ($isLongValue ? 4 : 1);\n" "\n" " $formatParts = [\n" " $isLongName ? 'NnameLength' : 'CnameLength',\n" " $isLongValue ? 'NvalueLength' : 'CvalueLength',\n" " ];\n" " $format = join('/', $formatParts);\n" "\n" " /** @phpstan-var false|array{nameLength: int, valueLength: int} */\n" " $payload = unpack($format, $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [$nameLength, $valueLength] = array_values($payload);\n" "\n" " // Clear top bit for long record\n" " $nameLength &= ($isLongName ? 0x7FFFFFFF : 0x7F);\n" " $valueLength &= ($isLongValue ? 0x7FFFFFFF : 0x7F);\n" "\n" " /** @phpstan-var false|array{nameData: string, valueData: string} */\n" " $payload = unpack(\n" " \"a{$nameLength}nameData/a{$valueLength}valueData\",\n" " substr($binaryData, $dataOffset)\n" " );\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [$nameData, $valueData] = array_values($payload);\n" "\n" " $self->values[$nameData] = $valueData;\n" "\n" " $keyValueLength = $dataOffset + $nameLength + $valueLength;\n" " $binaryData = substr($binaryData, $keyValueLength);\n" " $currentOffset += $keyValueLength;\n" " } while ($currentOffset < $self->getContentLength());\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " */\n" " protected function packPayload(): string\n" " {\n" " $payload = '';\n" " foreach ($this->values as $nameData => $valueData) {\n" " if ($valueData === null) { // @phpstan-ignore identical.alwaysFalse\n" " continue;\n" " }\n" " $nameLength = strlen($nameData);\n" " $valueLength = strlen((string) $valueData);\n" " $isLongName = $nameLength > 127;\n" " $isLongValue = $valueLength > 127;\n" " $formatParts = [\n" " $isLongName ? 'N' : 'C',\n" " $isLongValue ? 'N' : 'C',\n" " \"a{$nameLength}\",\n" " \"a{$valueLength}\",\n" " ];\n" "\n" " $format = join('', $formatParts);\n" "\n" " $payload .= pack(\n" " $format,\n" " $isLongName ? ($nameLength | 0x80000000) : $nameLength,\n" " $isLongValue ? ($valueLength | 0x80000000) : $valueLength,\n" " $nameData,\n" " $valueData\n" " );\n" " }\n" "\n" " return $payload;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_abort_request = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * The Web server sends a FCGI_ABORT_REQUEST record to abort a request\n" " */\n" "class AbortRequest extends Record\n" "{\n" " public function __construct(int $requestId)\n" " {\n" " $this->type = FastCGI::ABORT_REQUEST;\n" " $this->setRequestId($requestId);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_begin_request = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * The Web server sends a FCGI_BEGIN_REQUEST record to start a request.\n" " */\n" "class BeginRequest extends Record\n" "{\n" " /**\n" " * The role component sets the role the Web server expects the application to play.\n" " * The currently-defined roles are:\n" " * FCGI_RESPONDER\n" " * FCGI_AUTHORIZER\n" " * FCGI_FILTER\n" " */\n" " protected int $role = FastCGI::UNKNOWN_ROLE;\n" "\n" " /**\n" " * The flags component contains a bit that controls connection shutdown.\n" " *\n" " * flags & FCGI_KEEP_CONN:\n" " * If zero, the application closes the connection after responding to this request.\n" " * If not zero, the application does not close the connection after responding to this request;\n" " * the Web server retains responsibility for the connection.\n" " */\n" " protected int $flags;\n" "\n" " /**\n" " * Reserved data, 5 bytes maximum\n" " */\n" " protected string $reserved1;\n" "\n" " public function __construct(int $role = FastCGI::UNKNOWN_ROLE, int $flags = 0, string $reserved = '')\n" " {\n" " $this->type = FastCGI::BEGIN_REQUEST;\n" " $this->role = $role;\n" " $this->flags = $flags;\n" " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" " /**\n" " * Returns the role\n" " *\n" " * The role component sets the role the Web server expects the application to play.\n" " * The currently-defined roles are:\n" " * FCGI_RESPONDER\n" " * FCGI_AUTHORIZER\n" " * FCGI_FILTER\n" " */\n" " public function getRole(): int\n" " {\n" " return $this->role;\n" " }\n" "\n" " /**\n" " * Returns the flags\n" " *\n" " * The flags component contains a bit that controls connection shutdown.\n" " *\n" " * flags & FCGI_KEEP_CONN:\n" " * If zero, the application closes the connection after responding to this request.\n" " * If not zero, the application does not close the connection after responding to this request;\n" " * the Web server retains responsibility for the connection.\n" " */\n" " public function getFlags(): int\n" " {\n" " return $this->flags;\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " * @param static $self\n" " */\n" " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" "\n" " /** @phpstan-var false|array{role: int, flags: int, reserved: string} */\n" " $payload = unpack('nrole/Cflags/a5reserved', $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [\n" " $self->role,\n" " $self->flags,\n" " $self->reserved1,\n" " ] = array_values($payload);\n" " }\n" "\n" " /** {@inheritdoc} */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" " 'nCa5',\n" " $this->role,\n" " $this->flags,\n" " $this->reserved1\n" " );\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_data = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Data binary stream\n" " *\n" " * FCGI_DATA is a second stream record type used to send additional data to the application.\n" " */\n" "class Data extends Record\n" "{\n" " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::DATA;\n" " $this->setContentData($contentData);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_end_request = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * The application sends a FCGI_END_REQUEST record to terminate a request, either because the application\n" " * has processed the request or because the application has rejected the request.\n" " */\n" "class EndRequest extends Record\n" "{\n" " /**\n" " * The appStatus component is an application-level status code. Each role documents its usage of appStatus.\n" " */\n" " protected int $appStatus = 0;\n" "\n" " /**\n" " * The protocolStatus component is a protocol-level status code.\n" " *\n" " * The possible protocolStatus values are:\n" " * FCGI_REQUEST_COMPLETE: normal end of request.\n" " * FCGI_CANT_MPX_CONN: rejecting a new request.\n" " * This happens when a Web server sends concurrent requests over one connection to an application that is\n" " * designed to process one request at a time per connection.\n" " * FCGI_OVERLOADED: rejecting a new request.\n" " * This happens when the application runs out of some resource, e.g. database connections.\n" " * FCGI_UNKNOWN_ROLE: rejecting a new request.\n" " * This happens when the Web server has specified a role that is unknown to the application.\n" " */\n" " protected int $protocolStatus = FastCGI::REQUEST_COMPLETE;\n" "\n" " /**\n" " * Reserved data, 3 bytes maximum\n" " */\n" " protected string $reserved1;\n" "\n" " public function __construct(int $protocolStatus = FastCGI::REQUEST_COMPLETE, int $appStatus = 0, string $reserved = '')\n" " {\n" " $this->type = FastCGI::END_REQUEST;\n" " $this->protocolStatus = $protocolStatus;\n" " $this->appStatus = $appStatus;\n" " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" " /**\n" " * Returns app status\n" " *\n" " * The appStatus component is an application-level status code. Each role documents its usage of appStatus.\n" " */\n" " public function getAppStatus(): int\n" " {\n" " return $this->appStatus;\n" " }\n" "\n" " /**\n" " * Returns the protocol status\n" " *\n" " * The possible protocolStatus values are:\n" " * FCGI_REQUEST_COMPLETE: normal end of request.\n" " * FCGI_CANT_MPX_CONN: rejecting a new request.\n" " * This happens when a Web server sends concurrent requests over one connection to an application that is\n" " * designed to process one request at a time per connection.\n" " * FCGI_OVERLOADED: rejecting a new request.\n" " * This happens when the application runs out of some resource, e.g. database connections.\n" " * FCGI_UNKNOWN_ROLE: rejecting a new request.\n" " * This happens when the Web server has specified a role that is unknown to the application.\n" " */\n" " public function getProtocolStatus(): int\n" " {\n" " return $this->protocolStatus;\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " * @param static $self\n" " */\n" " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" "\n" " /** @phpstan-var false|array{appStatus: int, protocolStatus: int, reserved: string} */\n" " $payload = unpack('NappStatus/CprotocolStatus/a3reserved', $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [\n" " $self->appStatus,\n" " $self->protocolStatus,\n" " $self->reserved1,\n" " ] = array_values($payload);\n" " }\n" "\n" " /** {@inheritdoc} */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" " 'NCa3',\n" " $this->appStatus,\n" " $this->protocolStatus,\n" " $this->reserved1\n" " );\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_get_values = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "\n" "/**\n" " * GetValues API\n" " *\n" " * The Web server can query specific variables within the application.\n" " * The server will typically perform a query on application startup in order to to automate certain aspects of\n" " * system configuration.\n" " *\n" " * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied.\n" " * If the application doesn't understand a variable name that was included in the query, it omits that name from\n" " * the response.\n" " *\n" " * FCGI_GET_VALUES is designed to allow an open-ended set of variables.\n" " *\n" " * The initial set provides information to help the server perform application and connection management:\n" " * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept,\n" " * e.g. \"1\" or \"10\".\n" " * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. \"1\" or \"50\".\n" " * FCGI_MPXS_CONNS: \"0\" if this application does not multiplex connections (i.e. handle concurrent requests\n" " * over each connection), \"1\" otherwise.\n" " */\n" "class GetValues extends Params\n" "{\n" " /**\n" " * Constructs a request\n" " *\n" " * @param array $keys List of keys to receive\n" " *\n" " * @phpstan-param list $keys\n" " */\n" " public function __construct(array $keys)\n" " {\n" " parent::__construct(array_fill_keys($keys, ''));\n" " $this->type = FastCGI::GET_VALUES;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_get_values_result = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "\n" "/**\n" " * GetValues API\n" " *\n" " * The Web server can query specific variables within the application.\n" " * The server will typically perform a query on application startup in order to to automate certain aspects of\n" " * system configuration.\n" " *\n" " * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied.\n" " * If the application doesn't understand a variable name that was included in the query, it omits that name from\n" " * the response.\n" " *\n" " * FCGI_GET_VALUES is designed to allow an open-ended set of variables.\n" " *\n" " * The initial set provides information to help the server perform application and connection management:\n" " * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept,\n" " * e.g. \"1\" or \"10\".\n" " * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. \"1\" or \"50\".\n" " * FCGI_MPXS_CONNS: \"0\" if this application does not multiplex connections (i.e. handle concurrent requests\n" " * over each connection), \"1\" otherwise.\n" " */\n" "class GetValuesResult extends Params\n" "{\n" " /**\n" " * Constructs a param request\n" " *\n" " * @phpstan-param array $values\n" " */\n" " public function __construct(array $values)\n" " {\n" " parent::__construct($values);\n" " $this->type = FastCGI::GET_VALUES_RESULT;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_stdin = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Stdin binary stream\n" " *\n" " * FCGI_STDIN is a stream record type used in sending arbitrary data from the Web server to the application\n" " */\n" "class Stdin extends Record\n" "{\n" " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDIN;\n" " $this->setContentData($contentData);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_stdout = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Stdout binary stream\n" " *\n" " * FCGI_STDOUT is a stream record for sending arbitrary data from the application to the Web server\n" " */\n" "class Stdout extends Record\n" "{\n" " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDOUT;\n" " $this->setContentData($contentData);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_stderr = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Stderr binary stream\n" " *\n" " * FCGI_STDERR is a stream record for sending arbitrary data from the application to the Web server\n" " */\n" "class Stderr extends Record\n" "{\n" " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDERR;\n" " $this->setContentData($contentData);\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_record_unknown_type = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI\\Record;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" "/**\n" " * Record for unknown queries\n" " *\n" " * The set of management record types is likely to grow in future versions of this protocol.\n" " * To provide for this evolution, the protocol includes the FCGI_UNKNOWN_TYPE management record.\n" " * When an application receives a management record whose type T it does not understand, the application responds\n" " * with {FCGI_UNKNOWN_TYPE, 0, {T}}.\n" " */\n" "class UnknownType extends Record\n" "{\n" " /**\n" " * Type of the unrecognized management record.\n" " */\n" " protected int $type1;\n" "\n" " /**\n" " * Reserved data, 7 bytes maximum\n" " */\n" " protected string $reserved1;\n" "\n" " public function __construct(int $type, string $reserved = '')\n" " {\n" " $this->type = FastCGI::UNKNOWN_TYPE;\n" " $this->type1 = $type;\n" " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" " /**\n" " * Returns the unrecognized type\n" " */\n" " public function getUnrecognizedType(): int\n" " {\n" " return $this->type1;\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " * @param static $self\n" " */\n" " public static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" "\n" " /** @phpstan-var false|array{type: int, reserved: string} */\n" " $payload = unpack('Ctype/a7reserved', $binaryData);\n" " if ($payload === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " [$self->type1, $self->reserved1] = array_values($payload);\n" " }\n" "\n" " /**\n" " * {@inheritdoc}\n" " */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" " 'Ca7',\n" " $this->type1,\n" " $this->reserved1\n" " );\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_frame_parser = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record\\AbortRequest;\n" "use Swoole\\FastCGI\\Record\\BeginRequest;\n" "use Swoole\\FastCGI\\Record\\Data;\n" "use Swoole\\FastCGI\\Record\\EndRequest;\n" "use Swoole\\FastCGI\\Record\\GetValues;\n" "use Swoole\\FastCGI\\Record\\GetValuesResult;\n" "use Swoole\\FastCGI\\Record\\Params;\n" "use Swoole\\FastCGI\\Record\\Stderr;\n" "use Swoole\\FastCGI\\Record\\Stdin;\n" "use Swoole\\FastCGI\\Record\\Stdout;\n" "use Swoole\\FastCGI\\Record\\UnknownType;\n" "\n" "/**\n" " * Utility class to simplify parsing of FastCGI protocol data.\n" " */\n" "class FrameParser\n" "{\n" " /**\n" " * Mapping of constants to the classes\n" " *\n" " * @phpstan-var array\n" " */\n" " protected static array $classMapping = [\n" " FastCGI::BEGIN_REQUEST => BeginRequest::class,\n" " FastCGI::ABORT_REQUEST => AbortRequest::class,\n" " FastCGI::END_REQUEST => EndRequest::class,\n" " FastCGI::PARAMS => Params::class,\n" " FastCGI::STDIN => Stdin::class,\n" " FastCGI::STDOUT => Stdout::class,\n" " FastCGI::STDERR => Stderr::class,\n" " FastCGI::DATA => Data::class,\n" " FastCGI::GET_VALUES => GetValues::class,\n" " FastCGI::GET_VALUES_RESULT => GetValuesResult::class,\n" " FastCGI::UNKNOWN_TYPE => UnknownType::class,\n" " ];\n" "\n" " /**\n" " * Checks if the buffer contains a valid frame to parse\n" " */\n" " public static function hasFrame(string $binaryBuffer): bool\n" " {\n" " $bufferLength = strlen($binaryBuffer);\n" " if ($bufferLength < FastCGI::HEADER_LEN) {\n" " return false;\n" " }\n" "\n" " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */\n" " $fastInfo = unpack(FastCGI::HEADER_FORMAT, $binaryBuffer);\n" " if ($fastInfo === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " if ($bufferLength < FastCGI::HEADER_LEN + $fastInfo['contentLength'] + $fastInfo['paddingLength']) {\n" " return false;\n" " }\n" "\n" " return true;\n" " }\n" "\n" " /**\n" " * Parses a frame from the binary buffer\n" " *\n" " * @return Record One of the corresponding FastCGI record\n" " */\n" " public static function parseFrame(string &$binaryBuffer): Record\n" " {\n" " $bufferLength = strlen($binaryBuffer);\n" " if ($bufferLength < FastCGI::HEADER_LEN) {\n" " throw new \\RuntimeException('Not enough data in the buffer to parse');\n" " }\n" " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */\n" " $recordHeader = unpack(FastCGI::HEADER_FORMAT, $binaryBuffer);\n" " if ($recordHeader === false) {\n" " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" " $recordType = $recordHeader['type'];\n" " if (!isset(self::$classMapping[$recordType])) {\n" " throw new \\DomainException(\"Invalid FastCGI record type {$recordType} received\");\n" " }\n" "\n" " /** @var Record $className */\n" " $className = self::$classMapping[$recordType];\n" " $record = $className::unpack($binaryBuffer);\n" "\n" " $offset = FastCGI::HEADER_LEN + $record->getContentLength() + $record->getPaddingLength();\n" " $binaryBuffer = substr($binaryBuffer, $offset);\n" "\n" " return $record;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_message = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "class Message\n" "{\n" " protected array $params = [];\n" "\n" " protected string $body = '';\n" "\n" " protected string $error = '';\n" "\n" " public function getParam(string $name): ?string\n" " {\n" " return $this->params[$name] ?? null;\n" " }\n" "\n" " public function withParam(string $name, string $value): static\n" " {\n" " $this->params[$name] = $value;\n" " return $this;\n" " }\n" "\n" " public function withoutParam(string $name): static\n" " {\n" " unset($this->params[$name]);\n" " return $this;\n" " }\n" "\n" " public function getParams(): array\n" " {\n" " return $this->params;\n" " }\n" "\n" " public function withParams(array $params): static\n" " {\n" " $this->params = $params;\n" " return $this;\n" " }\n" "\n" " public function withAddedParams(array $params): static\n" " {\n" " $this->params = $params + $this->params;\n" " return $this;\n" " }\n" "\n" " public function getBody(): string\n" " {\n" " return $this->body;\n" " }\n" "\n" " public function withBody(string|\\Stringable $body): self\n" " {\n" " $this->body = (string) $body;\n" " return $this;\n" " }\n" "\n" " public function getError(): string\n" " {\n" " return $this->error;\n" " }\n" "\n" " public function withError(string $error): static\n" " {\n" " $this->error = $error;\n" " return $this;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_request = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record\\BeginRequest;\n" "use Swoole\\FastCGI\\Record\\Params;\n" "use Swoole\\FastCGI\\Record\\Stdin;\n" "\n" "class Request extends Message implements \\Stringable\n" "{\n" " protected bool $keepConn = false;\n" "\n" " public function __toString(): string\n" " {\n" " $body = $this->getBody();\n" " $beginRequestFrame = new BeginRequest(FastCGI::RESPONDER, $this->keepConn ? FastCGI::KEEP_CONN : 0);\n" " $paramsFrame = new Params($this->getParams());\n" " $paramsEofFrame = new Params([]);\n" " if (empty($body)) {\n" " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}\";\n" " } else {\n" " $stdinList = [];\n" " while (true) {\n" " $stdinList[] = $stdin = new Stdin($body);\n" " $stdinLength = $stdin->getContentLength();\n" " if ($stdinLength === strlen($body)) {\n" " break;\n" " }\n" " $body = substr($body, $stdinLength);\n" " }\n" " $stdinList[] = new Stdin('');\n" " $stdin = implode('', $stdinList);\n" " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}{$stdin}\";\n" " }\n" " return $message;\n" " }\n" "\n" " public function getKeepConn(): bool\n" " {\n" " return $this->keepConn;\n" " }\n" "\n" " public function withKeepConn(bool $keepConn): self\n" " {\n" " $this->keepConn = $keepConn;\n" " return $this;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_response = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "use Swoole\\FastCGI\\Record\\EndRequest;\n" "use Swoole\\FastCGI\\Record\\Stderr;\n" "use Swoole\\FastCGI\\Record\\Stdout;\n" "\n" "class Response extends Message\n" "{\n" " /**\n" " * @param array $records\n" " */\n" " public function __construct(array $records)\n" " {\n" " if (!static::verify($records)) {\n" " throw new \\InvalidArgumentException('Bad records');\n" " }\n" "\n" " $body = $error = '';\n" " foreach ($records as $record) {\n" " if ($record instanceof Stdout) {\n" " if ($record->getContentLength() > 0) {\n" " $body .= $record->getContentData();\n" " }\n" " } elseif ($record instanceof Stderr) {\n" " if ($record->getContentLength() > 0) {\n" " $error .= $record->getContentData();\n" " }\n" " }\n" " }\n" " $this->withBody($body)->withError($error);\n" " }\n" "\n" " /**\n" " * @param array $records\n" " */\n" " protected static function verify(array $records): bool\n" " {\n" " return !empty($records) && $records[array_key_last($records)] instanceof EndRequest;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_http_request = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "class HttpRequest extends Request\n" "{\n" " protected array $params = [\n" " 'REQUEST_SCHEME' => 'http',\n" " 'REQUEST_METHOD' => 'GET',\n" " 'DOCUMENT_ROOT' => '',\n" " 'SCRIPT_FILENAME' => '',\n" " 'SCRIPT_NAME' => '',\n" " 'DOCUMENT_URI' => '/',\n" " 'REQUEST_URI' => '/',\n" " 'QUERY_STRING' => '',\n" " 'CONTENT_TYPE' => 'text/plain',\n" " 'CONTENT_LENGTH' => '0',\n" " 'GATEWAY_INTERFACE' => 'CGI/1.1',\n" " 'SERVER_PROTOCOL' => 'HTTP/1.1',\n" " 'SERVER_SOFTWARE' => 'swoole/' . SWOOLE_VERSION,\n" " 'REMOTE_ADDR' => 'unknown',\n" " 'REMOTE_PORT' => '0',\n" " 'SERVER_ADDR' => 'unknown',\n" " 'SERVER_PORT' => '0',\n" " 'SERVER_NAME' => 'Swoole',\n" " 'REDIRECT_STATUS' => '200',\n" " ];\n" "\n" " public function getScheme(): ?string\n" " {\n" " return $this->params['REQUEST_SCHEME'] ?? null;\n" " }\n" "\n" " public function withScheme(string $scheme): self\n" " {\n" " $this->params['REQUEST_SCHEME'] = $scheme;\n" " return $this;\n" " }\n" "\n" " public function withoutScheme(): void\n" " {\n" " unset($this->params['REQUEST_SCHEME']);\n" " }\n" "\n" " public function getMethod(): ?string\n" " {\n" " return $this->params['REQUEST_METHOD'] ?? null;\n" " }\n" "\n" " public function withMethod(string $method): self\n" " {\n" " $this->params['REQUEST_METHOD'] = $method;\n" " return $this;\n" " }\n" "\n" " public function withoutMethod(): void\n" " {\n" " unset($this->params['REQUEST_METHOD']);\n" " }\n" "\n" " public function getDocumentRoot(): ?string\n" " {\n" " return $this->params['DOCUMENT_ROOT'] ?? null;\n" " }\n" "\n" " public function withDocumentRoot(string $documentRoot): self\n" " {\n" " $this->params['DOCUMENT_ROOT'] = $documentRoot;\n" " return $this;\n" " }\n" "\n" " public function withoutDocumentRoot(): void\n" " {\n" " unset($this->params['DOCUMENT_ROOT']);\n" " }\n" "\n" " public function getScriptFilename(): ?string\n" " {\n" " return $this->params['SCRIPT_FILENAME'] ?? null;\n" " }\n" "\n" " public function withScriptFilename(string $scriptFilename): self\n" " {\n" " $this->params['SCRIPT_FILENAME'] = $scriptFilename;\n" " return $this;\n" " }\n" "\n" " public function withoutScriptFilename(): void\n" " {\n" " unset($this->params['SCRIPT_FILENAME']);\n" " }\n" "\n" " public function getScriptName(): ?string\n" " {\n" " return $this->params['SCRIPT_NAME'] ?? null;\n" " }\n" "\n" " public function withScriptName(string $scriptName): self\n" " {\n" " $this->params['SCRIPT_NAME'] = $scriptName;\n" " return $this;\n" " }\n" "\n" " public function withoutScriptName(): void\n" " {\n" " unset($this->params['SCRIPT_NAME']);\n" " }\n" "\n" " public function withUri(string $uri): self\n" " {\n" " $info = parse_url($uri);\n" " return $this->withRequestUri($uri)\n" " ->withDocumentUri($info['path'] ?? '')\n" " ->withQueryString($info['query'] ?? '')\n" " ;\n" " }\n" "\n" " public function getDocumentUri(): ?string\n" " {\n" " return $this->params['DOCUMENT_URI'] ?? null;\n" " }\n" "\n" " public function withDocumentUri(string $documentUri): self\n" " {\n" " $this->params['DOCUMENT_URI'] = $documentUri;\n" " return $this;\n" " }\n" "\n" " public function withoutDocumentUri(): void\n" " {\n" " unset($this->params['DOCUMENT_URI']);\n" " }\n" "\n" " public function getRequestUri(): ?string\n" " {\n" " return $this->params['REQUEST_URI'] ?? null;\n" " }\n" "\n" " public function withRequestUri(string $requestUri): self\n" " {\n" " $this->params['REQUEST_URI'] = $requestUri;\n" " return $this;\n" " }\n" "\n" " public function withoutRequestUri(): void\n" " {\n" " unset($this->params['REQUEST_URI']);\n" " }\n" "\n" " public function withQuery($query): self\n" " {\n" " if (is_array($query)) {\n" " $query = http_build_query($query);\n" " }\n" " return $this->withQueryString($query);\n" " }\n" "\n" " public function getQueryString(): ?string\n" " {\n" " return $this->params['QUERY_STRING'] ?? null;\n" " }\n" "\n" " public function withQueryString(string $queryString): self\n" " {\n" " $this->params['QUERY_STRING'] = $queryString;\n" " return $this;\n" " }\n" "\n" " public function withoutQueryString(): void\n" " {\n" " unset($this->params['QUERY_STRING']);\n" " }\n" "\n" " public function getContentType(): ?string\n" " {\n" " return $this->params['CONTENT_TYPE'] ?? null;\n" " }\n" "\n" " public function withContentType(string $contentType): self\n" " {\n" " $this->params['CONTENT_TYPE'] = $contentType;\n" " return $this;\n" " }\n" "\n" " public function withoutContentType(): void\n" " {\n" " unset($this->params['CONTENT_TYPE']);\n" " }\n" "\n" " public function getContentLength(): ?int\n" " {\n" " return isset($this->params['CONTENT_LENGTH']) ? (int) $this->params['CONTENT_LENGTH'] : null;\n" " }\n" "\n" " public function withContentLength(int $contentLength): self\n" " {\n" " $this->params['CONTENT_LENGTH'] = (string) $contentLength;\n" " return $this;\n" " }\n" "\n" " public function withoutContentLength(): void\n" " {\n" " unset($this->params['CONTENT_LENGTH']);\n" " }\n" "\n" " public function getGatewayInterface(): ?string\n" " {\n" " return $this->params['GATEWAY_INTERFACE'] ?? null;\n" " }\n" "\n" " public function withGatewayInterface(string $gatewayInterface): self\n" " {\n" " $this->params['GATEWAY_INTERFACE'] = $gatewayInterface;\n" " return $this;\n" " }\n" "\n" " public function withoutGatewayInterface(): void\n" " {\n" " unset($this->params['GATEWAY_INTERFACE']);\n" " }\n" "\n" " public function getServerProtocol(): ?string\n" " {\n" " return $this->params['SERVER_PROTOCOL'] ?? null;\n" " }\n" "\n" " public function withServerProtocol(string $serverProtocol): self\n" " {\n" " $this->params['SERVER_PROTOCOL'] = $serverProtocol;\n" " return $this;\n" " }\n" "\n" " public function withoutServerProtocol(): void\n" " {\n" " unset($this->params['SERVER_PROTOCOL']);\n" " }\n" "\n" " public function withProtocolVersion(string $protocolVersion): self\n" " {\n" " if (!is_numeric($protocolVersion)) {\n" " throw new \\InvalidArgumentException('Protocol version must be numeric');\n" " }\n" " $this->params['SERVER_PROTOCOL'] = \"HTTP/{$protocolVersion}\";\n" " return $this;\n" " }\n" "\n" " public function getServerSoftware(): ?string\n" " {\n" " return $this->params['SERVER_SOFTWARE'] ?? null;\n" " }\n" "\n" " public function withServerSoftware(string $serverSoftware): self\n" " {\n" " $this->params['SERVER_SOFTWARE'] = $serverSoftware;\n" " return $this;\n" " }\n" "\n" " public function withoutServerSoftware(): void\n" " {\n" " unset($this->params['SERVER_SOFTWARE']);\n" " }\n" "\n" " public function getRemoteAddr(): ?string\n" " {\n" " return $this->params['REMOTE_ADDR'] ?? null;\n" " }\n" "\n" " public function withRemoteAddr(string $remoteAddr): self\n" " {\n" " $this->params['REMOTE_ADDR'] = $remoteAddr;\n" " return $this;\n" " }\n" "\n" " public function withoutRemoteAddr(): void\n" " {\n" " unset($this->params['REMOTE_ADDR']);\n" " }\n" "\n" " public function getRemotePort(): ?int\n" " {\n" " return isset($this->params['REMOTE_PORT']) ? (int) $this->params['REMOTE_PORT'] : null;\n" " }\n" "\n" " public function withRemotePort(int $remotePort): self\n" " {\n" " $this->params['REMOTE_PORT'] = (string) $remotePort;\n" " return $this;\n" " }\n" "\n" " public function withoutRemotePort(): void\n" " {\n" " unset($this->params['REMOTE_PORT']);\n" " }\n" "\n" " public function getServerAddr(): ?string\n" " {\n" " return $this->params['SERVER_ADDR'] ?? null;\n" " }\n" "\n" " public function withServerAddr(string $serverAddr): self\n" " {\n" " $this->params['SERVER_ADDR'] = $serverAddr;\n" " return $this;\n" " }\n" "\n" " public function withoutServerAddr(): void\n" " {\n" " unset($this->params['SERVER_ADDR']);\n" " }\n" "\n" " public function getServerPort(): ?int\n" " {\n" " return isset($this->params['SERVER_PORT']) ? (int) $this->params['SERVER_PORT'] : null;\n" " }\n" "\n" " public function withServerPort(int $serverPort): self\n" " {\n" " $this->params['SERVER_PORT'] = (string) $serverPort;\n" " return $this;\n" " }\n" "\n" " public function withoutServerPort(): void\n" " {\n" " unset($this->params['SERVER_PORT']);\n" " }\n" "\n" " public function getServerName(): ?string\n" " {\n" " return $this->params['SERVER_NAME'] ?? null;\n" " }\n" "\n" " public function withServerName(string $serverName): self\n" " {\n" " $this->params['SERVER_NAME'] = $serverName;\n" " return $this;\n" " }\n" "\n" " public function withoutServerName(): void\n" " {\n" " unset($this->params['SERVER_NAME']);\n" " }\n" "\n" " public function getRedirectStatus(): ?string\n" " {\n" " return $this->params['REDIRECT_STATUS'] ?? null;\n" " }\n" "\n" " public function withRedirectStatus(string $redirectStatus): self\n" " {\n" " $this->params['REDIRECT_STATUS'] = $redirectStatus;\n" " return $this;\n" " }\n" "\n" " public function withoutRedirectStatus(): void\n" " {\n" " unset($this->params['REDIRECT_STATUS']);\n" " }\n" "\n" " public function getHeader(string $name): ?string\n" " {\n" " return $this->params[static::convertHeaderNameToParamName($name)] ?? null;\n" " }\n" "\n" " public function withHeader(string $name, string $value): self\n" " {\n" " $this->params[static::convertHeaderNameToParamName($name)] = $value;\n" " return $this;\n" " }\n" "\n" " public function withoutHeader(string $name): void\n" " {\n" " unset($this->params[static::convertHeaderNameToParamName($name)]);\n" " }\n" "\n" " public function getHeaders(): array\n" " {\n" " $headers = [];\n" " foreach ($this->params as $name => $value) {\n" " if (str_starts_with($name, 'HTTP_')) {\n" " $headers[static::convertParamNameToHeaderName($name)] = $value;\n" " }\n" " }\n" " return $headers;\n" " }\n" "\n" " public function withHeaders(array $headers): self\n" " {\n" " foreach ($headers as $name => $value) {\n" " $this->withHeader($name, $value);\n" " }\n" " return $this;\n" " }\n" "\n" " public function withBody(array|string|\\Stringable $body): self\n" " {\n" " if (is_array($body)) {\n" " $body = http_build_query($body);\n" " $this->withContentType('application/x-www-form-urlencoded');\n" " }\n" " parent::withBody($body);\n" "\n" " return $this->withContentLength(strlen($body));\n" " }\n" "\n" " protected static function convertHeaderNameToParamName(string $name)\n" " {\n" " return 'HTTP_' . str_replace('-', '_', strtoupper($name));\n" " }\n" "\n" " protected static function convertParamNameToHeaderName(string $name)\n" " {\n" " return ucwords(str_replace('_', '-', substr($name, strlen('HTTP_'))), '-');\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_http_response = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" "use Swoole\\FastCGI\\Record\\EndRequest;\n" "use Swoole\\FastCGI\\Record\\Stderr;\n" "use Swoole\\FastCGI\\Record\\Stdout;\n" "use Swoole\\Http\\Status;\n" "\n" "class HttpResponse extends Response\n" "{\n" " /** @var int */\n" " protected $statusCode;\n" "\n" " /** @var string */\n" " protected $reasonPhrase;\n" "\n" " /**\n" " * @var array\n" " */\n" " protected array $headers = [];\n" "\n" " /**\n" " * @var array\n" " */\n" " protected array $headersMap = [];\n" "\n" " /**\n" " * @var array\n" " */\n" " protected array $setCookieHeaderLines = [];\n" "\n" " /**\n" " * @param array $records\n" " */\n" " public function __construct(array $records = [])\n" " {\n" " parent::__construct($records);\n" " $body = $this->getBody();\n" " if (strlen($body) === 0) {\n" " return;\n" " }\n" " $array = explode(\"\\r\\n\\r\\n\", $body, 2); // An array that contains the HTTP headers and the body.\n" " if (count($array) != 2) {\n" " $this->withStatusCode(Status::BAD_GATEWAY)->withReasonPhrase('Invalid FastCGI Response')->withError($body);\n" " return;\n" " }\n" " $headers = explode(\"\\r\\n\", $array[0]);\n" " $body = $array[1];\n" " foreach ($headers as $header) {\n" " $array = explode(':', $header, 2); // An array that contains the name and the value of an HTTP header.\n" " if (count($array) != 2) {\n" " continue; // Invalid HTTP header? Ignore it!\n" " }\n" " $name = trim($array[0]);\n" " $value = trim($array[1]);\n" " if (strcasecmp($name, 'Status') === 0) {\n" " $array = explode(' ', $value, 2); // An array that contains the status code (and the reason phrase).\n" " $statusCode = $array[0];\n" " $reasonPhrase = $array[1] ?? null;\n" " } elseif (strcasecmp($name, 'Set-Cookie') === 0) {\n" " $this->withSetCookieHeaderLine($value);\n" " } else {\n" " $this->withHeader($name, $value);\n" " }\n" " }\n" " $statusCode = (int) ($statusCode ?? Status::OK);\n" " $reasonPhrase = $reasonPhrase ?? Status::getReasonPhrase($statusCode);\n" " $this->withStatusCode($statusCode)->withReasonPhrase($reasonPhrase);\n" " $this->withBody($body);\n" " }\n" "\n" " public function getStatusCode(): int\n" " {\n" " return $this->statusCode;\n" " }\n" "\n" " public function withStatusCode(int $statusCode): self\n" " {\n" " $this->statusCode = $statusCode;\n" " return $this;\n" " }\n" "\n" " public function getReasonPhrase(): string\n" " {\n" " return $this->reasonPhrase;\n" " }\n" "\n" " public function withReasonPhrase(string $reasonPhrase): self\n" " {\n" " $this->reasonPhrase = $reasonPhrase;\n" " return $this;\n" " }\n" "\n" " public function getHeader(string $name): ?string\n" " {\n" " $name = $this->headersMap[strtolower($name)] ?? null;\n" " return $name ? $this->headers[$name] : null;\n" " }\n" "\n" " /**\n" " * @return array\n" " */\n" " public function getHeaders(): array\n" " {\n" " return $this->headers;\n" " }\n" "\n" " public function withHeader(string $name, string $value): self\n" " {\n" " $this->headers[$name] = $value;\n" " $this->headersMap[strtolower($name)] = $name;\n" " return $this;\n" " }\n" "\n" " /**\n" " * @param array $headers\n" " */\n" " public function withHeaders(array $headers): self\n" " {\n" " foreach ($headers as $name => $value) {\n" " $this->withHeader($name, $value);\n" " }\n" " return $this;\n" " }\n" "\n" " /**\n" " * @return array\n" " */\n" " public function getSetCookieHeaderLines(): array\n" " {\n" " return $this->setCookieHeaderLines;\n" " }\n" "\n" " public function withSetCookieHeaderLine(string $value): self\n" " {\n" " $this->setCookieHeaderLines[] = $value;\n" " return $this;\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_fast_cgi_client = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\FastCGI;\n" "\n" "use Swoole\\Constant;\n" "use Swoole\\Coroutine\\FastCGI\\Client\\Exception;\n" "use Swoole\\Coroutine\\Socket;\n" "use Swoole\\FastCGI\\FrameParser;\n" "use Swoole\\FastCGI\\HttpRequest;\n" "use Swoole\\FastCGI\\HttpResponse;\n" "use Swoole\\FastCGI\\Record\\EndRequest;\n" "use Swoole\\FastCGI\\Request;\n" "use Swoole\\FastCGI\\Response;\n" "\n" "class Client\n" "{\n" " protected int $af;\n" "\n" " protected string $host;\n" "\n" " protected int $port;\n" "\n" " protected bool $ssl;\n" "\n" " protected ?Socket $socket;\n" "\n" " public function __construct(string $host, int $port = 0, bool $ssl = false)\n" " {\n" " if (stripos($host, 'unix:/') === 0) {\n" " $this->af = AF_UNIX;\n" " $host = '/' . ltrim(substr($host, strlen('unix:/')), '/');\n" " $port = 0;\n" " } elseif (str_contains($host, ':')) {\n" " $this->af = AF_INET6;\n" " } else {\n" " $this->af = AF_INET;\n" " }\n" " $this->host = $host;\n" " $this->port = $port;\n" " $this->ssl = $ssl;\n" " }\n" "\n" " /**\n" " * @return ($request is HttpRequest ? HttpResponse : Response)\n" " * @throws Exception\n" " */\n" " public function execute(Request $request, float $timeout = -1): Response\n" " {\n" " if (!isset($this->socket)) {\n" " $this->socket = $socket = new Socket($this->af, SOCK_STREAM, IPPROTO_IP);\n" " $socket->setProtocol([\n" " Constant::OPTION_OPEN_SSL => $this->ssl,\n" " Constant::OPTION_OPEN_FASTCGI_PROTOCOL => true,\n" " ]);\n" " if (!$socket->connect($this->host, $this->port, $timeout)) {\n" " $this->ioException();\n" " }\n" " } else {\n" " $socket = $this->socket;\n" " }\n" " $sendData = (string) $request;\n" " if ($socket->sendAll($sendData) !== strlen($sendData)) {\n" " $this->ioException();\n" " }\n" " $records = [];\n" " while (true) {\n" " $recvData = $socket->recvPacket($timeout);\n" " if (!$recvData) {\n" " if ($recvData === '') {\n" " $this->ioException(SOCKET_ECONNRESET);\n" " }\n" " $this->ioException();\n" " }\n" " if (!FrameParser::hasFrame($recvData)) {\n" " $this->ioException(SOCKET_EPROTO);\n" " }\n" "\n" " do {\n" " $records[] = $record = FrameParser::parseFrame($recvData);\n" " } while (strlen($recvData) !== 0);\n" " if ($record instanceof EndRequest) {\n" " if (!$request->getKeepConn()) {\n" " $this->socket->close();\n" " $this->socket = null;\n" " }\n" " // @phpstan-ignore argument.type,argument.type\n" " return ($request instanceof HttpRequest) ? new HttpResponse($records) : new Response($records);\n" " }\n" " }\n" "\n" " // Code execution should never reach here. However, we still put an exit() statement here for safe purpose.\n" " exit(1); // @phpstan-ignore deadCode.unreachable\n" " }\n" "\n" " public static function parseUrl(string $url): array\n" " {\n" " $url = parse_url($url);\n" " $host = $url['host'] ?? '';\n" " $port = $url['port'] ?? 0;\n" " if (empty($host)) {\n" " $host = $url['path'] ?? '';\n" " if (empty($host)) {\n" " throw new \\InvalidArgumentException('Invalid url');\n" " }\n" " $host = \"unix:/{$host}\";\n" " }\n" " return [$host, $port];\n" " }\n" "\n" " public static function call(string $url, string $path, $data = '', float $timeout = -1): string\n" " {\n" " $client = new Client(...static::parseUrl($url));\n" " $pathInfo = parse_url($path);\n" " $path = $pathInfo['path'] ?? '';\n" " $root = dirname($path);\n" " $scriptName = '/' . basename($path);\n" " $documentUri = $scriptName;\n" " $query = $pathInfo['query'] ?? '';\n" " $requestUri = $query ? \"{$documentUri}?{$query}\" : $documentUri;\n" " $request = new HttpRequest();\n" " $request->withDocumentRoot($root)\n" " ->withScriptFilename($path)\n" " ->withScriptName($documentUri)\n" " ->withDocumentUri($documentUri)\n" " ->withRequestUri($requestUri)\n" " ->withQueryString($query)\n" " ->withBody($data)\n" " ->withMethod($request->getContentLength() === 0 ? 'GET' : 'POST')\n" " ;\n" " $response = $client->execute($request, $timeout);\n" " return $response->getBody();\n" " }\n" "\n" " protected function ioException(?int $errno = null): void\n" " {\n" " $socket = $this->socket;\n" " if ($errno !== null) {\n" " $socket->errCode = $errno;\n" " $socket->errMsg = swoole_strerror($errno);\n" " }\n" " $socket->close();\n" " $this->socket = null;\n" " throw new Exception($socket->errMsg, $socket->errCode);\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_fast_cgi_client_exception = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\FastCGI\\Client;\n" "\n" "class Exception extends \\Swoole\\Exception\n" "{\n" "}\n"; static const char* swoole_library_source_core_coroutine_fast_cgi_proxy = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\FastCGI;\n" "\n" "use Swoole\\FastCGI\\HttpRequest;\n" "use Swoole\\FastCGI\\HttpResponse;\n" "use Swoole\\Http;\n" "use Swoole\\Http\\Request as SwooleHttpRequest;\n" "use Swoole\\Http\\Response as SwooleHttpResponse;\n" "\n" "class Proxy\n" "{\n" " /* @var string */\n" " protected $host;\n" "\n" " /* @var int */\n" " protected $port;\n" "\n" " /* @var float */\n" " protected $timeout = -1;\n" "\n" " /* @var string */\n" " protected $documentRoot;\n" "\n" " /* @var bool */\n" " protected $https = false;\n" "\n" " /* @var string */\n" " protected $index = 'index.php';\n" "\n" " /* @var array */\n" " protected $params = [];\n" "\n" " /* @var null|callable */\n" " protected $staticFileFilter;\n" "\n" " public function __construct(string $url, string $documentRoot = '/')\n" " {\n" " [$this->host, $this->port] = Client::parseUrl($url);\n" " $this->documentRoot = $documentRoot;\n" " $this->staticFileFilter = [$this, 'staticFileFiltrate'];\n" " }\n" "\n" " public function withTimeout(float $timeout): self\n" " {\n" " $this->timeout = $timeout;\n" " return $this;\n" " }\n" "\n" " public function withHttps(bool $https): self\n" " {\n" " $this->https = $https;\n" " return $this;\n" " }\n" "\n" " public function withIndex(string $index): self\n" " {\n" " $this->index = $index;\n" " return $this;\n" " }\n" "\n" " public function getParam(string $name): ?string\n" " {\n" " return $this->params[$name] ?? null;\n" " }\n" "\n" " public function withParam(string $name, string $value): self\n" " {\n" " $this->params[$name] = $value;\n" " return $this;\n" " }\n" "\n" " public function withoutParam(string $name): self\n" " {\n" " unset($this->params[$name]);\n" " return $this;\n" " }\n" "\n" " public function getParams(): array\n" " {\n" " return $this->params;\n" " }\n" "\n" " public function withParams(array $params): self\n" " {\n" " $this->params = $params;\n" " return $this;\n" " }\n" "\n" " public function withAddedParams(array $params): self\n" " {\n" " $this->params = $params + $this->params;\n" " return $this;\n" " }\n" "\n" " public function withStaticFileFilter(?callable $filter): self\n" " {\n" " $this->staticFileFilter = $filter;\n" " return $this;\n" " }\n" "\n" " public function translateRequest(SwooleHttpRequest $userRequest): HttpRequest\n" " {\n" " $server = $userRequest->server;\n" " $headers = $userRequest->header;\n" " $pathInfo = $userRequest->server['path_info'];\n" " $pathInfo = '/' . ltrim($pathInfo, '/');\n" " if (strlen($this->index) !== 0) {\n" " $extension = pathinfo($pathInfo, PATHINFO_EXTENSION);\n" " if (empty($extension)) {\n" " $pathInfo = rtrim($pathInfo, '/') . '/' . $this->index;\n" " }\n" " }\n" " $requestUri = $scriptName = $documentUri = $server['request_uri'];\n" " $queryString = $server['query_string'] ?? '';\n" " if (strlen($queryString) !== 0) {\n" " $requestUri .= \"?{$server['query_string']}\";\n" " }\n" " $request = (new HttpRequest())\n" " ->withDocumentRoot($this->documentRoot)\n" " ->withScriptFilename($this->documentRoot . $pathInfo)\n" " ->withScriptName($scriptName)\n" " ->withDocumentUri($documentUri)\n" " ->withServerProtocol($server['server_protocol'])\n" " ->withServerAddr('127.0.0.1')\n" " ->withServerPort($server['server_port'])\n" " ->withRemoteAddr($server['remote_addr'])\n" " ->withRemotePort($server['remote_port'])\n" " ->withMethod($server['request_method'])\n" " ->withRequestUri($requestUri)\n" " ->withQueryString($queryString)\n" " ->withContentType($headers['content-type'] ?? '')\n" " ->withContentLength((int) ($headers['content-length'] ?? 0))\n" " ->withHeaders($headers)\n" " ->withBody($userRequest->rawContent())\n" " ->withAddedParams($this->params)\n" " ;\n" " if ($this->https) {\n" " $request->withParam('HTTPS', '1');\n" " }\n" "\n" " return $request;\n" " }\n" "\n" " public function translateResponse(HttpResponse $response, SwooleHttpResponse $userResponse): void\n" " {\n" " $userResponse->status($response->getStatusCode(), $response->getReasonPhrase());\n" " $userResponse->header = $response->getHeaders();\n" " $userResponse->cookie = $response->getSetCookieHeaderLines();\n" " $userResponse->end($response->getBody());\n" " }\n" "\n" " public function pass(SwooleHttpRequest|HttpRequest $userRequest, SwooleHttpResponse $userResponse): void\n" " {\n" " if (!$userRequest instanceof HttpRequest) {\n" " $request = $this->translateRequest($userRequest);\n" " } else {\n" " $request = $userRequest;\n" " }\n" " unset($userRequest);\n" " if ($this->staticFileFilter) {\n" " $filter = $this->staticFileFilter;\n" " if ($filter($request, $userResponse)) {\n" " return;\n" " }\n" " }\n" " $response = (new Client($this->host, $this->port))->execute($request, $this->timeout);\n" " $this->translateResponse($response, $userResponse);\n" " }\n" "\n" " /**\n" " * Send content of a static file to the client, if the file is accessible and is not a PHP file.\n" " *\n" " * @return bool True if the file doesn't have an extension of 'php', false otherwise. Note that the file may not be\n" " * accessible even the return value is true.\n" " */\n" " public function staticFileFiltrate(HttpRequest $request, SwooleHttpResponse $userResponse): bool\n" " {\n" " $extension = pathinfo($request->getScriptFilename(), PATHINFO_EXTENSION);\n" " if ($extension !== 'php') {\n" " $realPath = realpath($request->getScriptFilename());\n" " if (!$realPath || !str_starts_with($realPath, $this->documentRoot) || !is_file($realPath)) {\n" " $userResponse->status(Http\\Status::NOT_FOUND);\n" " } else {\n" " $userResponse->sendfile($realPath);\n" " }\n" " return true;\n" " }\n" " return false;\n" " }\n" "}\n"; static const char* swoole_library_source_core_process_manager = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Process;\n" "\n" "use Swoole\\Constant;\n" "\n" "use function Swoole\\Coroutine\\run;\n" "\n" "class Manager\n" "{\n" " /**\n" " * @var Pool\n" " */\n" " protected $pool;\n" "\n" " /**\n" " * @var int\n" " */\n" " protected $ipcType = SWOOLE_IPC_NONE;\n" "\n" " /**\n" " * @var int\n" " */\n" " protected $msgQueueKey = 0;\n" "\n" " /**\n" " * @var array\n" " */\n" " protected $startFuncMap = [];\n" "\n" " public function __construct(int $ipcType = SWOOLE_IPC_NONE, int $msgQueueKey = 0)\n" " {\n" " $this->setIPCType($ipcType)->setMsgQueueKey($msgQueueKey);\n" " }\n" "\n" " public function add(callable $func, bool $enableCoroutine = false): self\n" " {\n" " $this->addBatch(1, $func, $enableCoroutine);\n" " return $this;\n" " }\n" "\n" " public function addBatch(int $workerNum, callable $func, bool $enableCoroutine = false): self\n" " {\n" " for ($i = 0; $i < $workerNum; $i++) {\n" " $this->startFuncMap[] = [$func, $enableCoroutine];\n" " }\n" " return $this;\n" " }\n" "\n" " public function start(): void\n" " {\n" " $this->pool = new Pool(count($this->startFuncMap), $this->ipcType, $this->msgQueueKey, false);\n" "\n" " $this->pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) {\n" " [$func, $enableCoroutine] = $this->startFuncMap[$workerId];\n" " if ($enableCoroutine) {\n" " run($func, $pool, $workerId);\n" " } else {\n" " $func($pool, $workerId);\n" " }\n" " });\n" "\n" " $this->pool->start();\n" " }\n" "\n" " public function setIPCType(int $ipcType): self\n" " {\n" " $this->ipcType = $ipcType;\n" " return $this;\n" " }\n" "\n" " public function getIPCType(): int\n" " {\n" " return $this->ipcType;\n" " }\n" "\n" " public function setMsgQueueKey(int $msgQueueKey): self\n" " {\n" " $this->msgQueueKey = $msgQueueKey;\n" " return $this;\n" " }\n" "\n" " public function getMsgQueueKey(): int\n" " {\n" " return $this->msgQueueKey;\n" " }\n" "}\n"; static const char* swoole_library_source_core_remote_object = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "use Swoole\\RemoteObject\\Client;\n" "use Swoole\\RemoteObject\\Exception;\n" "\n" "class RemoteObject implements \\ArrayAccess, \\Stringable, \\Iterator, \\Countable\n" "{\n" " private int $objectId = 0;\n" "\n" " private int $coroutineId;\n" "\n" " private string $clientId;\n" "\n" " private ?Client $client = null;\n" "\n" " public function __construct($coroutineId, $clientId)\n" " {\n" " $this->coroutineId = $coroutineId;\n" " $this->clientId = $clientId;\n" " }\n" "\n" " public function __destruct()\n" " {\n" " // On the server side, this object will also be constructed,\n" " // but it is only used for data storage and serialization.\n" " // No remote calls are executed during destruction.\n" " // If the objectId is 0, it indicates that the object may have been a temporary object created by a function call\n" " // and does not need to be destructed.\n" " if ($this->client and $this->objectId > 0) {\n" " try {\n" " $this->execute('/destroy', [\n" " 'object' => $this->objectId,\n" " ]);\n" " } catch (Exception $e) {\n" " error_log($e->getMessage());\n" " debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n" " }\n" " }\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function __call(string $method, array $args)\n" " {\n" " $rs = $this->execute('/call_method', [\n" " 'object' => $this->objectId,\n" " 'method' => $method,\n" " 'args' => serialize($args),\n" " ]);\n" " return $rs['result'];\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function __get(string $property)\n" " {\n" " $rs = $this->execute('/read_property', [\n" " 'object' => $this->objectId,\n" " 'property' => $property,\n" " ]);\n" " return $rs['property'];\n" " }\n" "\n" " public function __set(string $property, mixed $value)\n" " {\n" " $this->execute('/write_property', [\n" " 'object' => $this->objectId,\n" " 'property' => $property,\n" " 'value' => serialize($value),\n" " ]);\n" " }\n" "\n" " public function __unserialize(array $data): void\n" " {\n" " $this->objectId = $data['objectId'];\n" " $this->coroutineId = $data['coroutineId'];\n" " $this->clientId = $data['clientId'];\n" " $this->client = Client::getInstance($this->clientId);\n" " }\n" "\n" " public function __serialize(): array\n" " {\n" " return [\n" " 'objectId' => $this->objectId,\n" " 'coroutineId' => $this->coroutineId,\n" " 'clientId' => $this->clientId,\n" " ];\n" " }\n" "\n" " public function __toString(): string\n" " {\n" " $rs = $this->execute('/to_string', [\n" " 'object' => $this->objectId,\n" " ]);\n" " return $rs['value'];\n" " }\n" "\n" " public function __invoke(...$args)\n" " {\n" " $rs = $this->execute('/call_method', [\n" " 'object' => $this->objectId,\n" " 'method' => '__invoke',\n" " 'args' => serialize($args),\n" " ]);\n" " return $rs['result'];\n" " }\n" "\n" " public static function call(Client $client, string $fn, array $args)\n" " {\n" " $object = new self(Coroutine::getCid(), $client->getId());\n" " $object->client = $client;\n" " $rs = $object->execute('/call_function', [\n" " 'function' => $fn,\n" " 'args' => serialize($args),\n" " ]);\n" " return $rs['result'];\n" " }\n" "\n" " public function getObjectId(): int\n" " {\n" " return $this->objectId;\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public static function create(Client $client, string $class, array $args): RemoteObject\n" " {\n" " $object = new self(Coroutine::getCid(), $client->getId());\n" " $object->client = $client;\n" " $rs = $object->execute('/new', [\n" " 'class' => $class,\n" " 'args' => serialize($args),\n" " ]);\n" " $object->objectId = intval($rs['object']);\n" " return $object;\n" " }\n" "\n" " /**\n" " * This method is only used on the server side.\n" " */\n" " public static function marshal(int $objectId, int $ownerCoroutineId, string $clientId): RemoteObject\n" " {\n" " $object = new self($ownerCoroutineId, $clientId);\n" " $object->objectId = $objectId;\n" " return $object;\n" " }\n" "\n" " public function offsetGet(mixed $offset): mixed\n" " {\n" " $rs = $this->execute('/offset_get', [\n" " 'object' => $this->objectId,\n" " 'offset' => $offset,\n" " ]);\n" " return $rs['value'];\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function offsetSet(mixed $offset, mixed $value): void\n" " {\n" " $this->execute('/offset_set', [\n" " 'object' => $this->objectId,\n" " 'offset' => $offset,\n" " 'value' => serialize($value),\n" " ]);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function offsetUnset(mixed $offset): void\n" " {\n" " $this->execute('/offset_unset', [\n" " 'object' => $this->objectId,\n" " 'offset' => $offset,\n" " ]);\n" " }\n" "\n" " public function offsetExists(mixed $offset): bool\n" " {\n" " $rs = $this->execute('/offset_exists', [\n" " 'object' => $this->objectId,\n" " 'offset' => $offset,\n" " ]);\n" " return $rs['exists'];\n" " }\n" "\n" " public function current(): mixed\n" " {\n" " return $this->__call('current', []);\n" " }\n" "\n" " public function next(): void\n" " {\n" " $this->__call('next', []);\n" " }\n" "\n" " public function key(): mixed\n" " {\n" " return $this->__call('key', []);\n" " }\n" "\n" " public function valid(): bool\n" " {\n" " return $this->__call('valid', []);\n" " }\n" "\n" " public function rewind(): void\n" " {\n" " $this->__call('rewind', []);\n" " }\n" "\n" " public function count(): int\n" " {\n" " return $this->__call('count', []);\n" " }\n" "\n" " private function execute(string $path, array $params = []): array\n" " {\n" " if (!$this->client) {\n" " throw new Exception('This remote object is not bound to a client, and cannot initiate remote calls');\n" " }\n" " return $this->client->execute($path, $params);\n" " }\n" "}\n"; static const char* swoole_library_source_core_remote_object_server = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\RemoteObject;\n" "\n" "use Swoole\\Atomic\\Long;\n" "use Swoole\\Http\\Request;\n" "use Swoole\\Http\\Response;\n" "use Swoole\\Http\\Server as HttpServer;\n" "use Swoole\\RemoteObject;\n" "\n" "class Server\n" "{\n" " public const DEFAULT_PORT = 9567;\n" "\n" " private HttpServer $server;\n" "\n" " private array $objects = [];\n" "\n" " private array $allowedClasses = [];\n" "\n" " private array $allowedFunctions = [];\n" "\n" " private Long $nextObjectId;\n" "\n" " private string $apiKey = '';\n" "\n" " public function __construct(string $host = '127.0.0.1', int $port = self::DEFAULT_PORT, array $options = [])\n" " {\n" " // By default, thread mode is used, and when viewed with ps, only one process will be displayed.\n" " $server_mode = $options['server_mode'] ?? SWOOLE_THREAD;\n" " $socket_type = $options['socket_type'] ?? SWOOLE_SOCK_TCP;\n" " $server = new HttpServer($host, $port, $server_mode, $socket_type);\n" " unset($options['server_mode'], $options['socket_type']);\n" "\n" " if (isset($options['allowed_classes'])) {\n" " if (!is_array($options['allowed_classes'])) {\n" " throw new Exception('allowed_classes must be an array');\n" " }\n" " $this->allowedClasses = array_flip($options['allowed_classes']);\n" " unset($options['allowed_classes']);\n" " }\n" "\n" " if (isset($options['allowed_functions'])) {\n" " if (!is_array($options['allowed_functions'])) {\n" " throw new Exception('allowed_functions must be an array');\n" " }\n" " $this->allowedFunctions = array_flip($options['allowed_functions']);\n" " unset($options['allowed_functions']);\n" " }\n" "\n" " if (isset($options['api_key'])) {\n" " $this->apiKey = $options['api_key'];\n" " unset($options['api_key']);\n" " }\n" "\n" " if ($options) {\n" " $server->set($options);\n" " }\n" " $server->on('request', [$this, 'onRequest']);\n" " $server->on('start', [$this, 'onStart']);\n" " $this->server = $server;\n" " $this->nextObjectId = new Long(1);\n" " }\n" "\n" " public function start(): bool\n" " {\n" " return $this->server->start();\n" " }\n" "\n" " public function onStart(): void\n" " {\n" " echo \"The remote-object server is started at http://{$this->server->host}:{$this->server->port}\\n\";\n" " }\n" "\n" " public function onRequest(Request $request, Response $response): void\n" " {\n" " $ctx = new Context($request, $response);\n" " if ($this->apiKey and $this->apiKey !== $request->header['x-api-key']) {\n" " $response->status(403);\n" " $ctx->end(['code' => -3, 'msg' => 'invalid api key']);\n" " return;\n" " }\n" " try {\n" " $method = $ctx->getHandler();\n" " if (method_exists($this, $method)) {\n" " $this->{$method}($ctx);\n" " } else {\n" " $ctx->end(['code' => -1, 'msg' => 'invalid request']);\n" " }\n" " } catch (\\Throwable $e) {\n" " $ctx->end(['code' => -2, 'exception' => [\n" " 'message' => $e->getMessage(),\n" " 'code' => $e->getCode(),\n" " 'class' => get_class($e),\n" " ]]);\n" " }\n" " }\n" "\n" " private function addObject($object): int\n" " {\n" " // The spl_object_id/spl_object_hash cannot be used,\n" " // as the IDs they generate will be reused after the objects are destroyed.\n" " $object_id = $this->nextObjectId->add();\n" " $this->objects[$object_id] = $object;\n" " return $object_id;\n" " }\n" "\n" " private function marshal(Context $ctx, mixed $data): mixed\n" " {\n" " if (is_object($data) or is_resource($data)) {\n" " $object_id = $this->addObject($data);\n" " return RemoteObject::marshal($object_id, $ctx->getCoroutineId(), $ctx->getClientId());\n" " }\n" " if (is_array($data)) {\n" " foreach ($data as $key => $value) {\n" " $data[$key] = $this->marshal($ctx, $value);\n" " }\n" " }\n" " return $data;\n" " }\n" "\n" " private function unmarshal($data): mixed\n" " {\n" " if (is_object($data) and $data instanceof RemoteObject) {\n" " return $this->objects[$data->getObjectId()];\n" " }\n" " if (is_array($data)) {\n" " foreach ($data as $key => $value) {\n" " $data[$key] = $this->unmarshal($value);\n" " }\n" " return $data;\n" " }\n" " return $data;\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function _new(Context $ctx): void\n" " {\n" " $class = trim($ctx->getParam('class'), '\\ ');\n" " if (count($this->allowedClasses) > 0 and !isset($this->allowedClasses[$class])) {\n" " throw new Exception(\"class[{$class}] not allowed\");\n" " }\n" " $class = '\\\\' . $class;\n" " $args = $ctx->getDataParam('args');\n" " foreach ($args as $key => $value) {\n" " $args[$key] = $this->unmarshal($value);\n" " }\n" " $obj = new $class(...$args);\n" " $object_id = $this->addObject($obj);\n" " $ctx->end(['code' => 0, 'object' => $object_id]);\n" " }\n" "\n" " private function _call_function(Context $ctx): void\n" " {\n" " $fn = trim($ctx->getParam('function'), '\\ ');\n" " if (count($this->allowedFunctions) > 0 and !isset($this->allowedFunctions[$fn])) {\n" " throw new Exception(\"function[{$fn}] not allowed\");\n" " }\n" " $args = $ctx->getDataParam('args');\n" " foreach ($args as $key => $value) {\n" " $args[$key] = $this->unmarshal($value);\n" " }\n" " $fn = '\\\\' . $fn;\n" " if (!function_exists($fn)) {\n" " throw new Exception(\"function[{$fn}] not found\");\n" " }\n" " $result = $fn(...$args);\n" " $ctx->end(['code' => 0, 'result' => $this->marshal($ctx, $result)]);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function _call_method(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $method = $ctx->getParam('method');\n" " $args = $ctx->getDataParam('args');\n" " foreach ($args as $key => $value) {\n" " $args[$key] = $this->unmarshal($value);\n" " }\n" " $obj = $this->objects[$object_id];\n" " if (!method_exists($obj, $method)) {\n" " $class = get_class($obj);\n" " throw new Exception(\"method[{$class}::{$method}] not found\");\n" " }\n" " $result = $obj->{$method}(...$args);\n" " $ctx->end(['code' => 0, 'result' => $this->marshal($ctx, $result)]);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function _read_property(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $property = $ctx->getParam('property');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $result = $obj->{$property};\n" " $ctx->end(['code' => 0, 'property' => $this->marshal($ctx, $result)]);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function _write_property(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $property = $ctx->getParam('property');\n" " $value = $ctx->getDataParam('value');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $obj->{$property} = $this->unmarshal($value);\n" " $ctx->end(['code' => 0]);\n" " }\n" "\n" " private function _ping(Context $ctx): void\n" " {\n" " $ctx->end(['code' => 0]);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " private function _destroy(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " unset($this->objects[$object_id]);\n" " $ctx->end(['code' => 0]);\n" " }\n" "\n" " private function _to_string(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $ctx->end(['code' => 0, 'value' => (string) $obj]);\n" " }\n" "\n" " private function _offset_get(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $offset = $ctx->getParam('offset');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $result = $obj->{$offset};\n" " $ctx->end(['code' => 0, 'value' => $this->marshal($ctx, $result)]);\n" " }\n" "\n" " private function _offset_set(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $offset = $ctx->getParam('offset');\n" " $value = $ctx->getDataParam('value');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $obj->{$offset} = $this->unmarshal($value);\n" " $ctx->end(['code' => 0]);\n" " }\n" "\n" " private function _offset_unset(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $offset = $ctx->getParam('offset');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " unset($obj->{$offset});\n" " $ctx->end(['code' => 0]);\n" " }\n" "\n" " private function _offset_exists(Context $ctx): void\n" " {\n" " $object_id = $ctx->getParam('object');\n" " $offset = $ctx->getParam('offset');\n" " if (!isset($this->objects[$object_id])) {\n" " throw new Exception(\"object[#{$object_id}] not found\");\n" " }\n" " $obj = $this->objects[$object_id];\n" " $result = isset($obj->{$offset});\n" " $ctx->end(['code' => 0, 'value' => $this->marshal($ctx, $result)]);\n" " }\n" "}\n"; static const char* swoole_library_source_core_remote_object_context = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\RemoteObject;\n" "\n" "use Swoole\\Http\\Request;\n" "use Swoole\\Http\\Response;\n" "\n" "class Context\n" "{\n" " public string $clientId;\n" "\n" " public int $coroutineId;\n" "\n" " public Request $request;\n" "\n" " public Response $response;\n" "\n" " public function __construct(Request $request, Response $response)\n" " {\n" " $this->clientId = $request->header['client-id'] ?? '';\n" " $this->coroutineId = intval($request->header['coroutine-id'] ?? 0);\n" " $this->request = $request;\n" " $this->response = $response;\n" " }\n" "\n" " public function end(array $data): void\n" " {\n" " $this->response->header('Content-Type', 'application/octet-stream');\n" " $this->response->end(serialize($data));\n" " }\n" "\n" " public function getHandler(): string\n" " {\n" " $path = $this->request->server['request_uri'];\n" " return str_replace('/', '_', $path);\n" " }\n" "\n" " public function getParam(string $name): string\n" " {\n" " if (!isset($this->request->post[$name])) {\n" " throw new Exception(\"param[{$name}] is empty\");\n" " }\n" " return $this->request->post[$name];\n" " }\n" "\n" " public function getDataParam(string $name): mixed\n" " {\n" " return unserialize($this->getParam($name));\n" " }\n" "\n" " public function getCoroutineId(): int\n" " {\n" " $coroutine_id = $this->request->header['coroutine-id'] ?? '';\n" " if (!$coroutine_id) {\n" " throw new Exception('coroutine-id is empty');\n" " }\n" " return intval($coroutine_id);\n" " }\n" "\n" " public function getClientId(): string\n" " {\n" " $client_id = $this->request->header['client-id'] ?? '';\n" " if (!$client_id) {\n" " throw new Exception('client-id is empty');\n" " }\n" " return $client_id;\n" " }\n" "}\n"; static const char* swoole_library_source_core_remote_object_client = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\RemoteObject;\n" "\n" "use Swoole\\Coroutine;\n" "use Swoole\\Coroutine\\Http\\Client as HttpClient;\n" "use Swoole\\RemoteObject;\n" "\n" "class Client\n" "{\n" " private static array $clients = [];\n" "\n" " private HttpClient $client;\n" "\n" " private string $id;\n" "\n" " private int $ownerCoroutineId;\n" "\n" " public function __construct(string $host = '127.0.0.1', int $port = Server::DEFAULT_PORT, array $options = [])\n" " {\n" " $this->id = $this->genUuid();\n" " $this->client = new HttpClient($host, $port);\n" " $this->ownerCoroutineId = Coroutine::getCid();\n" "\n" " $headers = [\n" " 'client-id' => $this->id,\n" " 'coroutine-id' => $this->ownerCoroutineId,\n" " ];\n" " if (isset($options['api_key'])) {\n" " $headers['x-api-key'] = $options['api_key'];\n" " }\n" " $this->client->setHeaders($headers);\n" " self::$clients[$this->id] = $this;\n" " }\n" "\n" " public function create(string $class, mixed ...$args): RemoteObject\n" " {\n" " return RemoteObject::create($this, $class, $args);\n" " }\n" "\n" " public function call(string $fn, mixed ...$args): mixed\n" " {\n" " return RemoteObject::call($this, $fn, $args);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public static function getInstance(string $clientId): ?static\n" " {\n" " if (empty($clientId)) {\n" " throw new Exception('RemoteObject is not bound to a client');\n" " }\n" " if (!isset(self::$clients[$clientId])) {\n" " return null;\n" " }\n" " return self::$clients[$clientId];\n" " }\n" "\n" " public function getId(): string\n" " {\n" " return $this->id;\n" " }\n" "\n" " public function execute(string $path, array $array)\n" " {\n" " $rs = $this->client->post($path, $array);\n" " if (!$rs) {\n" " throw new Exception($this->client->errMsg);\n" " }\n" " $result = unserialize($this->client->body);\n" " if ($result['code'] != 0) {\n" " $ex = $result['exception'];\n" " throw new Exception('Server Error: ' . $ex['message'], $ex['code']);\n" " }\n" " return $result;\n" " }\n" "\n" " public function ping(): bool\n" " {\n" " try {\n" " $this->execute('/ping', []);\n" " return true;\n" " } catch (\\Throwable $e) {\n" " return false;\n" " }\n" " }\n" "\n" " private function genUuid(): string\n" " {\n" " $data = random_bytes(16);\n" " $data[6] = chr(ord($data[6]) & 0x0F | 0x40);\n" " $data[8] = chr(ord($data[8]) & 0x3F | 0x80);\n" " return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));\n" " }\n" "}\n"; static const char* swoole_library_source_core_remote_object_exception = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\RemoteObject;\n" "\n" "class Exception extends \\RuntimeException\n" "{\n" "}\n"; static const char* swoole_library_source_core_remote_object_proxy_trait = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\RemoteObject;\n" "\n" "use Swoole\\RemoteObject;\n" "\n" "trait ProxyTrait\n" "{\n" " public function __call(string $method, array $args)\n" " {\n" " return $this->getObject()->{$method}(...$args);\n" " }\n" "\n" " public function __get(string $property)\n" " {\n" " return $this->getObject()->{$property};\n" " }\n" "\n" " public function __set(string $property, mixed $value)\n" " {\n" " $this->getObject()->{$property} = $value;\n" " }\n" "\n" " public function __toString(): string\n" " {\n" " return $this->getObject()->__toString();\n" " }\n" "\n" " public function __invoke(...$args)\n" " {\n" " return $this->getObject()->__invoke(...$args);\n" " }\n" "\n" " public function offsetGet(mixed $offset): mixed\n" " {\n" " return $this->getObject()->offsetGet($offset);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function offsetSet(mixed $offset, mixed $value): void\n" " {\n" " $this->getObject()->offsetSet($offset, $value);\n" " }\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function offsetUnset(mixed $offset): void\n" " {\n" " $this->getObject()->offsetUnset($offset);\n" " }\n" "\n" " public function offsetExists(mixed $offset): bool\n" " {\n" " return $this->getObject()->offsetExists($offset);\n" " }\n" "\n" " public function current(): mixed\n" " {\n" " return $this->getObject()->current();\n" " }\n" "\n" " public function next(): void\n" " {\n" " $this->getObject()->next();\n" " }\n" "\n" " public function key(): mixed\n" " {\n" " return $this->getObject()->key();\n" " }\n" "\n" " public function valid(): bool\n" " {\n" " return $this->getObject()->valid();\n" " }\n" "\n" " public function rewind(): void\n" " {\n" " $this->getObject()->rewind();\n" " }\n" "\n" " public function count(): int\n" " {\n" " return $this->getObject()->count();\n" " }\n" "\n" " abstract protected function getObject(): RemoteObject;\n" "}\n"; static const char* swoole_library_source_core_server_admin = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Server;\n" "\n" "use Swoole\\Coroutine;\n" "use Swoole\\Http\\Request;\n" "use Swoole\\Http\\Response;\n" "use Swoole\\Server;\n" "use Swoole\\StringObject;\n" "use Swoole\\Timer;\n" "\n" "class Admin\n" "{\n" " /**\n" " * gdb php\n" " * (gdb) p sizeof(zval)\n" " * $2 = 16\n" " * (gdb) p sizeof(zend_array)\n" " * $1 = 56\n" " * (gdb) p sizeof(zend_string)\n" " * $3 = 32\n" " * (gdb) p sizeof(zend_object)\n" " * $4 = 56\n" " */\n" " public const SIZE_OF_ZVAL = 16;\n" "\n" " public const SIZE_OF_ZEND_STRING = 32;\n" "\n" " public const SIZE_OF_ZEND_OBJECT = 56;\n" "\n" " public const SIZE_OF_ZEND_ARRAY = 56;\n" "\n" " private static array $map = [\n" " 'reactor' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" " 'reactor_thread' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" " 'worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" " 'event_worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" " 'task' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" " 'task_worker' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" " ];\n" "\n" " private static array $allList = [\n" " 'all',\n" " 'all_reactor',\n" " 'all_reactor_thread',\n" " 'all_worker',\n" " 'all_event_worker',\n" " 'all_task',\n" " 'all_task_worker',\n" " 'specific',\n" " ];\n" "\n" " private static array $postMethodList = [\n" " 'server_reload',\n" " 'server_shutdown',\n" " 'close_session',\n" " ];\n" "\n" " private static string $accessToken = '';\n" "\n" " public static function init(Server $server): void\n" " {\n" " $accepted_process_types = SWOOLE_SERVER_COMMAND_MASTER |\n" " SWOOLE_SERVER_COMMAND_MANAGER |\n" " SWOOLE_SERVER_COMMAND_EVENT_WORKER |\n" " SWOOLE_SERVER_COMMAND_TASK_WORKER;\n" "\n" " $server->addCommand(\n" " 'server_reload',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $server->reload();\n" " return self::json('Operation succeeded');\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'server_shutdown',\n" " $accepted_process_types,\n" " function (Server $server, string $msg): void {\n" " $server->shutdown();\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'coroutine_stats',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(Coroutine::stats())\n" " );\n" "\n" " $server->addCommand(\n" " 'coroutine_list',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(iterator_to_array(Coroutine::list()))\n" " );\n" "\n" " $server->addCommand(\n" " 'coroutine_bt',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $json = json_decode($msg, null, 512, JSON_THROW_ON_ERROR);\n" " $cid = empty($json->cid) ? 0 : intval($json->cid);\n" " $bt = Coroutine::getBackTrace($cid);\n" " if ($bt === false) {\n" " return self::json(\"Coroutine#{$cid} not exists\", 4004);\n" " }\n" " return self::json($bt);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'server_stats',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json($server->stats())\n" " );\n" "\n" " $server->addCommand(\n" " 'server_setting',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $setting = $server->setting;\n" " $setting['mode'] = $server->mode;\n" " $setting['host'] = $server->host;\n" " $setting['port'] = $server->port;\n" " $setting['master_pid'] = $server->master_pid;\n" " $setting['manager_pid'] = $server->manager_pid;\n" " return self::json($setting);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_client_info',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['session_id'])) {\n" " return self::json('require session_id', 4003);\n" " }\n" " return self::json($server->getClientInfo(intval($json['session_id'])));\n" " }\n" " );\n" "\n" " $server->addCommand('close_session', $accepted_process_types, [self::class, 'handlerCloseSession']);\n" " $server->addCommand('get_version_info', $accepted_process_types, [self::class, 'handlerGetVersionInfo']);\n" " $server->addCommand('get_worker_info', $accepted_process_types, [self::class, 'handlerGetWorkerInfo']);\n" " $server->addCommand('get_timer_list', $accepted_process_types, [self::class, 'handlerGetTimerList']);\n" " $server->addCommand('get_coroutine_list', $accepted_process_types, [self::class, 'handlerGetCoroutineList']);\n" " $server->addCommand('get_objects', $accepted_process_types, [self::class, 'handlerGetObjects']);\n" " $server->addCommand('get_class_info', $accepted_process_types, [self::class, 'handlerGetClassInfo']);\n" " $server->addCommand('get_function_info', $accepted_process_types, [self::class, 'handlerGetFunctionInfo']);\n" " $server->addCommand('get_object_by_handle', $accepted_process_types, [self::class, 'handlerGetObjectByHandle']);\n" " $server->addCommand('get_server_cpu_usage', $accepted_process_types, [self::class, 'handlerGetServerCpuUsage']);\n" " $server->addCommand(\n" " 'get_server_memory_usage',\n" " $accepted_process_types,\n" " [self::class, 'handlerGetServerMemoryUsage']\n" " );\n" " $server->addCommand(\n" " 'get_static_property_value',\n" " $accepted_process_types,\n" " [self::class, 'handlerGetStaticPropertyValue']\n" " );\n" " $server->addCommand(\n" " 'get_defined_functions',\n" " $accepted_process_types,\n" " [self::class, 'handlerGetDefinedFunctions']\n" " );\n" " $server->addCommand('get_declared_classes', $accepted_process_types, [self::class, 'handlerGetDeclaredClasses']);\n" "\n" " $server->addCommand(\n" " 'gc_status',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " return self::json(gc_status());\n" " }\n" " );\n" "\n" " if (extension_loaded('opcache')) {\n" " $server->addCommand(\n" " 'opcache_status',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(opcache_get_status(true))\n" " );\n" " }\n" "\n" " $server->addCommand(\n" " 'getpid',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(['pid' => posix_getpid()])\n" " );\n" "\n" " $server->addCommand(\n" " 'memory_usage',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json([\n" " 'usage' => memory_get_usage(),\n" " 'real_usage' => memory_get_usage(true),\n" " ])\n" " );\n" "\n" " $server->addCommand(\n" " 'get_included_files',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(['files' => get_included_files()])\n" " );\n" "\n" " $server->addCommand('get_resources', $accepted_process_types, [self::class, 'handlerGetResources']);\n" "\n" " $server->addCommand(\n" " 'get_defined_constants',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $constants = get_defined_constants();\n" " foreach ($constants as $k => $c) {\n" " if (is_resource($c)) {\n" " unset($constants[$k]);\n" " }\n" " }\n" " unset($constants['NULL'], $constants['NAN'], $constants['INF']);\n" " return self::json($constants);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_loaded_extensions',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $extensions = get_loaded_extensions();\n" " $list = [];\n" " foreach ($extensions as $key => $extension) {\n" " $ext = new \\ReflectionExtension($extension);\n" " $list[$key] = [\n" " 'id' => ++$key,\n" " 'name' => $extension,\n" " 'version' => (string) $ext->getVersion(),\n" " ];\n" " }\n" " return self::json($list);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_declared_interfaces',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(get_declared_interfaces())\n" " );\n" "\n" " $server->addCommand(\n" " 'get_declared_traits',\n" " $accepted_process_types,\n" " fn (Server $server, string $msg) => self::json(get_declared_traits())\n" " );\n" "\n" " $server->addCommand(\n" " 'get_included_file_contents',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['filename'])) {\n" " return self::json('require filename', 4003);\n" " }\n" "\n" " if (!file_exists($json['filename'])) {\n" " return self::json(\"{$json['filename']} not exist\", 4004);\n" " }\n" "\n" " if (!in_array($json['filename'], get_included_files())) {\n" " return self::json('no permission', 4003);\n" " }\n" "\n" " return self::json(file_get_contents($json['filename']));\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_globals',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $globals = [];\n" " foreach ($GLOBALS as $key => $item) {\n" " if ($key === 'GLOBALS') {\n" " continue;\n" " }\n" " $type = gettype($item);\n" " $other = [];\n" " if ($type === 'object') {\n" " $other = [\n" " 'class_name' => $item::class,\n" " 'object_id' => spl_object_id($item),\n" " 'object_hash' => spl_object_hash($item),\n" " ];\n" " }\n" " if ($type == 'resource' || $type == 'resource (closed)') {\n" " $item = '';\n" " }\n" " $globals[] = [\n" " 'key' => $key,\n" " 'value' => $item,\n" " 'type' => $type,\n" " 'other' => $other,\n" " ];\n" " }\n" " return self::json($globals);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_extension_info',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" "\n" " if (empty($json['extension_name']) || !extension_loaded($json['extension_name'])) {\n" " return self::json('require extension_name', 4004);\n" " }\n" "\n" " $ext = new \\ReflectionExtension($json['extension_name']);\n" "\n" " ob_start();\n" " $ext->info();\n" " $info = ob_get_clean();\n" "\n" " $constants = $ext->getConstants();\n" " foreach ($constants as $k => $c) {\n" " if (is_resource($c)) {\n" " unset($constants[$k]);\n" " }\n" " }\n" "\n" " unset($constants['NULL'], $constants['NAN'], $constants['INF']);\n" "\n" " return self::json([\n" " 'classes' => $ext->getClassNames(),\n" " 'version' => $ext->getVersion(),\n" " 'constants' => $constants,\n" " 'ini_entries' => $ext->getINIEntries(),\n" " 'dependencies' => $ext->getDependencies(),\n" " 'functions' => array_keys($ext->getFunctions()),\n" " 'info' => trim($info),\n" " ]);\n" " }\n" " );\n" "\n" " $server->addCommand(\n" " 'get_composer_packages',\n" " $accepted_process_types,\n" " function (Server $server, string $msg) {\n" " if (!class_exists(\\Composer\\InstalledVersions::class)) {\n" " return self::json('require composer 2.0', 4003);\n" " }\n" "\n" " $list = [];\n" " foreach (['getAllRawData', 'getRawData'] as $method) {\n" " if (!method_exists(\\Composer\\InstalledVersions::class, $method)) {\n" " continue;\n" " }\n" "\n" " $raw_data[] = \\Composer\\InstalledVersions::$method();\n" " if ($method === 'getAllRawData') {\n" " $raw_data = \\Composer\\InstalledVersions::$method();\n" " array_shift($raw_data);\n" " }\n" "\n" " foreach ($raw_data as $key => $package) {\n" " $key_name = $package['root']['name'];\n" " if ($package['root']['name'] === '__root__' && isset($list['__root__'])) {\n" " $key_name = \"__root__{$key}\";\n" " }\n" " $package['root']['install_path'] = !empty($package['root']['install_path']) ? realpath($package['root']['install_path']) : '';\n" " $list[$key_name] = $package;\n" " }\n" " break;\n" " }\n" " return self::json($list);\n" " }\n" " );\n" " }\n" "\n" " public static function getAccessToken(): string\n" " {\n" " return self::$accessToken;\n" " }\n" "\n" " public static function start(Server $server): void\n" " {\n" " $admin_server_uri = swoole_string($server->setting['admin_server']);\n" " if ($admin_server_uri->startsWith('unix:/')) {\n" " swoole_error_log(SWOOLE_LOG_ERROR, \"admin_server[{$server->setting['admin_server']}] is not supported\");\n" " return;\n" " }\n" "\n" " if ($admin_server_uri->contains('@')) {\n" " [$access_name, $access_secret] = $admin_server_uri->split('@', 2)->get(0)->split(':', 2)->toArray();\n" " self::$accessToken = sha1($access_name . $access_secret);\n" " [$host, $port] = $admin_server_uri->split('@', 2)->get(1)->split(':', 2)->toArray();\n" " } else {\n" " [$host, $port] = $admin_server_uri->split(':', 2)->toArray();\n" " }\n" "\n" " $admin_server = new Coroutine\\Http\\Server($host, intval($port));\n" "\n" " $admin_server->handle('/api', function (Request $req, Response $resp) use ($server) {\n" " $path_array = swoole_string($req->server['request_uri'])->trim('/')->split('/');\n" " if ($path_array->count() < 2 or $path_array->count() > 3) {\n" " $resp->status(403);\n" " $resp->end(self::json('Bad API path', 4003));\n" " return;\n" " }\n" "\n" " $resp->header('Server', 'swoole-admin-server');\n" " $resp->header('Access-Control-Allow-Origin', '*');\n" " $resp->header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n" " $resp->header('Access-Control-Allow-Headers', 'X-ACCESS-TOKEN, X-ADMIN-SERVER-ACCESS-TOKEN');\n" "\n" " $method = $req->getMethod();\n" "\n" " if ($method === 'OPTIONS') {\n" " $resp->end();\n" " return;\n" " }\n" "\n" " $token = self::getAccessToken();\n" " if (!empty($token)) {\n" " $token_header = $req->header['x-admin-server-access-token'] ?? '';\n" " if ($token_header !== $token) {\n" " $resp->status(403);\n" " $resp->end(self::json('Bad access token', 4003));\n" " return;\n" " }\n" " }\n" "\n" " $cmd = $path_array->get(1)->toString();\n" "\n" " if (in_array($cmd, self::$postMethodList) && $method != 'POST') {\n" " $resp->status(403);\n" " $resp->end(self::json('Bad request method', 4003));\n" " return;\n" " }\n" "\n" " if ($method == 'GET') {\n" " $data = $req->get;\n" " } else {\n" " $data = $req->post;\n" " }\n" "\n" " if ($cmd === 'multi') {\n" " $body = json_decode($req->getContent(), true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($body) || !is_array($body) || $method != 'POST') {\n" " goto _bad_process;\n" " }\n" "\n" " $result = self::handlerMulti($server, $body);\n" " $resp->end(json_encode($result, JSON_INVALID_UTF8_IGNORE));\n" " return;\n" " }\n" "\n" " if ($path_array->count() == 2) {\n" " $process = swoole_string('master');\n" " } else {\n" " $process = $path_array->get(2);\n" " }\n" "\n" " if ($process->startsWith('master')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MASTER;\n" " $process_id = 0;\n" " } elseif ($process->startsWith('manager')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MANAGER;\n" " $process_id = 0;\n" " } elseif ($process->startsWith('all') || $process->equals('specific')) {\n" " if (!in_array($process->toString(), self::$allList)) {\n" " goto _bad_process;\n" " }\n" "\n" " $result = self::handlerGetAll($server, $process, $cmd, $data);\n" "\n" " $resp->end(self::json($result));\n" " return;\n" " } else {\n" " $array = $process->split('-');\n" " if ($array->count() != 2) {\n" " _bad_process:\n" " $resp->status(403);\n" " $resp->end(self::json('Bad process', 4003));\n" " return;\n" " }\n" "\n" " if (!isset(self::$map[$array->get(0)->toString()])) {\n" " goto _bad_process;\n" " }\n" "\n" " $process_type = self::$map[$array->get(0)->toString()];\n" " $process_id = intval($array->get(1)->toString());\n" " }\n" "\n" " $result = $server->command($cmd, $process_id, intval($process_type), $data, false);\n" " if (!$result) {\n" " $resp->end(json_encode([\n" " 'code' => swoole_last_error(),\n" " 'data' => swoole_strerror(swoole_last_error()),\n" " ], JSON_THROW_ON_ERROR));\n" " } else {\n" " $resp->end($result);\n" " }\n" " });\n" " $admin_server->handle('/', function (Request $req, Response $resp): void {\n" " $resp->status(404);\n" " });\n" " $server->admin_server = $admin_server;\n" " $admin_server->start();\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public static function handlerGetResources(Server $server, string $msg)\n" " {\n" " $resources = get_resources();\n" " $list = [];\n" " foreach ($resources as $r) {\n" " $info = [\n" " 'id' => get_resource_id($r),\n" " 'type' => get_resource_type($r),\n" " ];\n" " if ($info['type'] == 'stream') {\n" " $info['info'] = stream_get_meta_data($r);\n" " }\n" " $list[] = $info;\n" " }\n" " return self::json($list);\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public static function handlerGetWorkerInfo(Server $server, string $msg)\n" " {\n" " $info = [\n" " 'id' => $server->getWorkerId(),\n" " 'pid' => $server->getWorkerPid(),\n" " 'gc_status' => gc_status(),\n" " 'memory_usage' => memory_get_usage(),\n" " 'memory_real_usage' => memory_get_usage(true),\n" " 'process_status' => self::getProcessStatus(),\n" " 'coroutine_stats' => Coroutine::stats(),\n" " 'timer_stats' => Timer::stats(),\n" " 'vm_status' => swoole_get_vm_status(),\n" " ];\n" " return self::json($info);\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public static function handlerCloseSession(Server $server, string $msg)\n" " {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['session_id'])) {\n" " return self::json('require session_id', 4003);\n" " }\n" " if ($server->close(intval($json['session_id']), !empty($json['force']))) {\n" " return self::json([]);\n" " }\n" " return self::json(['error' => swoole_last_error()], 4004);\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public static function handlerGetTimerList(Server $server, string $msg)\n" " {\n" " $list = [];\n" " foreach (Timer::list() as $timer_id) {\n" " $list[] = [\n" " 'id' => $timer_id,\n" " 'info' => Timer::info($timer_id),\n" " ];\n" " }\n" "\n" " return self::json($list);\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public static function handlerGetCoroutineList(Server $server, string $msg)\n" " {\n" " $list = [];\n" " foreach (Coroutine::list() as $cid) {\n" " $list[] = [\n" " 'id' => $cid,\n" " 'elapsed' => Coroutine::getElapsed($cid),\n" " 'stack_usage' => Coroutine::getStackUsage($cid),\n" " 'backTrace' => Coroutine::getBackTrace($cid, DEBUG_BACKTRACE_IGNORE_ARGS, 1),\n" " ];\n" " }\n" "\n" " return self::json($list);\n" " }\n" "\n" " public static function handlerGetObjects(Server $server, string $msg)\n" " {\n" " $list = [];\n" " $objects = swoole_get_objects();\n" " foreach ($objects as $o) {\n" " $class_name = $o::class;\n" " $class = new \\ReflectionClass($class_name);\n" " $filename = $class->getFileName();\n" " $line = $class->getStartLine();\n" " $list[] = [\n" " 'id' => spl_object_id($o),\n" " 'hash' => spl_object_hash($o),\n" " 'class' => $class_name,\n" " 'filename' => $filename ?: '',\n" " 'line' => $line ?: '',\n" " 'memory_size' => self::getObjectMemorySize($o),\n" " ];\n" " }\n" "\n" " return self::json($list);\n" " }\n" "\n" " public static function handlerGetClassInfo(Server $server, string $msg)\n" " {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (!empty($json['class_name'])) {\n" " if (!class_exists($json['class_name'], false) && !interface_exists($json['class_name'], false)) {\n" " return self::json(\"{$json['class_name']} not exists\", 4003);\n" " }\n" " $name = $json['class_name'];\n" " } elseif (!empty($json['interface_name'])) {\n" " if (!interface_exists($json['interface_name'], false)) {\n" " return self::json(\"{$json['interface_name']} not exists\", 4003);\n" " }\n" " $name = $json['interface_name'];\n" " } else {\n" " return self::json(['error' => 'require class_name or interface_name'], 4004);\n" " }\n" "\n" " $class = new \\ReflectionClass($name);\n" "\n" " $filename = $class->getFileName();\n" "\n" " $getTmpConstants = function ($data) {\n" " $tmp = [];\n" " foreach ($data as $k => $v) {\n" " $tmp[] = [\n" " 'name' => $k,\n" " 'value' => is_array($v) ? var_export($v, true) : $v,\n" " 'type' => is_array($v) ? 'detail' : 'default',\n" " ];\n" " }\n" " return $tmp;\n" " };\n" "\n" " $tmpConstants = $class->getConstants();\n" " $constants = $tmpConstants ? $getTmpConstants($tmpConstants) : [];\n" "\n" " $staticProperties = [];\n" " $properties = [];\n" " $tmpProperties = $class->getProperties();\n" "\n" " $getTmpProperties = function ($class, $data) {\n" " $static = [];\n" " $noStatic = [];\n" " $defaultProperties = $class->getDefaultProperties();\n" " foreach ($data as $k => $v) {\n" " $name = $v->getName();\n" " $modifiers = \\Reflection::getModifierNames($v->getModifiers());\n" " if ($v->isStatic()) {\n" " $static[] = [\n" " 'name' => $name,\n" " 'value' => $defaultProperties[$name],\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " } else {\n" " $noStatic[] = [\n" " 'name' => $name,\n" " 'value' => $defaultProperties[$name],\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " }\n" " }\n" " return ['static' => $static, 'no_static' => $noStatic];\n" " };\n" "\n" " if ($tmpProperties) {\n" " $tmpProperties = $getTmpProperties($class, $tmpProperties);\n" " $staticProperties = $tmpProperties['static'];\n" " $properties = $tmpProperties['no_static'];\n" " }\n" "\n" " $staticMethods = [];\n" " $methods = [];\n" " $tmpStaticMethods = $class->getMethods();\n" "\n" " $getTmpMethods = function ($data) {\n" " $static = [];\n" " $noStatic = [];\n" " foreach ($data as $k => $v) {\n" " $name = $v->getName();\n" " $line = $v->getStartLine();\n" " $modifiers = \\Reflection::getModifierNames($v->getModifiers());\n" " if ($v->isStatic()) {\n" " $static[] = [\n" " 'name' => $name,\n" " 'line' => $line ?: '',\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " } else {\n" " $noStatic[] = [\n" " 'name' => $name,\n" " 'line' => $line ?: '',\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " }\n" " }\n" " return ['static' => $static, 'no_static' => $noStatic];\n" " };\n" "\n" " if ($tmpStaticMethods) {\n" " $tmpStaticMethods = $getTmpMethods($tmpStaticMethods);\n" " $staticMethods = $tmpStaticMethods['static'];\n" " $methods = $tmpStaticMethods['no_static'];\n" " }\n" "\n" " $tmpParentClass = $class->getParentClass();\n" " $parentClass = $tmpParentClass ? $tmpParentClass->getName() : '';\n" "\n" " $data = [\n" " 'filename' => $filename,\n" " 'constants' => $constants,\n" " 'staticProperties' => $staticProperties,\n" " 'properties' => $properties,\n" " 'staticMethods' => $staticMethods,\n" " 'methods' => $methods,\n" " 'parentClass' => $parentClass,\n" " 'interface' => $class->getInterfaceNames(),\n" " ];\n" " return self::json($data);\n" " }\n" "\n" " public static function handlerGetFunctionInfo(Server $server, string $msg)\n" " {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" "\n" " $className = $json['class_name'] ?? '';\n" " $functionName = $json['function_name'] ?? '';\n" "\n" " if (empty($json) || empty($functionName)) {\n" " return self::json('require function_name', 4004);\n" " }\n" "\n" " $isStatic = false;\n" " if (!empty($className)) {\n" " if (!class_exists($className) && !interface_exists($className)) {\n" " return self::json(\"{$className} not exists\", 4004);\n" " }\n" " if (!method_exists($className, $functionName)) {\n" " return self::json(\"{$className}->{$functionName} not exists\", 4004);\n" " }\n" " $ref = new \\ReflectionMethod($className, $functionName);\n" " $isStatic = $ref->isStatic();\n" " } else {\n" " if (!function_exists($functionName)) {\n" " return self::json(\"{$functionName} not exists\", 4004);\n" " }\n" " $ref = new \\ReflectionFunction($functionName);\n" " }\n" "\n" " $result = [\n" " 'filename' => $ref->getFileName(),\n" " 'line' => $ref->getStartLine() ?: '',\n" " 'num' => $ref->getNumberOfParameters(),\n" " 'user_defined' => $ref->isUserDefined(),\n" " 'extension' => $ref->getExtensionName(),\n" " 'is_static' => $isStatic,\n" " ];\n" "\n" " $params = $ref->getParameters();\n" "\n" " $list = [];\n" " foreach ($params as $param) {\n" " $type = $optional = $default = '';\n" "\n" " $paramName = $param->getName();\n" "\n" " if ($param->hasType()) {\n" " /** @var \\ReflectionNamedType|\\ReflectionUnionType $reflection */\n" " $reflection = $param->getType();\n" " if ($reflection instanceof \\ReflectionUnionType) {\n" " $unionType = [];\n" " foreach ($reflection->getTypes() as $objType) {\n" " $unionType[] = $objType->getName();\n" " }\n" " $type = implode('|', $unionType);\n" " } else {\n" " $type = $reflection->getName();\n" " }\n" " }\n" "\n" " if ($param->isOptional() && !$param->isVariadic()) {\n" " $optional = '?';\n" " if ($param->isDefaultValueAvailable()) {\n" " $value = $param->getDefaultValue();\n" " if (in_array($value, [true, false, null, ''])) {\n" " if ($value === null) {\n" " $value = 'null';\n" " }\n" " if ($value === true) {\n" " $value = 'true';\n" " }\n" " if ($value === false) {\n" " $value = 'false';\n" " }\n" " if ($value === '') {\n" " $value = \"''\";\n" " }\n" " }\n" " $default = \" = {$value}\";\n" " }\n" " }\n" "\n" " $isPassedByReference = $param->isPassedByReference() ? '&' : '';\n" " $isVariadic = $param->isVariadic() ? '...' : '';\n" "\n" " $option = \"{$optional}{$type} {$isPassedByReference}{$isVariadic}\";\n" " $param = \"\\${$paramName}{$default}\";\n" "\n" " $list[] = [\n" " 'optional' => $optional,\n" " 'type' => $type,\n" " 'is_passed_by_reference' => $isPassedByReference,\n" " 'is_variadic' => $isVariadic,\n" " 'name' => $paramName,\n" " 'default' => $default,\n" " 'full' => $option !== ' ' ? \"{$option}{$param}\" : $param,\n" " ];\n" " }\n" " $result['params'] = $list;\n" "\n" " return self::json($result);\n" " }\n" "\n" " public static function handlerGetObjectByHandle(Server $server, string $msg)\n" " {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json) || empty($json['object_id']) || empty($json['object_hash'])) {\n" " return self::json(['error' => 'Params Error!'], 4004);\n" " }\n" "\n" " $object = swoole_get_object_by_handle((int) $json['object_id']);\n" " if (!$object) {\n" " return self::json(['error' => 'Object destroyed!'], 4004);\n" " }\n" "\n" " $object_hash = spl_object_hash($object); // @phpstan-ignore argument.type\n" " if ($object_hash != $json['object_hash']) {\n" " return self::json(['error' => 'Object destroyed!'], 4004);\n" " }\n" "\n" " return self::json(var_export($object, true));\n" " }\n" "\n" " public static function handlerGetVersionInfo(Server $server, string $msg)\n" " {\n" " $ip_arr = swoole_get_local_ip();\n" " $host = [];\n" " $local = [];\n" " foreach ($ip_arr as $k => $ip) {\n" " if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n" " $host[] = $ip;\n" " } else {\n" " $local[] = $ip;\n" " }\n" " }\n" " $data = [\n" " 'os' => php_uname('s') . '-' . php_uname('r'),\n" " 'swoole' => swoole_version(),\n" " 'php' => phpversion(),\n" " 'ip' => $host ? $host[0] : $local[0],\n" " ];\n" " return self::json($data);\n" " }\n" "\n" " public static function handlerGetDefinedFunctions(Server $server, string $msg)\n" " {\n" " $functions = get_defined_functions();\n" " $arr = [\n" " 'internal' => $functions['internal'],\n" " ];\n" " if (!empty($functions['user'])) {\n" " foreach ($functions['user'] as $function_name) {\n" " $function = new \\ReflectionFunction($function_name);\n" " $filename = $function->getFileName();\n" " $line = $function->getStartLine();\n" " $arr['user'][] = [\n" " 'function' => $function_name,\n" " 'filename' => $filename,\n" " 'line' => $line,\n" " ];\n" " }\n" " }\n" " return self::json($arr);\n" " }\n" "\n" " public static function handlerGetDeclaredClasses(Server $server, string $msg)\n" " {\n" " $classes = get_declared_classes();\n" " $arr = [];\n" " if ($classes) {\n" " foreach ($classes as $classes_name) {\n" " $function = new \\ReflectionClass($classes_name);\n" " $filename = $function->getFileName();\n" " $line = $function->getStartLine();\n" " $arr[] = [\n" " 'class' => $classes_name,\n" " 'filename' => $filename ?: '',\n" " 'line' => $line ?: '',\n" " ];\n" " }\n" " }\n" " return self::json($arr);\n" " }\n" "\n" " public static function handlerGetServerMemoryUsage(Server $server, string $msg)\n" " {\n" " $total = 0;\n" "\n" " $result['master'] = $result['manager'] = 0;\n" " if (self::haveMasterProcess($server)) {\n" " $result['master'] = self::getProcessMemoryRealUsage($server->master_pid);\n" " }\n" " $total += $result['master'];\n" "\n" " if (self::haveManagerProcess($server)) {\n" " $result['manager'] = self::getProcessMemoryRealUsage($server->manager_pid);\n" " }\n" " $total += $result['manager'];\n" "\n" " $n = $server->setting['worker_num'] + $server->setting['task_worker_num'];\n" " /** @var int $n */\n" " for ($i = 0; $i < $n; $i++) {\n" " $key = 'worker-' . $i;\n" " $result[$key] = self::getProcessMemoryRealUsage($server->getWorkerPid($i));\n" " $total += $result[$key];\n" " }\n" "\n" " $result['total'] = $total;\n" "\n" " $result['memory_size'] = 0;\n" " // TODO: Support other OS\n" " if (PHP_OS_FAMILY === 'Linux') {\n" " preg_match('#MemTotal:\\s+(\\d+) kB#i', file_get_contents('/proc/meminfo'), $match);\n" " $result['memory_size'] = intval($match[1]) * 1024;\n" " }\n" "\n" " return self::json($result);\n" " }\n" "\n" " public static function handlerGetServerCpuUsage(Server $server, string $msg)\n" " {\n" " $total = 0;\n" "\n" " $result['master'] = $result['manager'] = 0;\n" " if (self::haveMasterProcess($server)) {\n" " $result['master'] = self::getProcessCpuUsage($server->master_pid);\n" " }\n" "\n" " $total += $result['master'][1] ?? 0;\n" "\n" " if (self::haveManagerProcess($server)) {\n" " $result['manager'] = self::getProcessCpuUsage($server->manager_pid);\n" " }\n" "\n" " $total += $result['manager'][1] ?? 0;\n" "\n" " $n = $server->setting['worker_num'] + $server->setting['task_worker_num'];\n" " /** @var int $n */\n" " for ($i = 0; $i < $n; $i++) {\n" " $key = 'worker-' . $i;\n" " $result[$key] = self::getProcessCpuUsage($server->getWorkerPid($i))[1] ?? 0;\n" " $total += $result[$key];\n" " }\n" "\n" " $result['total'] = $total;\n" " $result['cpu_num'] = swoole_cpu_num();\n" "\n" " return self::json($result);\n" " }\n" "\n" " public static function handlerGetStaticPropertyValue(Server $server, string $msg)\n" " {\n" " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['class_name'])) {\n" " return self::json(['error' => 'require class_name!'], 4004);\n" " }\n" " if (empty($json['property_name'])) {\n" " return self::json(['error' => 'require property_name!'], 4004);\n" " }\n" "\n" " $className = $json['class_name'];\n" " $propertyName = $json['property_name'];\n" "\n" " if (!class_exists($className)) {\n" " return self::json(\"class[{$className}] not exists\", 4004);\n" " }\n" "\n" " $reflection = new \\ReflectionClass($className);\n" " $value = $reflection->getStaticPropertyValue($propertyName, []);\n" "\n" " $result = [\n" " 'value' => var_export($value, true),\n" " ];\n" " return self::json($result);\n" " }\n" "\n" " private static function handlerMulti(Server $server, array $list)\n" " {\n" " $return_list = [];\n" " foreach ($list as $key => $content) {\n" " $path_array = swoole_string($content['path'])->trim('/')->split('/');\n" " $cmd = $path_array->get(1)->toString();\n" "\n" " if ($path_array->count() == 2) {\n" " $process = swoole_string('master');\n" " } else {\n" " $process = $path_array->get(2);\n" " }\n" "\n" " $data = [];\n" " $url_query = parse_url($process->toString(), PHP_URL_QUERY) ?? [];\n" " if (!empty($url_query)) {\n" " parse_str($url_query, $data);\n" " }\n" " $data = array_merge($data, $content['post'] ?? []);\n" "\n" " if ($process->startsWith('master')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MASTER;\n" " $process_id = 0;\n" " } elseif ($process->startsWith('manager')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MANAGER;\n" " $process_id = 0;\n" " } elseif ($process->startsWith('all') || $process->startsWith('specific')) {\n" " if (!in_array($process->toString(), self::$allList) && !$process->startsWith('specific')) {\n" " $return_list[$key] = json_decode('{}');\n" " continue;\n" " }\n" "\n" " $result = self::handlerGetAll($server, $process, $cmd, $data);\n" "\n" " $return_list[$key] = ['code' => 0, 'data' => $result];\n" " continue;\n" " } else {\n" " $array = $process->split('-');\n" "\n" " if ($array->count() != 2 || !isset(self::$map[$array->get(0)->toString()])) {\n" " $return_list[$key] = json_decode('{}');\n" " continue;\n" " }\n" "\n" " $process_type = self::$map[$array->get(0)->toString()];\n" " $process_id = intval($array->get(1)->toString());\n" " }\n" "\n" " $return_list[$key] = $server->command($cmd, $process_id, intval($process_type), $data, true);\n" " }\n" "\n" " return $return_list;\n" " }\n" "\n" " private static function handlerGetAll(Server $server, StringObject $process, $cmd, $data, bool $json_decode = true)\n" " {\n" " if ($process->equals('all')) {\n" " $result = self::handlerGetMaster($cmd, $data, $server, $json_decode) +\n" " self::handlerGetManager($cmd, $data, $server, $json_decode) +\n" " self::handlerGetAllWorker($cmd, $data, $server, $json_decode) +\n" " self::handlerGetAllTaskWorker($cmd, $data, $server, $json_decode);\n" " } elseif ($process->startsWith('all_reactor')) {\n" " $result = self::handlerGetAllReactor($cmd, $data, $server, $json_decode);\n" " } elseif ($process->equals('all_worker') || $process->equals('all_event_worker')) {\n" " $result = self::handlerGetAllWorker($cmd, $data, $server, $json_decode);\n" " } elseif ($process->startsWith('all_task')) {\n" " $result = self::handlerGetAllTaskWorker($cmd, $data, $server, $json_decode);\n" " } else {\n" " // specific\n" " $result = [];\n" " if (!empty($data['workers']) && is_array($data['workers'])) {\n" " foreach ($data['workers'] as $name) {\n" " $process = swoole_string($name);\n" " if ($process->startsWith('master')) {\n" " $result += self::handlerGetMaster($cmd, $data, $server, $json_decode);\n" " } elseif ($process->startsWith('manager')) {\n" " $result += self::handlerGetManager($cmd, $data, $server, $json_decode);\n" " } else {\n" " $array = $process->split('-');\n" " if ($array->count() != 2 || !isset(self::$map[$array->get(0)->toString()])) {\n" " $result[$name] = $json_decode ? json_decode('{}') : $json_decode;\n" " } else {\n" " $process_type = self::$map[$array->get(0)->toString()];\n" " $process_id = intval($array->get(1)->toString());\n" " $result[$name] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" " }\n" " }\n" " }\n" " }\n" "\n" " return $result;\n" " }\n" "\n" " private static function handlerGetMaster($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $list['master'] = $server->command($cmd, 0, SWOOLE_SERVER_COMMAND_MASTER, $data, $json_decode);\n" " return $list;\n" " }\n" "\n" " private static function handlerGetManager($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $list['manager'] = $server->command($cmd, 0, SWOOLE_SERVER_COMMAND_MANAGER, $data, $json_decode);\n" " return $list;\n" " }\n" "\n" " private static function handlerGetAllReactor($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $list = [];\n" " if ($server->mode === SWOOLE_BASE) {\n" " return $list;\n" " }\n" " $process_type = SWOOLE_SERVER_COMMAND_REACTOR_THREAD;\n" " if (empty($server->setting['reactor_num'])) {\n" " $reactor_num = $server->setting['worker_num'];\n" " } else {\n" " $reactor_num = $server->setting['reactor_num'];\n" " }\n" " for ($process_id = 0; $process_id < $reactor_num; $process_id++) {\n" " $list[\"reactor-{$process_id}\"] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" " return $list;\n" " }\n" "\n" " private static function handlerGetAllWorker($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $process_type = SWOOLE_SERVER_COMMAND_EVENT_WORKER;\n" " $worker_num = $server->setting['worker_num'];\n" " $list = [];\n" " for ($process_id = 0; $process_id < $worker_num; $process_id++) {\n" " $list[\"worker-{$process_id}\"] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" " return $list;\n" " }\n" "\n" " private static function handlerGetAllTaskWorker($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $process_type = SWOOLE_SERVER_COMMAND_TASK_WORKER;\n" " $list = [];\n" " if (empty($server->setting['task_worker_num'])) {\n" " return $list;\n" " }\n" " $task_worker_num = $server->setting['task_worker_num'];\n" " for ($process_id = 0; $process_id < $task_worker_num; $process_id++) {\n" " $list[\"task_worker-{$process_id}\"] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" " return $list;\n" " }\n" "\n" " private static function getProcessCpuUsage($pid)\n" " {\n" " // TODO: Support other OS\n" " if (PHP_OS_FAMILY !== 'Linux' || !file_exists(\"/proc/{$pid}/stat\")) {\n" " return [0];\n" " }\n" "\n" " $statAll = file_get_contents('/proc/stat');\n" " $statProc = file_get_contents(\"/proc/{$pid}/stat\");\n" "\n" " $dataAll = preg_split(\"/[ \\t]+/\", $statAll, 6);\n" " assert($dataAll[0] === 'cpu', '/proc/stat malformed');\n" " $dataProc = preg_split(\"/[ \\t]+/\", $statProc, 15);\n" "\n" " if (isset($dataProc[13]) and isset($dataProc[14])) {\n" " return [\n" " (int) $dataAll[1] + (int) $dataAll[2] + (int) $dataAll[3] + (int) $dataAll[4],\n" " (int) $dataProc[13] + (int) $dataProc[14],\n" " ];\n" " }\n" " return [(int) $dataAll[1] + (int) $dataAll[2] + (int) $dataAll[3] + (int) $dataAll[4]];\n" " }\n" "\n" " private static function getProcessMemoryRealUsage($pid = 'self')\n" " {\n" " $status = self::getProcessStatus($pid);\n" " if (!is_array($status) || !isset($status['VmRSS'])) {\n" " return 0;\n" " }\n" " return intval($status['VmRSS']) * 1024;\n" " }\n" "\n" " private static function getProcessStatus($pid = 'self')\n" " {\n" " $array = [];\n" " // TODO: Support other OS\n" " if (PHP_OS_FAMILY !== 'Linux' || !file_exists(\"/proc/{$pid}/status\")) {\n" " return $array;\n" " }\n" " $status = swoole_string(trim(file_get_contents(\"/proc/{$pid}/status\")));\n" " $lines = $status->split(\"\\n\");\n" " foreach ($lines as $l) {\n" " if (empty($l)) {\n" " continue;\n" " }\n" " [$k, $v] = swoole_string($l)->split(':');\n" " $array[$k] = trim($v);\n" " }\n" " return $array;\n" " }\n" "\n" " private static function getArrayMemorySize(array $a): int\n" " {\n" " $size = self::SIZE_OF_ZVAL + self::SIZE_OF_ZEND_ARRAY;\n" " foreach ($a as $k => $v) {\n" " if (is_string($k)) {\n" " $size += self::getStringMemorySize($k);\n" " } else {\n" " $size += self::SIZE_OF_ZVAL;\n" " }\n" " if (is_string($v)) {\n" " $size += self::getStringMemorySize($v);\n" " } elseif (is_array($v)) {\n" " $size += self::getArrayMemorySize($v);\n" " } else {\n" " $size += self::SIZE_OF_ZVAL;\n" " }\n" " }\n" " return $size;\n" " }\n" "\n" " private static function getStringMemorySize(string $s): int\n" " {\n" " return self::SIZE_OF_ZVAL + self::SIZE_OF_ZEND_STRING + strlen($s);\n" " }\n" "\n" " private static function getObjectMemorySize(object $o): int\n" " {\n" " $vars = get_object_vars($o);\n" " $size = self::SIZE_OF_ZEND_OBJECT;\n" "\n" " foreach ($vars as $v) {\n" " if (is_array($v)) {\n" " $size += self::getArrayMemorySize($v);\n" " } elseif (is_string($v)) {\n" " $size += self::getStringMemorySize($v);\n" " } else {\n" " $size += self::SIZE_OF_ZVAL;\n" " }\n" " }\n" "\n" " return $size;\n" " }\n" "\n" " private static function haveMasterProcess(Server $server): bool\n" " {\n" " if ($server->mode === SWOOLE_BASE) {\n" " return false;\n" " }\n" "\n" " return true;\n" " }\n" "\n" " private static function haveManagerProcess(Server $server): bool\n" " {\n" " if ($server->mode === SWOOLE_BASE && $server->getManagerPid() === 0) {\n" " return false;\n" " }\n" "\n" " return true;\n" " }\n" "\n" " private static function json($data, $code = 0)\n" " {\n" " $result = json_encode(['code' => $code, 'data' => $data], JSON_INVALID_UTF8_IGNORE);\n" " if (empty($result)) {\n" " return json_encode([\n" " 'code' => 5010,\n" " 'data' => ['message' => json_last_error_msg(), 'code' => json_last_error()],\n" " ]);\n" " }\n" " return $result;\n" " }\n" "}\n"; static const char* swoole_library_source_core_server_helper = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Server;\n" "\n" "use Swoole\\Constant;\n" "use Swoole\\Coroutine;\n" "use Swoole\\Server;\n" "use Swoole\\Timer;\n" "\n" "class Helper\n" "{\n" " public const STATS_TIMER_INTERVAL_TIME = 1000;\n" "\n" " public const GLOBAL_OPTIONS = [\n" " 'debug_mode' => true,\n" " 'trace_flags' => true,\n" " 'log_file' => true,\n" " 'log_level' => true,\n" " 'log_date_format' => true,\n" " 'log_date_with_microseconds' => true,\n" " 'log_rotation' => true,\n" " 'display_errors' => true,\n" " 'dns_server' => true,\n" " 'socket_dns_timeout' => true,\n" " 'socket_connect_timeout' => true,\n" " 'socket_write_timeout' => true,\n" " 'socket_send_timeout' => true,\n" " 'socket_read_timeout' => true,\n" " 'socket_recv_timeout' => true,\n" " 'socket_buffer_size' => true,\n" " 'socket_timeout' => true,\n" " 'http2_header_table_size' => true,\n" " 'http2_enable_push' => true,\n" " 'http2_max_concurrent_streams' => true,\n" " 'http2_init_window_size' => true,\n" " 'http2_max_frame_size' => true,\n" " 'http2_max_header_list_size' => true,\n" " ];\n" "\n" " public const SERVER_OPTIONS = [\n" " 'chroot' => true,\n" " 'user' => true,\n" " 'group' => true,\n" " 'daemonize' => true,\n" " 'pid_file' => true,\n" " 'reactor_num' => true,\n" " 'single_thread' => true,\n" " 'worker_num' => true,\n" " 'max_wait_time' => true,\n" " 'max_queued_bytes' => true,\n" " 'max_concurrency' => true,\n" " 'worker_max_concurrency' => true,\n" " 'enable_coroutine' => true,\n" " 'send_timeout' => true,\n" " 'dispatch_mode' => true,\n" " 'send_yield' => true,\n" " 'dispatch_func' => true,\n" " 'discard_timeout_request' => true,\n" " 'enable_unsafe_event' => true,\n" " 'enable_delay_receive' => true,\n" " 'enable_reuse_port' => true,\n" " 'task_use_object' => true,\n" " 'task_object' => true,\n" " 'event_object' => true,\n" " 'task_enable_coroutine' => true,\n" " 'task_worker_num' => true,\n" " 'task_ipc_mode' => true,\n" " 'task_tmpdir' => true,\n" " 'task_max_request' => true,\n" " 'task_max_request_grace' => true,\n" " 'max_connection' => true,\n" " 'max_conn' => true,\n" " 'start_session_id' => true,\n" " 'heartbeat_check_interval' => true,\n" " 'heartbeat_idle_time' => true,\n" " 'max_request' => true,\n" " 'max_request_grace' => true,\n" " 'reload_async' => true,\n" " 'open_cpu_affinity' => true,\n" " 'cpu_affinity_ignore' => true,\n" " 'http_parse_cookie' => true,\n" " 'http_parse_post' => true,\n" " 'http_parse_files' => true,\n" " 'http_compression' => true,\n" " 'http_compression_level' => true,\n" " 'compression_level' => true,\n" " 'http_gzip_level' => true,\n" " 'http_compression_min_length' => true,\n" " 'compression_min_length' => true,\n" " 'websocket_compression' => true,\n" " 'upload_tmp_dir' => true,\n" " 'upload_max_filesize' => true,\n" " 'enable_static_handler' => true,\n" " 'document_root' => true,\n" " 'http_autoindex' => true,\n" " 'http_index_files' => true,\n" " 'http_compression_types' => true,\n" " 'compression_types' => true,\n" " 'static_handler_locations' => true,\n" " 'input_buffer_size' => true,\n" " 'buffer_input_size' => true,\n" " 'output_buffer_size' => true,\n" " 'buffer_output_size' => true,\n" " 'message_queue_key' => true,\n" " 'bootstrap' => true,\n" " 'init_arguments' => true,\n" " 'url_rewrite_rules' => true,\n" " ];\n" "\n" " public const PORT_OPTIONS = [\n" " 'ssl_cert_file' => true,\n" " 'ssl_key_file' => true,\n" " 'backlog' => true,\n" " 'socket_buffer_size' => true,\n" " 'kernel_socket_recv_buffer_size' => true,\n" " 'kernel_socket_send_buffer_size' => true,\n" " 'heartbeat_idle_time' => true,\n" " 'buffer_high_watermark' => true,\n" " 'buffer_low_watermark' => true,\n" " 'open_tcp_nodelay' => true,\n" " 'tcp_defer_accept' => true,\n" " 'open_tcp_keepalive' => true,\n" " 'open_eof_check' => true,\n" " 'open_eof_split' => true,\n" " 'package_eof' => true,\n" " 'open_http_protocol' => true,\n" " 'open_websocket_protocol' => true,\n" " 'websocket_subprotocol' => true,\n" " 'open_websocket_close_frame' => true,\n" " 'open_websocket_ping_frame' => true,\n" " 'open_websocket_pong_frame' => true,\n" " 'open_http2_protocol' => true,\n" " 'open_mqtt_protocol' => true,\n" " 'open_redis_protocol' => true,\n" " 'max_idle_time' => true,\n" " 'tcp_keepidle' => true,\n" " 'tcp_keepinterval' => true,\n" " 'tcp_keepcount' => true,\n" " 'tcp_user_timeout' => true,\n" " 'tcp_fastopen' => true,\n" " 'open_length_check' => true,\n" " 'package_length_type' => true,\n" " 'package_length_offset' => true,\n" " 'package_body_offset' => true,\n" " 'package_body_start' => true,\n" " 'package_length_func' => true,\n" " 'package_max_length' => true,\n" " 'ssl_compress' => true,\n" " 'ssl_protocols' => true,\n" " 'ssl_verify_peer' => true,\n" " 'ssl_allow_self_signed' => true,\n" " 'ssl_client_cert_file' => true,\n" " 'ssl_cafile' => true,\n" " 'ssl_capath' => true,\n" " 'ssl_verify_depth' => true,\n" " 'ssl_prefer_server_ciphers' => true,\n" " 'ssl_ciphers' => true,\n" " 'ssl_ecdh_curve' => true,\n" " 'ssl_dhparam' => true,\n" " 'ssl_sni_certs' => true,\n" " ];\n" "\n" " public const AIO_OPTIONS = [\n" " 'aio_core_worker_num' => true,\n" " 'aio_worker_num' => true,\n" " 'aio_max_wait_time' => true,\n" " 'aio_max_idle_time' => true,\n" " 'iouring_entries' => true,\n" " 'iouring_workers' => true,\n" " 'iouring_flag' => true,\n" " 'enable_signalfd' => true,\n" " 'wait_signal' => true,\n" " 'dns_cache_refresh_time' => true,\n" " 'thread_num' => true,\n" " 'min_thread_num' => true,\n" " 'max_thread_num' => true,\n" " 'socket_dontwait' => true,\n" " 'dns_lookup_random' => true,\n" " 'use_async_resolver' => true,\n" " 'enable_coroutine' => true,\n" " ];\n" "\n" " public const COROUTINE_OPTIONS = [\n" " 'max_coro_num' => true,\n" " 'max_coroutine' => true,\n" " 'enable_deadlock_check' => true,\n" " 'hook_flags' => true,\n" " 'enable_preemptive_scheduler' => true,\n" " 'c_stack_size' => true,\n" " 'stack_size' => true,\n" " 'name_resolver' => true,\n" " 'dns_cache_expire' => true,\n" " 'dns_cache_capacity' => true,\n" " ];\n" "\n" " public const HELPER_OPTIONS = [\n" " 'stats_file' => true,\n" " 'stats_timer_interval' => true,\n" " 'admin_server' => true,\n" " ];\n" "\n" " public static function checkOptions(array $input_options): void\n" " {\n" " $const_options = self::GLOBAL_OPTIONS + self::SERVER_OPTIONS + self::PORT_OPTIONS\n" " + self::AIO_OPTIONS + self::COROUTINE_OPTIONS + self::HELPER_OPTIONS;\n" "\n" " foreach ($input_options as $k => $v) {\n" " if (!array_key_exists(strtolower($k), $const_options)) {\n" " // TODO throw exception\n" " trigger_error(\"unsupported option [{$k}]\", E_USER_WARNING);\n" " debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n" " }\n" " }\n" " }\n" "\n" " public static function onBeforeStart(Server $server): void\n" " {\n" " if (!empty($server->setting['admin_server'])) {\n" " Admin::init($server);\n" " }\n" " }\n" "\n" " public static function onBeforeShutdown(Server $server): void\n" " {\n" " if (isset($server->admin_server)) { // @phpstan-ignore isset.property\n" " $server->admin_server->shutdown();\n" " $server->admin_server = null; // @phpstan-ignore assign.propertyType\n" " }\n" " }\n" "\n" " public static function onWorkerStart(Server $server, int $workerId): void\n" " {\n" " if (!empty($server->setting['stats_file']) and $workerId == 0) {\n" " $interval_ms = empty($server->setting['stats_timer_interval']) ? self::STATS_TIMER_INTERVAL_TIME : intval($server->setting['stats_timer_interval']);\n" "\n" " $server->stats_timer = Timer::tick($interval_ms, function () use ($server) {\n" " $stats = $server->stats();\n" " $stats_file = swoole_string($server->setting['stats_file']);\n" " if ($stats_file->endsWith('.json')) {\n" " $out = json_encode($stats, JSON_THROW_ON_ERROR);\n" " } elseif ($stats_file->endsWith('.php')) {\n" " $out = \" $v) {\n" " $lines[] = \"{$k}: {$v}\";\n" " }\n" " $out = implode(\"\\n\", $lines);\n" " }\n" " file_put_contents($server->setting['stats_file'], $out);\n" " });\n" " }\n" " }\n" "\n" " public static function onWorkerExit(Server $server, int $workerId): void\n" " {\n" " if ($server->stats_timer) {\n" " Timer::clear($server->stats_timer);\n" " $server->stats_timer = null;\n" " }\n" " }\n" "\n" " public static function onWorkerStop(Server $server, int $workerId)\n" " {\n" " }\n" "\n" " public static function onStart(Server $server): void\n" " {\n" " if (!empty($server->setting[Constant::OPTION_ADMIN_SERVER])) {\n" " Coroutine::create(function () use ($server): void {\n" " Admin::start($server);\n" " });\n" " }\n" " }\n" "\n" " public static function onShutdown(Server $server)\n" " {\n" " }\n" "\n" " public static function onBeforeReload(Server $server)\n" " {\n" " }\n" "\n" " public static function onAfterReload(Server $server)\n" " {\n" " }\n" "\n" " public static function onManagerStart(Server $server)\n" " {\n" " }\n" "\n" " public static function onManagerStop(Server $server)\n" " {\n" " }\n" "\n" " public static function onWorkerError(Server $server)\n" " {\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" "use Swoole\\Coroutine\\Http\\ClientProxy;\n" "use Swoole\\Http\\Status;\n" "use Swoole\\NameResolver\\Cluster;\n" "use Swoole\\NameResolver\\Exception;\n" "\n" "abstract class NameResolver\n" "{\n" " protected $baseUrl;\n" "\n" " protected $info;\n" "\n" " private $filter_fn;\n" "\n" " public function __construct($url, protected $prefix = 'swoole_service_')\n" " {\n" " $this->checkServerUrl($url);\n" " }\n" "\n" " abstract public function join(string $name, string $ip, int $port, array $options = []): bool;\n" "\n" " abstract public function leave(string $name, string $ip, int $port): bool;\n" "\n" " abstract public function getCluster(string $name): ?Cluster;\n" "\n" " public function withFilter(callable $fn): self\n" " {\n" " $this->filter_fn = $fn;\n" " return $this;\n" " }\n" "\n" " public function getFilter()\n" " {\n" " return $this->filter_fn;\n" " }\n" "\n" " public function hasFilter(): bool\n" " {\n" " return !empty($this->filter_fn);\n" " }\n" "\n" " /**\n" " * return string: final result, non-empty string must be a valid IP address,\n" " * and an empty string indicates name lookup failed, and lookup operation will not continue.\n" " * return Cluster: has multiple nodes and failover is possible\n" " * return false or null: try another name resolver\n" " * @return Cluster|false|string|null\n" " */\n" " public function lookup(string $name)\n" " {\n" " if ($this->hasFilter() and ($this->getFilter())($name) !== true) {\n" " return null;\n" " }\n" " $cluster = $this->getCluster($name);\n" " // lookup failed, terminate execution\n" " if ($cluster == null) {\n" " return '';\n" " }\n" " // only one node, cannot retry\n" " if ($cluster->count() == 1) {\n" " return $cluster->pop();\n" " }\n" " return $cluster;\n" " }\n" "\n" " /**\n" " * !!! The host MUST BE IP ADDRESS\n" " */\n" " protected function checkServerUrl(string $url)\n" " {\n" " $info = parse_url($url);\n" " if (empty($info['scheme']) or empty($info['host'])) {\n" " throw new \\RuntimeException(\"invalid url parameter '{$url}'\");\n" " }\n" " if (!filter_var($info['host'], FILTER_VALIDATE_IP)) {\n" " $info['ip'] = gethostbyname($info['host']);\n" " if (!filter_var($info['ip'], FILTER_VALIDATE_IP)) {\n" " throw new \\RuntimeException(\"Failed to resolve host '{$info['host']}'\");\n" " }\n" " } else {\n" " $info['ip'] = $info['host'];\n" " }\n" " $baseUrl = $info['scheme'] . '://' . $info['ip'];\n" " if (!empty($info['port'])) {\n" " $baseUrl .= \":{$info['port']}\";\n" " }\n" " if (!empty($info['path'])) {\n" " $baseUrl .= rtrim($info['path'], '/');\n" " }\n" " $this->baseUrl = $baseUrl;\n" " $this->info = $info;\n" " }\n" "\n" " protected function checkResponse(ClientProxy $response): bool\n" " {\n" " if ($response->getStatusCode() === Status::OK) {\n" " return true;\n" " }\n" "\n" " throw new Exception('Http Body: ' . $response->getBody(), $response->getStatusCode());\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver_exception = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @see https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "class Exception extends \\RuntimeException\n" "{\n" "}\n"; static const char* swoole_library_source_core_name_resolver_cluster = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "use Swoole\\Exception;\n" "\n" "class Cluster\n" "{\n" " private array $nodes = [];\n" "\n" " /**\n" " * @throws Exception\n" " */\n" " public function add(string $host, int $port, int $weight = 100): void\n" " {\n" " if (!filter_var($host, FILTER_VALIDATE_IP)) {\n" " throw new Exception(\"Bad IP Address [{$host}]\");\n" " }\n" " if ($port < 0 or $port > 65535) {\n" " throw new Exception(\"Bad Port [{$port}]\");\n" " }\n" " if ($weight < 0 or $weight > 100) {\n" " throw new Exception(\"Bad Weight [{$weight}]\");\n" " }\n" " $this->nodes[] = ['host' => $host, 'port' => $port, 'weight' => $weight];\n" " }\n" "\n" " /**\n" " * @return false|string\n" " */\n" " public function pop()\n" " {\n" " if (empty($this->nodes)) {\n" " return false;\n" " }\n" " $index = array_rand($this->nodes, 1);\n" " $node = $this->nodes[$index];\n" " unset($this->nodes[$index]);\n" " return $node;\n" " }\n" "\n" " public function count(): int\n" " {\n" " return count($this->nodes);\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver_redis = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @see https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "use Swoole\\NameResolver;\n" "\n" "class Redis extends NameResolver\n" "{\n" " private $serverHost;\n" "\n" " private $serverPort;\n" "\n" " public function __construct($url, $prefix = 'swoole:service:')\n" " {\n" " parent::__construct($url, $prefix);\n" " $this->serverHost = $this->info['ip'];\n" " $this->serverPort = $this->info['port'] ?? 6379;\n" " }\n" "\n" " public function join(string $name, string $ip, int $port, array $options = []): bool\n" " {\n" " if (($redis = $this->connect()) === false) {\n" " return false;\n" " }\n" " if ($redis->sAdd($this->prefix . $name, $ip . ':' . $port) === false) {\n" " return false;\n" " }\n" " return true;\n" " }\n" "\n" " public function leave(string $name, string $ip, int $port): bool\n" " {\n" " if (($redis = $this->connect()) === false) {\n" " return false;\n" " }\n" " if ($redis->sRem($this->prefix . $name, $ip . ':' . $port) === false) {\n" " return false;\n" " }\n" " return true;\n" " }\n" "\n" " public function getCluster(string $name): ?Cluster\n" " {\n" " if (($redis = $this->connect()) === false) {\n" " return null;\n" " }\n" " $members = $redis->sMembers($this->prefix . $name);\n" " if (empty($members)) {\n" " return null;\n" " }\n" " $cluster = new Cluster();\n" " foreach ($members as $m) {\n" " [$host, $port] = explode(':', $m);\n" " $cluster->add($host, intval($port));\n" " }\n" " return $cluster;\n" " }\n" "\n" " protected function connect()\n" " {\n" " $redis = new \\Redis();\n" " if ($redis->connect($this->serverHost, $this->serverPort) === false) {\n" " return false;\n" " }\n" " return $redis;\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver_nacos = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @see https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "use Swoole\\Coroutine;\n" "use Swoole\\NameResolver;\n" "\n" "class Nacos extends NameResolver\n" "{\n" " /**\n" " * @throws Coroutine\\Http\\Client\\Exception|Exception\n" " */\n" " public function join(string $name, string $ip, int $port, array $options = []): bool\n" " {\n" " $params['port'] = $port;\n" " $params['ip'] = $ip;\n" " $params['healthy'] = 'true';\n" " $params['weight'] = $options['weight'] ?? 100;\n" " $params['encoding'] = $options['encoding'] ?? 'utf-8';\n" " $params['namespaceId'] = $options['namespaceId'] ?? 'public';\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params);\n" " $r = Coroutine\\Http\\post($url, []);\n" " return $this->checkResponse($r);\n" " }\n" "\n" " /**\n" " * @throws Coroutine\\Http\\Client\\Exception|Exception\n" " */\n" " public function leave(string $name, string $ip, int $port): bool\n" " {\n" " $params['port'] = $port;\n" " $params['ip'] = $ip;\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params);\n" " $r = Coroutine\\Http\\request($this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params), 'DELETE');\n" " return $this->checkResponse($r);\n" " }\n" "\n" " /**\n" " * @throws Coroutine\\Http\\Client\\Exception|Exception|\\Swoole\\Exception\n" " */\n" " public function getCluster(string $name): ?Cluster\n" " {\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance/list?' . http_build_query($params);\n" " $r = Coroutine\\Http\\get($url);\n" " if (!$this->checkResponse($r)) {\n" " return null;\n" " }\n" " $result = json_decode($r->getBody(), null, 512, JSON_THROW_ON_ERROR);\n" " if (empty($result)) {\n" " return null;\n" " }\n" " $cluster = new Cluster();\n" " foreach ($result->hosts as $node) {\n" " $cluster->add($node->ip, $node->port, $node->weight);\n" " }\n" " return $cluster;\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver_consul = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @see https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "use Swoole\\NameResolver;\n" "\n" "use function Swoole\\Coroutine\\Http\\get;\n" "use function Swoole\\Coroutine\\Http\\request;\n" "\n" "class Consul extends NameResolver\n" "{\n" " public function join(string $name, string $ip, int $port, array $options = []): bool\n" " {\n" " $weight = $options['weight'] ?? 100;\n" " $data = [\n" " 'ID' => $this->getServiceId($name, $ip, $port),\n" " 'Name' => $this->prefix . $name,\n" " 'Address' => $ip,\n" " 'Port' => $port,\n" " 'EnableTagOverride' => false,\n" " 'Weights' => [\n" " 'Passing' => $weight,\n" " 'Warning' => 1,\n" " ],\n" " ];\n" " $url = $this->baseUrl . '/v1/agent/service/register';\n" " $r = request($url, 'PUT', json_encode($data, JSON_THROW_ON_ERROR));\n" " return $this->checkResponse($r);\n" " }\n" "\n" " public function leave(string $name, string $ip, int $port): bool\n" " {\n" " $url = $this->baseUrl . '/v1/agent/service/deregister/' . $this->getServiceId(\n" " $name,\n" " $ip,\n" " $port\n" " );\n" " $r = request($url, 'PUT');\n" " return $this->checkResponse($r);\n" " }\n" "\n" " public function enableMaintenanceMode(string $name, string $ip, int $port): bool\n" " {\n" " $url = $this->baseUrl . '/v1/agent/service/maintenance/' . $this->getServiceId(\n" " $name,\n" " $ip,\n" " $port\n" " );\n" " $r = request($url, 'PUT');\n" " return $this->checkResponse($r);\n" " }\n" "\n" " public function getCluster(string $name): ?Cluster\n" " {\n" " $url = $this->baseUrl . '/v1/catalog/service/' . $this->prefix . $name;\n" " $r = get($url);\n" " if (!$this->checkResponse($r)) {\n" " return null;\n" " }\n" " $list = json_decode($r->getBody(), null, 512, JSON_THROW_ON_ERROR);\n" " if (empty($list)) {\n" " return null;\n" " }\n" " $cluster = new Cluster();\n" " foreach ($list as $li) {\n" " $cluster->add($li->ServiceAddress, $li->ServicePort, $li->ServiceWeights->Passing);\n" " }\n" " return $cluster;\n" " }\n" "\n" " private function getServiceId(string $name, string $ip, int $port): string\n" " {\n" " return $this->prefix . $name . \"_{$ip}:{$port}\";\n" " }\n" "}\n"; static const char* swoole_library_source_core_thread_pool = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Thread;\n" "\n" "use PhpParser\\Error;\n" "use PhpParser\\ParserFactory;\n" "use Swoole\\Thread;\n" "\n" "/**\n" " * @since 6.0.0-beta\n" " */\n" "class Pool\n" "{\n" " private array $threads = [];\n" "\n" " private string $autoloader = '';\n" "\n" " private string $classDefinitionFile = '';\n" "\n" " private string $runnableClass = '';\n" "\n" " private int $threadNum = 0;\n" "\n" " private string $proxyFile;\n" "\n" " private array $arguments = [];\n" "\n" " private object $running;\n" "\n" " private object $queue;\n" "\n" " private array $indexes = [];\n" "\n" " public function __construct(string $runnableClass, int $threadNum)\n" " {\n" " if ($threadNum <= 0) {\n" " throw new \\Exception('threadNum must be greater than 0');\n" " }\n" " $this->runnableClass = $runnableClass;\n" " $this->threadNum = $threadNum;\n" " }\n" "\n" " public function withArguments(...$arguments): static\n" " {\n" " $this->arguments = $arguments;\n" " return $this;\n" " }\n" "\n" " public function withAutoloader(string $autoloader): static\n" " {\n" " $this->autoloader = $autoloader;\n" " return $this;\n" " }\n" "\n" " public function withClassDefinitionFile(string $classDefinitionFile): static\n" " {\n" " $this->classDefinitionFile = $classDefinitionFile;\n" " return $this;\n" " }\n" "\n" " /**\n" " * @throws \\ReflectionException\n" " */\n" " public function start(): void\n" " {\n" " if (empty($this->classDefinitionFile) and class_exists($this->runnableClass, false)) {\n" " $file = (new \\ReflectionClass($this->runnableClass))->getFileName();\n" " if (!$this->isValidPhpFile($file)) {\n" " throw new \\Exception('class definition file must not contain any expressions.');\n" " }\n" " $this->classDefinitionFile = $file;\n" " } elseif ($this->classDefinitionFile) {\n" " require_once $this->classDefinitionFile;\n" " }\n" "\n" " if (!class_exists($this->runnableClass)) {\n" " throw new \\Exception(\"class `{$this->runnableClass}` not found\");\n" " }\n" "\n" " if (!is_subclass_of($this->runnableClass, Runnable::class)) {\n" " throw new \\Exception(\"class `{$this->runnableClass}` must implements Thread\\\\Runnable\");\n" " }\n" "\n" " if (empty($this->autoloader)) {\n" " $include_files = get_included_files();\n" " foreach ($include_files as $file) {\n" " if (str_ends_with($file, 'vendor/autoload.php')) {\n" " $this->autoloader = $file;\n" " break;\n" " }\n" " }\n" " }\n" "\n" " if ($this->autoloader) {\n" " $this->proxyFile = dirname($this->autoloader) . '/thread_runner.php';\n" " } else {\n" " $this->proxyFile = dirname($this->classDefinitionFile) . '/thread_runner.php';\n" " }\n" "\n" " if (!is_file($this->proxyFile)) {\n" " $script = 'run($threadArguments); }' . PHP_EOL;\n" " $script .= 'finally { $queue->push($threadId, Swoole\\Thread\\Queue::NOTIFY_ONE); }' . PHP_EOL;\n" " $script .= PHP_EOL;\n" " file_put_contents($this->proxyFile, $script);\n" " }\n" "\n" " $this->queue = new Queue();\n" " $this->running = new Atomic(1);\n" "\n" " for ($index = 0; $index < $this->threadNum; $index++) {\n" " $this->createThread($index);\n" " }\n" "\n" " while ($this->running->get()) {\n" " $threadId = $this->queue->pop(-1);\n" " $thread = $this->threads[$threadId];\n" " $index = $this->indexes[$threadId];\n" " $thread->join();\n" " unset($this->threads[$threadId], $this->indexes[$threadId]);\n" "\n" " $this->createThread($index);\n" " }\n" "\n" " foreach ($this->threads as $thread) {\n" " $thread->join();\n" " }\n" " }\n" "\n" " public function shutdown(): void\n" " {\n" " $this->running->set(0);\n" " }\n" "\n" " protected function isValidPhpFile($filePath): bool\n" " {\n" " $allowedNodeTypes = [\n" " \\PhpParser\\Node\\Stmt\\Class_::class,\n" " \\PhpParser\\Node\\Stmt\\Const_::class,\n" " \\PhpParser\\Node\\Stmt\\Use_::class,\n" " \\PhpParser\\Node\\Stmt\\Namespace_::class,\n" " \\PhpParser\\Node\\Stmt\\Declare_::class,\n" " ];\n" "\n" " $parser = (new ParserFactory())->createForNewestSupportedVersion();\n" " try {\n" " $code = file_get_contents($filePath);\n" " $stmts = $parser->parse($code);\n" " $skipLine = -1;\n" " foreach ($stmts as $stmt) {\n" " $isAllowed = false;\n" " foreach ($allowedNodeTypes as $allowedNodeType) {\n" " if ($stmt instanceof $allowedNodeType) {\n" " $isAllowed = true;\n" " break;\n" " }\n" " }\n" " if (!$isAllowed) {\n" " if ($stmt->getLine() == $skipLine) {\n" " continue;\n" " }\n" " return false;\n" " }\n" " }\n" " } catch (Error $error) {\n" " return false;\n" " }\n" " return true;\n" " }\n" "\n" " protected function createThread($index): void\n" " {\n" " $thread = new Thread($this->proxyFile,\n" " $this->autoloader,\n" " $this->runnableClass,\n" " $this->queue,\n" " $this->classDefinitionFile,\n" " $this->running,\n" " $index,\n" " ...$this->arguments\n" " );\n" " $this->indexes[$thread->id] = $index;\n" " $this->threads[$thread->id] = $thread;\n" " }\n" "}\n"; static const char* swoole_library_source_core_thread_runnable = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Thread;\n" "\n" "/**\n" " * @since 6.0.0-beta\n" " */\n" "abstract class Runnable\n" "{\n" " protected Atomic $running;\n" "\n" " protected int $id;\n" "\n" " public function __construct($running, $index)\n" " {\n" " $this->running = $running;\n" " $this->id = $index;\n" " }\n" "\n" " abstract public function run(array $args): void;\n" "\n" " protected function isRunning(): bool\n" " {\n" " return $this->running->get() === 1;\n" " }\n" "\n" " protected function shutdown(): void\n" " {\n" " $this->running->set(0);\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_functions = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" "use Swoole\\Coroutine;\n" "\n" "function run(callable $fn, ...$args)\n" "{\n" " $s = new Scheduler();\n" " $options = Coroutine::getOptions();\n" " if (!isset($options['hook_flags'])) {\n" " $s->set(['hook_flags' => SWOOLE_HOOK_ALL]);\n" " }\n" " $s->add($fn, ...$args);\n" " return $s->start();\n" "}\n" "\n" "function go(callable $fn, ...$args)\n" "{\n" " return Coroutine::create($fn, ...$args);\n" "}\n" "\n" "function defer(callable $fn)\n" "{\n" " Coroutine::defer($fn);\n" "}\n" "\n" "function batch(array $tasks, float $timeout = -1): array\n" "{\n" " $wg = new WaitGroup(count($tasks));\n" " foreach ($tasks as $id => $task) {\n" " Coroutine::create(function () use ($wg, &$tasks, $id, $task) {\n" " $tasks[$id] = null;\n" " $tasks[$id] = $task();\n" " $wg->done();\n" " });\n" " }\n" " $wg->wait($timeout);\n" " return $tasks;\n" "}\n" "\n" "function parallel(int $n, callable $fn): void\n" "{\n" " $count = $n;\n" " $wg = new WaitGroup($n);\n" " while ($count--) {\n" " Coroutine::create(function () use ($fn, $wg) {\n" " $fn();\n" " $wg->done();\n" " });\n" " }\n" " $wg->wait();\n" "}\n" "\n" "/**\n" " * Applies the callback to the elements of the given list.\n" " *\n" " * The callback function takes on two parameters. The list parameter's value being the first, and the key/index second.\n" " * Each callback runs in a new coroutine, allowing the list to be processed in parallel.\n" " *\n" " * @param array $list A list of key/value paired input data.\n" " * @param callable $fn The callback function to apply to each item on the list. The callback takes on two parameters.\n" " * The list parameter's value being the first, and the key/index second.\n" " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" " * @return array Returns an array containing the results of applying the callback function to the corresponding value\n" " * and key of the list (used as arguments for the callback). The returned array will preserve the keys of\n" " * the list.\n" " */\n" "function map(array $list, callable $fn, float $timeout = -1): array\n" "{\n" " $wg = new WaitGroup(count($list));\n" " foreach ($list as $id => $elem) {\n" " Coroutine::create(function () use ($wg, &$list, $id, $elem, $fn): void {\n" " $list[$id] = null;\n" " $list[$id] = $fn($elem, $id);\n" " $wg->done();\n" " });\n" " }\n" " $wg->wait($timeout);\n" " return $list;\n" "}\n" "\n" "function deadlock_check()\n" "{\n" " $all_coroutines = Coroutine::listCoroutines();\n" " $count = Coroutine::stats()['coroutine_num'];\n" "\n" " // coroutine deadlock detected, header\n" " $hr_width = 64 + strlen(strval($count));\n" " $hr1 = str_repeat('=', $hr_width);\n" " $hr2 = str_repeat('-', $hr_width);\n" " echo '',\n" " \"\\n {$hr1}\",\n" " \"\\n [FATAL ERROR]: all coroutines (count: {$count}) are asleep - deadlock!\",\n" " \"\\n {$hr1}\",\n" " \"\\n\";\n" "\n" " // print all coroutine backtraces\n" " $options = Coroutine::getOptions();\n" " if (empty($options['deadlock_check_disable_trace'])) {\n" " $index = 0;\n" " $limit = empty($options['deadlock_check_limit']) ? 32 : intval($options['deadlock_check_limit']);\n" " $depth = empty($options['deadlock_check_depth']) ? 32 : intval($options['deadlock_check_depth']);\n" " foreach ($all_coroutines as $cid) {\n" " echo \"\\n [Coroutine-{$cid}]\";\n" " echo \"\\n {$hr2}\\n\";\n" " echo Coroutine::printBackTrace($cid, DEBUG_BACKTRACE_IGNORE_ARGS, $depth);\n" " $index++;\n" " // limit the number of maximum outputs\n" " if ($index >= $limit) {\n" " break;\n" " }\n" " }\n" " }\n" "\n" " // footer\n" " echo \"\\n {$hr1}\\n\";\n" "}\n"; static const char* swoole_library_source_ext_curl = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "/* @noinspection PhpComposerExtensionStubsInspection */\n" "\n" "declare(strict_types=1);\n" "\n" "function swoole_curl_init(string $url = ''): Swoole\\Curl\\Handler\n" "{\n" " return new Swoole\\Curl\\Handler($url);\n" "}\n" "\n" "function swoole_curl_setopt(Swoole\\Curl\\Handler $obj, int $opt, $value): bool\n" "{\n" " return $obj->setOpt($opt, $value);\n" "}\n" "\n" "function swoole_curl_setopt_array(Swoole\\Curl\\Handler $obj, $array): bool\n" "{\n" " foreach ($array as $k => $v) {\n" " if ($obj->setOpt($k, $v) !== true) {\n" " return false;\n" " }\n" " }\n" " return true;\n" "}\n" "\n" "function swoole_curl_exec(Swoole\\Curl\\Handler $obj)\n" "{\n" " return $obj->exec();\n" "}\n" "\n" "function swoole_curl_getinfo(Swoole\\Curl\\Handler $obj, int $opt = 0)\n" "{\n" " $info = $obj->getInfo();\n" " if (is_array($info) and $opt) {\n" " return match ($opt) {\n" " CURLINFO_EFFECTIVE_URL => $info['url'],\n" " CURLINFO_HTTP_CODE => $info['http_code'],\n" " CURLINFO_CONTENT_TYPE => $info['content_type'],\n" " CURLINFO_REDIRECT_COUNT => $info['redirect_count'],\n" " CURLINFO_REDIRECT_URL => $info['redirect_url'],\n" " CURLINFO_TOTAL_TIME => $info['total_time'],\n" " CURLINFO_STARTTRANSFER_TIME => $info['starttransfer_time'],\n" " CURLINFO_SIZE_DOWNLOAD => $info['size_download'],\n" " CURLINFO_SPEED_DOWNLOAD => $info['speed_download'],\n" " CURLINFO_REDIRECT_TIME => $info['redirect_time'],\n" " CURLINFO_HEADER_SIZE => $info['header_size'],\n" " CURLINFO_PRIMARY_IP => $info['primary_ip'],\n" " CURLINFO_PRIVATE => $info['private'],\n" " default => null,\n" " };\n" " }\n" " return $info;\n" "}\n" "\n" "function swoole_curl_errno(Swoole\\Curl\\Handler $obj): int\n" "{\n" " return $obj->errno();\n" "}\n" "\n" "function swoole_curl_error(Swoole\\Curl\\Handler $obj): string\n" "{\n" " return $obj->error();\n" "}\n" "\n" "function swoole_curl_reset(Swoole\\Curl\\Handler $obj)\n" "{\n" " return $obj->reset();\n" "}\n" "\n" "function swoole_curl_close(Swoole\\Curl\\Handler $obj): void\n" "{\n" " $obj->close();\n" "}\n" "\n" "function swoole_curl_multi_getcontent(Swoole\\Curl\\Handler $obj)\n" "{\n" " return $obj->getContent();\n" "}\n"; static const char* swoole_library_source_ext_sockets = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "use Swoole\\Coroutine\\Socket;\n" "\n" "function swoole_socket_create(int $domain, int $type, int $protocol)\n" "{\n" " return new Socket($domain, $type, $protocol);\n" "}\n" "\n" "function swoole_socket_connect(Socket $socket, string $address, int $port = 0)\n" "{\n" " return $socket->connect($address, $port);\n" "}\n" "\n" "function swoole_socket_read(Socket $socket, int $length, int $type = PHP_BINARY_READ)\n" "{\n" " if ($type != PHP_BINARY_READ) {\n" " return $socket->recvLine($length);\n" " }\n" " return $socket->recv($length);\n" "}\n" "\n" "function swoole_socket_write(Socket $socket, string $buffer, int $length = 0)\n" "{\n" " if ($length > 0 and $length < strlen($buffer)) {\n" " $buffer = substr($buffer, 0, $length);\n" " }\n" " return $socket->send($buffer);\n" "}\n" "\n" "function swoole_socket_send(Socket $socket, string $buffer, int $length, int $flags)\n" "{\n" " if ($flags != 0) {\n" " throw new RuntimeException(\"\\$flags[{$flags}] is not supported\");\n" " }\n" " return swoole_socket_write($socket, $buffer, $length);\n" "}\n" "\n" "function swoole_socket_recv(Socket $socket, &$buffer, int $length, int $flags)\n" "{\n" " if ($flags & MSG_OOB) {\n" " throw new RuntimeException('\\$flags[MSG_OOB] is not supported');\n" " }\n" " if ($flags & MSG_PEEK) {\n" " $buffer = $socket->peek($length);\n" " }\n" " $timeout = $flags & MSG_DONTWAIT ? 0.001 : 0;\n" " if ($flags & MSG_WAITALL) {\n" " $buffer = $socket->recvAll($length, $timeout);\n" " } else {\n" " $buffer = $socket->recv($length, $timeout);\n" " }\n" " if ($buffer === false) {\n" " return false;\n" " }\n" " return strlen($buffer);\n" "}\n" "\n" "function swoole_socket_sendto(Socket $socket, string $buffer, int $length, int $flags, string $addr, int $port = 0)\n" "{\n" " if ($flags != 0) {\n" " throw new RuntimeException(\"\\$flags[{$flags}] is not supported\");\n" " }\n" " if ($socket->type != SOCK_DGRAM) {\n" " throw new RuntimeException('only supports dgram type socket');\n" " }\n" " if ($length > 0 and $length < strlen($buffer)) {\n" " $buffer = substr($buffer, 0, $length);\n" " }\n" " return $socket->sendto($addr, $port, $buffer);\n" "}\n" "\n" "function swoole_socket_recvfrom(Socket $socket, &$buffer, int $length, int $flags, &$name, &$port = null)\n" "{\n" " if ($flags != 0) {\n" " throw new RuntimeException(\"\\$flags[{$flags}] is not supported\");\n" " }\n" " if ($length == 0) {\n" " $socket->errCode = SOCKET_EAGAIN;\n" " return false;\n" " }\n" " if ($socket->type != SOCK_DGRAM) {\n" " throw new RuntimeException('only supports dgram type socket');\n" " }\n" " $data = $socket->recvfrom($peer);\n" " if ($data === false) {\n" " return false;\n" " }\n" " $name = $peer['address'];\n" " if (func_num_args() == 6) {\n" " $port = $peer['port'];\n" " }\n" " if ($length < strlen($data)) {\n" " $buffer = substr($data, 0, $length);\n" " } else {\n" " $buffer = $data;\n" " }\n" " return strlen($buffer);\n" "}\n" "\n" "function swoole_socket_bind(Socket $socket, string $address, int $port = 0): bool\n" "{\n" " return $socket->bind($address, $port);\n" "}\n" "\n" "function swoole_socket_listen(Socket $socket, int $backlog = 0): bool\n" "{\n" " return $socket->listen($backlog);\n" "}\n" "\n" "function swoole_socket_create_listen(int $port, int $backlog = 128)\n" "{\n" " $socket = new Socket(AF_INET, SOCK_STREAM, SOL_TCP);\n" " if (!$socket->bind('0.0.0.0', $port)) {\n" " return false;\n" " }\n" " if (!$socket->listen($backlog)) {\n" " return false;\n" " }\n" " return $socket;\n" "}\n" "\n" "function swoole_socket_accept(Socket $socket)\n" "{\n" " return $socket->accept();\n" "}\n" "\n" "function swoole_socket_getpeername(Socket $socket, &$address, &$port = null)\n" "{\n" " $info = $socket->getpeername();\n" " if (!$info) {\n" " return false;\n" " }\n" " $address = $info['address'];\n" " if (func_num_args() == 3) {\n" " $port = $info['port'];\n" " }\n" " return true;\n" "}\n" "\n" "function swoole_socket_getsockname(Socket $socket, &$address, &$port = null)\n" "{\n" " $info = $socket->getsockname();\n" " if (!$info) {\n" " return false;\n" " }\n" " $address = $info['address'];\n" " if (func_num_args() == 3) {\n" " $port = $info['port'];\n" " }\n" " return true;\n" "}\n" "\n" "function swoole_socket_set_option(Socket $socket, int $level, int $optname, $optval): bool\n" "{\n" " return $socket->setOption($level, $optname, $optval);\n" "}\n" "\n" "function swoole_socket_setopt(Socket $socket, int $level, int $optname, $optval): bool\n" "{\n" " return $socket->setOption($level, $optname, $optval);\n" "}\n" "\n" "function swoole_socket_get_option(Socket $socket, int $level, int $optname)\n" "{\n" " return $socket->getOption($level, $optname);\n" "}\n" "\n" "function swoole_socket_getopt(Socket $socket, int $level, int $optname)\n" "{\n" " return $socket->getOption($level, $optname);\n" "}\n" "\n" "function swoole_socket_shutdown(Socket $socket, int $how = 2): bool\n" "{\n" " return $socket->shutdown($how);\n" "}\n" "\n" "function swoole_socket_close(Socket $socket)\n" "{\n" " $socket->close();\n" "}\n" "\n" "function swoole_socket_clear_error(?Socket $socket = null)\n" "{\n" " if ($socket) {\n" " $socket->errCode = 0;\n" " }\n" " swoole_clear_error();\n" "}\n" "\n" "function swoole_socket_last_error(?Socket $socket = null): int\n" "{\n" " if ($socket) {\n" " return $socket->errCode;\n" " }\n" " return swoole_last_error();\n" "}\n" "\n" "function swoole_socket_set_block(Socket $socket)\n" "{\n" " if ($socket->isClosed()) {\n" " return false;\n" " }\n" " if (isset($socket->__ext_sockets_nonblock) and $socket->__ext_sockets_nonblock) {\n" " $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, $socket->__ext_sockets_timeout); // @phpstan-ignore property.notFound\n" " }\n" " $socket->__ext_sockets_nonblock = false; // @phpstan-ignore property.notFound\n" " return true;\n" "}\n" "\n" "function swoole_socket_set_nonblock(Socket $socket)\n" "{\n" " if ($socket->isClosed()) {\n" " return false;\n" " }\n" " if (isset($socket->__ext_sockets_nonblock) and $socket->__ext_sockets_nonblock) {\n" " return true;\n" " }\n" " $socket->__ext_sockets_nonblock = true; // @phpstan-ignore property.notFound\n" " $socket->__ext_sockets_timeout = $socket->getOption(SOL_SOCKET, SO_RCVTIMEO); // @phpstan-ignore property.notFound\n" " $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, ['sec' => 0, 'usec' => 1000]);\n" " return true;\n" "}\n" "\n" "function swoole_socket_create_pair(\n" " int $domain,\n" " int $type,\n" " int $protocol,\n" " array &$pair,\n" ") {\n" " $_pair = swoole_coroutine_socketpair($domain, $type, $protocol);\n" " if ($_pair) {\n" " $pair = $_pair;\n" " return true;\n" " }\n" " return false;\n" "}\n" "\n" "/**\n" " * @since 5.0.0\n" " */\n" "function swoole_socket_import_stream(mixed $stream): Socket|false\n" "{\n" " return Socket::import($stream); // @phpstan-ignore staticMethod.notFound\n" "}\n"; static const char* swoole_library_source_ext_standard = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "/* @noinspection PhpComposerExtensionStubsInspection */\n" "\n" "declare(strict_types=1);\n" "\n" "use Swoole\\Coroutine\\System;\n" "\n" "function swoole_gethostbynamel(string $domain)\n" "{\n" " return System::getaddrinfo($domain);\n" "}\n" "\n" "function swoole_mail(string $to, string $subject, string $message, array $headers = []): bool\n" "{\n" " $client = swoole_get_default_remote_object_client();\n" " return $client->call('mail', $to, $subject, $message, $headers);\n" "}\n" "\n" "function swoole_checkdnsrr(string $hostname, string $type = 'MX'): bool\n" "{\n" " $client = swoole_get_default_remote_object_client();\n" " return $client->call('checkdnsrr', ...func_get_args());\n" "}\n" "\n" "function swoole_dns_check_record(string $hostname, string $type = 'MX'): bool\n" "{\n" " return swoole_checkdnsrr($hostname, $type);\n" "}\n" "\n" "function swoole_real_getmxrr(string $hostname, ?array $hosts = null, ?array $weights = null): array\n" "{\n" " if (func_num_args() === 2) {\n" " $result['result'] = getmxrr($hostname, $hosts);\n" " $result['host'] = $hosts;\n" " } else {\n" " $result['result'] = getmxrr($hostname, $hosts, $weights);\n" " $result['host'] = $hosts;\n" " $result['weight'] = $weights;\n" " }\n" " return $result;\n" "}\n" "\n" "function swoole_getmxrr(string $hostname, array &$hosts, ?array &$weights = null): bool\n" "{\n" " $client = swoole_get_default_remote_object_client();\n" " $_hosts = $hosts;\n" " $_weights = $weights === null ? null : $weights;\n" " $result = $client->call('swoole_real_getmxrr', $hostname, $_hosts, $_weights);\n" " $hosts = $result['host'];\n" " $weights = $result['weight'];\n" " return $result['result'];\n" "}\n" "\n" "function swoole_dns_get_mx(string $hostname, array &$hosts, ?array &$weights = null): bool\n" "{\n" " return swoole_getmxrr($hostname, $hosts, $weights);\n" "}\n" "\n" "function swoole_real_dns_get_record(string $hostname, int $type, ?array $authoritative_name_servers = null, ?array $additional_records = null, bool $raw = false): array\n" "{\n" " if ($authoritative_name_servers === null && $additional_records === null) {\n" " $result['result'] = dns_get_record($hostname, $type);\n" " } elseif ($additional_records === null) {\n" " $result['result'] = dns_get_record($hostname, $type, $authoritative_name_servers);\n" " } else {\n" " $result['result'] = dns_get_record($hostname, $type, $authoritative_name_servers, $additional_records);\n" " }\n" " $result['authoritative_name_servers'] = $authoritative_name_servers;\n" " $result['additional_records'] = $additional_records;\n" " return $result;\n" "}\n" "\n" "function swoole_dns_get_record(string $hostname,\n" " int $type = DNS_ANY,\n" " ?array &$authoritative_name_servers = null,\n" " ?array &$additional_records = null,\n" " bool $raw = false): array|false\n" "{\n" " $client = swoole_get_default_remote_object_client();\n" " $result = $client->call('swoole_real_dns_get_record', $hostname, $type, $authoritative_name_servers, $additional_records, $raw);\n" " $authoritative_name_servers = $result['authoritative_name_servers'];\n" " $additional_records = $result['additional_records'];\n" " return $result['result'];\n" "}\n" "\n" "function swoole_gethostbyaddr(string $ip): string\n" "{\n" " $client = swoole_get_default_remote_object_client();\n" " return $client->call('gethostbyaddr', $ip);\n" "}\n"; static const char* swoole_library_source_ext_mongodb = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\MongoDB;\n" "\n" "use Swoole\\RemoteObject;\n" "use Swoole\\RemoteObject\\ProxyTrait;\n" "\n" "class Client\n" "{\n" " use ProxyTrait;\n" "\n" " public const DEFAULT_URI = 'mongodb://127.0.0.1/';\n" "\n" " protected RemoteObject $client;\n" "\n" " public function __construct(?string $uri = self::DEFAULT_URI, array $uriOptions = [], array $driverOptions = [])\n" " {\n" " $remoteObjectClient = swoole_library_get_option('mongodb_remote_object_client');\n" " if ($remoteObjectClient === null) {\n" " $remoteObjectClient = swoole_get_default_remote_object_client();\n" " }\n" " $this->client = $remoteObjectClient->create(\\MongoDB\\Client::class, $uri, $uriOptions, $driverOptions);\n" " }\n" "\n" " protected function getObject(): RemoteObject\n" " {\n" " return $this->client;\n" " }\n" "}\n"; static const char* swoole_library_source_functions = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "if (PHP_VERSION_ID < 80100) { // @phpstan-ignore smaller.alwaysFalse\n" " throw new RuntimeException('require PHP version 8.1 or later');\n" "}\n" "\n" "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " function _string(string $string = ''): Swoole\\StringObject\n" " {\n" " return new Swoole\\StringObject($string);\n" " }\n" "\n" " function _mbstring(string $string = ''): Swoole\\MultibyteStringObject\n" " {\n" " return new Swoole\\MultibyteStringObject($string);\n" " }\n" "\n" " function _array(array $array = []): Swoole\\ArrayObject\n" " {\n" " return new Swoole\\ArrayObject($array);\n" " }\n" "}\n" "\n" "class SwooleLibrary\n" "{\n" " /**\n" " * @var array\n" " */\n" " public static array $options = [];\n" "\n" " public static bool $remote_object_server_initiated = false;\n" "\n" " public static string $remote_object_server_socket_file = '';\n" "}\n" "\n" "/**\n" " * @param array $options\n" " */\n" "function swoole_library_set_options(array $options): void\n" "{\n" " SwooleLibrary::$options = $options;\n" "}\n" "\n" "function swoole_library_get_options(): array\n" "{\n" " return SwooleLibrary::$options;\n" "}\n" "\n" "function swoole_library_set_option(string $key, mixed $value): void\n" "{\n" " SwooleLibrary::$options[$key] = $value;\n" "}\n" "\n" "function swoole_library_get_option(string $key): mixed\n" "{\n" " return SwooleLibrary::$options[$key] ?? null;\n" "}\n" "\n" "function swoole_string(string $string = ''): Swoole\\StringObject\n" "{\n" " return new Swoole\\StringObject($string);\n" "}\n" "\n" "function swoole_mbstring(string $string = ''): Swoole\\MultibyteStringObject\n" "{\n" " return new Swoole\\MultibyteStringObject($string);\n" "}\n" "\n" "function swoole_array(array $array = []): Swoole\\ArrayObject\n" "{\n" " return new Swoole\\ArrayObject($array);\n" "}\n" "\n" "function swoole_table(int $size, string $fields): Swoole\\Table\n" "{\n" " $_fields = swoole_string($fields)->trim()->split(',');\n" "\n" " $table = new Swoole\\Table($size, 0.25);\n" "\n" " foreach ($_fields as $f) {\n" " $_f = swoole_string($f)->trim()->split(':');\n" " $name = $_f->get(0)->trim()->toString();\n" " $type = $_f->get(1)->trim();\n" "\n" " switch ($type) {\n" " case 'i':\n" " case 'int':\n" " $table->column($name, Swoole\\Table::TYPE_INT);\n" " break;\n" " case 'f':\n" " case 'float':\n" " $table->column($name, Swoole\\Table::TYPE_FLOAT);\n" " break;\n" " case 's':\n" " case 'string':\n" " if ($_f->count() < 3) {\n" " throw new RuntimeException('need to give string length');\n" " }\n" " $length = (int) $_f->get(2)->trim()->toString();\n" " if ($length <= 0) {\n" " throw new RuntimeException(\"invalid string length[{$length}]\");\n" " }\n" " $table->column($name, Swoole\\Table::TYPE_STRING, $length);\n" " break;\n" " default:\n" " throw new RuntimeException(\"unknown field type[{$type}]\");\n" " }\n" " }\n" "\n" " if (!$table->create()) {\n" " throw new RuntimeException('failed to create table');\n" " }\n" "\n" " return $table;\n" "}\n" "\n" "function swoole_array_list(...$arrray): Swoole\\ArrayObject\n" "{\n" " return new Swoole\\ArrayObject($arrray);\n" "}\n" "\n" "function swoole_array_default_value(array $array, $key, $default_value = null)\n" "{\n" " return array_key_exists($key, $array) ? $array[$key] : $default_value;\n" "}\n" "\n" "function swoole_is_in_container(): bool\n" "{\n" " $mountinfo = file_get_contents('/proc/self/mountinfo');\n" " return strpos($mountinfo, 'kubepods') > 0 || strpos($mountinfo, 'docker') > 0;\n" "}\n" "\n" "function swoole_container_cpu_num(): int\n" "{\n" " $swoole_cpu_num = intval(getenv('SWOOLE_CPU_NUM'));\n" " if ($swoole_cpu_num > 0) {\n" " return $swoole_cpu_num;\n" " }\n" " if (!swoole_is_in_container()) {\n" " return swoole_cpu_num();\n" " }\n" " // cgroup v2\n" " $cpu_max = '/sys/fs/cgroup/cpu.max';\n" " if (file_exists($cpu_max)) {\n" " $cpu_max = file_get_contents($cpu_max);\n" " $fields = explode($cpu_max, ' ');\n" " $quota_us = $fields[0];\n" " if ($quota_us === 'max') { // @phpstan-ignore identical.alwaysFalse\n" " return swoole_cpu_num();\n" " }\n" " $period_us = $fields[1] ?? 100000;\n" " } else {\n" " $quota_us = file_get_contents('/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us');\n" " $period_us = file_get_contents('/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us');\n" " }\n" " $cpu_num = floatval($quota_us) / floatval($period_us);\n" " if ($cpu_num < 1) {\n" " return swoole_cpu_num();\n" " }\n" " return intval(floor($cpu_num));\n" "}\n" "\n" "function swoole_init_default_remote_object_server(): void\n" "{\n" " $dir = swoole_library_get_option('default_remote_object_server_dir');\n" " if (empty($dir)) {\n" " $home = getenv('HOME') ?: sys_get_temp_dir();\n" " $dir = $home . '/.swoole';\n" " swoole_library_set_option('default_remote_object_server_dir', $dir);\n" " }\n" "\n" " $pid_file = $dir . '/remote-object-server.pid';\n" "\n" " if (!is_dir($dir)) {\n" " mkdir($dir, 0755, true);\n" " } else {\n" " if (is_file($pid_file)\n" " and posix_kill(intval(file_get_contents($pid_file)), 0)) {\n" " return;\n" " }\n" " }\n" "\n" " $options = swoole_library_get_option('default_remote_object_server_options');\n" " if (!$options) {\n" " $worker_num = swoole_library_get_option('default_remote_object_server_worker_num') ?: 128;\n" " $options = [\n" " 'worker_num' => $worker_num,\n" " 'server_mode' => defined('SWOOLE_THREAD') ? SWOOLE_THREAD : SWOOLE_BASE,\n" " ];\n" " }\n" "\n" " $php_file = $dir . '/remote-object-server.php';\n" " $socket_file = $dir . '/remote-object-server.sock';\n" " $log_file = $dir . '/remote-object-server.log';\n" " $lock_file = $dir . '/remote-object-server.lock';\n" "\n" " $wait_ready_fn = function () use ($socket_file) {\n" " // wait for remote object server ready\n" " while (true) {\n" " if (posix_access($socket_file, POSIX_R_OK)) {\n" " break;\n" " }\n" " usleep(500000);\n" " }\n" " };\n" "\n" " $lock_handle = fopen($lock_file, 'c');\n" " if (!$lock_handle) {\n" " throw new RuntimeException(\"failed to open lock file[{$lock_file}]\");\n" " }\n" " // If the lock was not acquired, it indicates that another process is trying to start the remote object server.\n" " // In this case, the service should be skipped from starting and proceed to the ready wait detection branch.\n" " if (!flock($lock_handle, LOCK_EX | LOCK_NB)) {\n" " fclose($lock_handle);\n" " $wait_ready_fn();\n" " return;\n" " }\n" "\n" " $options['enable_coroutine'] = false;\n" " $options['bootstrap'] = $php_file;\n" " $options['pid_file'] = $pid_file;\n" " $options['log_file'] = $log_file;\n" " $options['daemonize'] = true;\n" " $options['socket_type'] = SWOOLE_SOCK_UNIX_STREAM;\n" "\n" " $rv = file_put_contents($php_file, 'start();\\n\");\n" " if (!$rv) {\n" " throw new RuntimeException(\"failed to write php file[{$php_file}]\");\n" " }\n" "\n" " $php_bin = PHP_BINARY;\n" " if (posix_access($socket_file, POSIX_R_OK)) {\n" " unlink($socket_file);\n" " }\n" "\n" " $hook_flags = Swoole\\Runtime::getHookFlags();\n" " // Having enabled the MongoDB hook, you need to install the MongoDB PHP library through Composer.\n" " if (defined('SWOOLE_HOOK_MONGODB') and $hook_flags & SWOOLE_HOOK_MONGODB and !is_dir($dir . '/vendor/mongodb/mongodb')) {\n" " system(\"cd {$dir} && composer require mongodb/mongodb\");\n" " }\n" "\n" " // start server\n" " $proc = proc_open(\"{$php_bin} {$php_file}\", [\n" " 0 => ['pipe', 'r'],\n" " 1 => ['pipe', 'w'],\n" " 2 => ['pipe', 'w'],\n" " ], $pipes);\n" " if ($proc === false) {\n" " throw new RuntimeException('failed to start remote object server');\n" " }\n" " $rc = proc_close($proc);\n" " if ($rc !== 0) {\n" " $output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);\n" " throw new RuntimeException(\"failed to start remote object server: exit code {$rc}, output: \" . $output);\n" " }\n" "\n" " $wait_ready_fn();\n" " flock($lock_handle, LOCK_UN);\n" " fclose($lock_handle);\n" "}\n" "\n" "function swoole_get_default_remote_object_client(): Swoole\\RemoteObject\\Client\n" "{\n" " if (!SwooleLibrary::$remote_object_server_initiated) {\n" " SwooleLibrary::$remote_object_server_initiated = true;\n" " swoole_init_default_remote_object_server();\n" " }\n" " if (!SwooleLibrary::$remote_object_server_socket_file) {\n" " $dir = swoole_library_get_option('default_remote_object_server_dir');\n" " if (empty($dir)) {\n" " $home = getenv('HOME') ?: sys_get_temp_dir();\n" " $dir = $home . '/.swoole';\n" " }\n" " SwooleLibrary::$remote_object_server_socket_file = 'unix://' . $dir . '/remote-object-server.sock';\n" " }\n" " return new Swoole\\RemoteObject\\Client(SwooleLibrary::$remote_object_server_socket_file);\n" "}\n"; static const char* swoole_library_source_alias = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " class_alias(Swoole\\Coroutine\\WaitGroup::class, Co\\WaitGroup::class, true);\n" " class_alias(Swoole\\Coroutine\\Server::class, Co\\Server::class, true);\n" " class_alias(Swoole\\Coroutine\\Server\\Connection::class, Co\\Server\\Connection::class, true);\n" " class_alias(Swoole\\Coroutine\\FastCGI\\Client::class, Co\\FastCGI\\Client::class, true);\n" " class_alias(Swoole\\Coroutine\\FastCGI\\Client\\Exception::class, Co\\FastCGI\\Client\\Exception::class, true);\n" " class_alias(Swoole\\Coroutine\\FastCGI\\Proxy::class, Co\\FastCGI\\Proxy::class, true);\n" "}\n" "\n" "class_alias(Swoole\\Process\\Manager::class, Swoole\\Process\\ProcessManager::class, true);\n"; static const char* swoole_library_source_alias_ns = "\n" "/**\n" " * This file is part of Swoole.\n" " *\n" " * @link https://www.swoole.com\n" " * @contact team@swoole.com\n" " * @license https://github.com/swoole/library/blob/master/LICENSE\n" " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Co;\n" "\n" "use Swoole\\Coroutine;\n" "\n" "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " function run(callable $fn, ...$args)\n" " {\n" " return \\Swoole\\Coroutine\\run($fn, ...$args);\n" " }\n" "\n" " function go(callable $fn, ...$args)\n" " {\n" " return Coroutine::create($fn, ...$args);\n" " }\n" "\n" " function defer(callable $fn)\n" " {\n" " Coroutine::defer($fn);\n" " }\n" "}\n"; void php_swoole_load_library() { _eval(swoole_library_source_constants, "@swoole/library/constants.php"); _eval(swoole_library_source_std_exec, "@swoole/library/std/exec.php"); _eval(swoole_library_source_core_constant, "@swoole/library/core/Constant.php"); _eval(swoole_library_source_core_string_object, "@swoole/library/core/StringObject.php"); _eval(swoole_library_source_core_multibyte_string_object, "@swoole/library/core/MultibyteStringObject.php"); _eval(swoole_library_source_core_exception_array_key_not_exists, "@swoole/library/core/Exception/ArrayKeyNotExists.php"); _eval(swoole_library_source_core_array_object, "@swoole/library/core/ArrayObject.php"); _eval(swoole_library_source_core_object_proxy, "@swoole/library/core/ObjectProxy.php"); _eval(swoole_library_source_core_coroutine_wait_group, "@swoole/library/core/Coroutine/WaitGroup.php"); _eval(swoole_library_source_core_coroutine_server, "@swoole/library/core/Coroutine/Server.php"); _eval(swoole_library_source_core_coroutine_server_connection, "@swoole/library/core/Coroutine/Server/Connection.php"); _eval(swoole_library_source_core_coroutine_barrier, "@swoole/library/core/Coroutine/Barrier.php"); _eval(swoole_library_source_core_coroutine_http_client_proxy, "@swoole/library/core/Coroutine/Http/ClientProxy.php"); _eval(swoole_library_source_core_coroutine_http_functions, "@swoole/library/core/Coroutine/Http/functions.php"); _eval(swoole_library_source_core_connection_pool, "@swoole/library/core/ConnectionPool.php"); _eval(swoole_library_source_core_database_object_proxy, "@swoole/library/core/Database/ObjectProxy.php"); _eval(swoole_library_source_core_database_mysqli_config, "@swoole/library/core/Database/MysqliConfig.php"); _eval(swoole_library_source_core_database_mysqli_exception, "@swoole/library/core/Database/MysqliException.php"); _eval(swoole_library_source_core_database_mysqli_pool, "@swoole/library/core/Database/MysqliPool.php"); _eval(swoole_library_source_core_database_mysqli_proxy, "@swoole/library/core/Database/MysqliProxy.php"); _eval(swoole_library_source_core_database_mysqli_statement_proxy, "@swoole/library/core/Database/MysqliStatementProxy.php"); _eval(swoole_library_source_core_database_detects_lost_connections, "@swoole/library/core/Database/DetectsLostConnections.php"); _eval(swoole_library_source_core_database_pdo_config, "@swoole/library/core/Database/PDOConfig.php"); _eval(swoole_library_source_core_database_pdo_pool, "@swoole/library/core/Database/PDOPool.php"); _eval(swoole_library_source_core_database_pdo_proxy, "@swoole/library/core/Database/PDOProxy.php"); _eval(swoole_library_source_core_database_pdo_statement_proxy, "@swoole/library/core/Database/PDOStatementProxy.php"); _eval(swoole_library_source_core_database_redis_config, "@swoole/library/core/Database/RedisConfig.php"); _eval(swoole_library_source_core_database_redis_pool, "@swoole/library/core/Database/RedisPool.php"); _eval(swoole_library_source_core_http_status, "@swoole/library/core/Http/Status.php"); _eval(swoole_library_source_core_curl_exception, "@swoole/library/core/Curl/Exception.php"); _eval(swoole_library_source_core_curl_handler, "@swoole/library/core/Curl/Handler.php"); _eval(swoole_library_source_core_fast_cgi, "@swoole/library/core/FastCGI.php"); _eval(swoole_library_source_core_fast_cgi_record, "@swoole/library/core/FastCGI/Record.php"); _eval(swoole_library_source_core_fast_cgi_record_params, "@swoole/library/core/FastCGI/Record/Params.php"); _eval(swoole_library_source_core_fast_cgi_record_abort_request, "@swoole/library/core/FastCGI/Record/AbortRequest.php"); _eval(swoole_library_source_core_fast_cgi_record_begin_request, "@swoole/library/core/FastCGI/Record/BeginRequest.php"); _eval(swoole_library_source_core_fast_cgi_record_data, "@swoole/library/core/FastCGI/Record/Data.php"); _eval(swoole_library_source_core_fast_cgi_record_end_request, "@swoole/library/core/FastCGI/Record/EndRequest.php"); _eval(swoole_library_source_core_fast_cgi_record_get_values, "@swoole/library/core/FastCGI/Record/GetValues.php"); _eval(swoole_library_source_core_fast_cgi_record_get_values_result, "@swoole/library/core/FastCGI/Record/GetValuesResult.php"); _eval(swoole_library_source_core_fast_cgi_record_stdin, "@swoole/library/core/FastCGI/Record/Stdin.php"); _eval(swoole_library_source_core_fast_cgi_record_stdout, "@swoole/library/core/FastCGI/Record/Stdout.php"); _eval(swoole_library_source_core_fast_cgi_record_stderr, "@swoole/library/core/FastCGI/Record/Stderr.php"); _eval(swoole_library_source_core_fast_cgi_record_unknown_type, "@swoole/library/core/FastCGI/Record/UnknownType.php"); _eval(swoole_library_source_core_fast_cgi_frame_parser, "@swoole/library/core/FastCGI/FrameParser.php"); _eval(swoole_library_source_core_fast_cgi_message, "@swoole/library/core/FastCGI/Message.php"); _eval(swoole_library_source_core_fast_cgi_request, "@swoole/library/core/FastCGI/Request.php"); _eval(swoole_library_source_core_fast_cgi_response, "@swoole/library/core/FastCGI/Response.php"); _eval(swoole_library_source_core_fast_cgi_http_request, "@swoole/library/core/FastCGI/HttpRequest.php"); _eval(swoole_library_source_core_fast_cgi_http_response, "@swoole/library/core/FastCGI/HttpResponse.php"); _eval(swoole_library_source_core_coroutine_fast_cgi_client, "@swoole/library/core/Coroutine/FastCGI/Client.php"); _eval(swoole_library_source_core_coroutine_fast_cgi_client_exception, "@swoole/library/core/Coroutine/FastCGI/Client/Exception.php"); _eval(swoole_library_source_core_coroutine_fast_cgi_proxy, "@swoole/library/core/Coroutine/FastCGI/Proxy.php"); _eval(swoole_library_source_core_process_manager, "@swoole/library/core/Process/Manager.php"); _eval(swoole_library_source_core_remote_object, "@swoole/library/core/RemoteObject.php"); _eval(swoole_library_source_core_remote_object_server, "@swoole/library/core/RemoteObject/Server.php"); _eval(swoole_library_source_core_remote_object_context, "@swoole/library/core/RemoteObject/Context.php"); _eval(swoole_library_source_core_remote_object_client, "@swoole/library/core/RemoteObject/Client.php"); _eval(swoole_library_source_core_remote_object_exception, "@swoole/library/core/RemoteObject/Exception.php"); _eval(swoole_library_source_core_remote_object_proxy_trait, "@swoole/library/core/RemoteObject/ProxyTrait.php"); _eval(swoole_library_source_core_server_admin, "@swoole/library/core/Server/Admin.php"); _eval(swoole_library_source_core_server_helper, "@swoole/library/core/Server/Helper.php"); _eval(swoole_library_source_core_name_resolver, "@swoole/library/core/NameResolver.php"); _eval(swoole_library_source_core_name_resolver_exception, "@swoole/library/core/NameResolver/Exception.php"); _eval(swoole_library_source_core_name_resolver_cluster, "@swoole/library/core/NameResolver/Cluster.php"); _eval(swoole_library_source_core_name_resolver_redis, "@swoole/library/core/NameResolver/Redis.php"); _eval(swoole_library_source_core_name_resolver_nacos, "@swoole/library/core/NameResolver/Nacos.php"); _eval(swoole_library_source_core_name_resolver_consul, "@swoole/library/core/NameResolver/Consul.php"); _eval(swoole_library_source_core_thread_pool, "@swoole/library/core/Thread/Pool.php"); _eval(swoole_library_source_core_thread_runnable, "@swoole/library/core/Thread/Runnable.php"); _eval(swoole_library_source_core_coroutine_functions, "@swoole/library/core/Coroutine/functions.php"); _eval(swoole_library_source_ext_curl, "@swoole/library/ext/curl.php"); _eval(swoole_library_source_ext_sockets, "@swoole/library/ext/sockets.php"); _eval(swoole_library_source_ext_standard, "@swoole/library/ext/standard.php"); _eval(swoole_library_source_ext_mongodb, "@swoole/library/ext/mongodb.php"); _eval(swoole_library_source_functions, "@swoole/library/functions.php"); _eval(swoole_library_source_alias, "@swoole/library/alias.php"); _eval(swoole_library_source_alias_ns, "@swoole/library/alias_ns.php"); } ================================================ FILE: ext-src/php_swoole_odbc.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_ODBC_H #define PHP_SWOOLE_ODBC_H #include "php_swoole.h" #ifdef SW_USE_ODBC BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" #if PHP_VERSION_ID < 80300 #include "thirdparty/php82/pdo_odbc/php_pdo_odbc_int.h" #elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h" #elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h" #else #include "thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h" #endif extern const pdo_driver_t swoole_pdo_odbc_driver; #include "php_version.h" #define PHP_PDO_ODBC_VERSION PHP_VERSION RETCODE swoole_odbc_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR *ServerName, SQLSMALLINT NameLength1, SQLCHAR *UserName, SQLSMALLINT NameLength2, SQLCHAR *Authentication, SQLSMALLINT NameLength3); SQLRETURN SQL_API swoole_odbc_SQLDriverConnect(SQLHDBC hdbc, SQLHWND hwnd, SQLCHAR *szConnStrIn, SQLSMALLINT cbConnStrIn, SQLCHAR *szConnStrOut, SQLSMALLINT cbConnStrOutMax, SQLSMALLINT *pcbConnStrOut, SQLUSMALLINT fDriverCompletion); SQLRETURN SQL_API swoole_odbc_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength); SQLRETURN SQL_API swoole_odbc_SQLGetInfo(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, SQLPOINTER InfoValue, SQLSMALLINT BufferLength, SQLSMALLINT *StringLength); SQLRETURN SQL_API swoole_odbc_SQLGetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT RecNumber, SQLCHAR *Sqlstate, SQLINTEGER *NativeError, SQLCHAR *MessageText, SQLSMALLINT BufferLength, SQLSMALLINT *TextLength); SQLRETURN SQL_API swoole_odbc_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength); SQLRETURN SQL_API swoole_odbc_SQLExecute(SQLHSTMT StatementHandle); SQLRETURN SQL_API swoole_odbc_SQLCloseCursor(SQLHSTMT StatementHandle); SQLRETURN SQL_API swoole_odbc_SQLPutData(SQLHSTMT StatementHandle, SQLPOINTER Data, SQLLEN StrLen_or_Ind); SQLRETURN SQL_API swoole_odbc_SQLGetData(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValue, SQLLEN BufferLength, SQLLEN *StrLen_or_Ind); SQLRETURN SQL_API swoole_odbc_SQLRowCount(SQLHSTMT StatementHandle, SQLLEN *RowCount); SQLRETURN SQL_API swoole_odbc_SQLDescribeCol(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLCHAR *ColumnName, SQLSMALLINT BufferLength, SQLSMALLINT *NameLength, SQLSMALLINT *DataType, SQLULEN *ColumnSize, SQLSMALLINT *DecimalDigits, SQLSMALLINT *Nullable); SQLRETURN SQL_API swoole_odbc_SQLMoreResults( SQLHSTMT hstmt); SQLRETURN SQL_API swoole_odbc_SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT CompletionType); SQLRETURN SQL_API swoole_odbc_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); SQLRETURN SQL_API swoole_odbc_SQLDisconnect(SQLHDBC ConnectionHandle); void swoole_odbc_set_blocking(bool blocking); #ifdef SW_USE_ODBC_HOOK #define SQLConnect swoole_odbc_SQLConnect #define SQLDriverConnect swoole_odbc_SQLDriverConnect #define SQLExecDirect swoole_odbc_SQLExecDirect #define SQLGetInfo swoole_odbc_SQLGetInfo #define SQLGetDiagRec swoole_odbc_SQLGetDiagRec #define SQLPrepare swoole_odbc_SQLPrepare #define SQLExecute swoole_odbc_SQLExecute #define SQLCloseCursor swoole_odbc_SQLCloseCursor #define SQLGetData swoole_odbc_SQLGetData #define SQLPutData swoole_odbc_SQLPutData #define SQLRowCount swoole_odbc_SQLRowCount #define SQLDescribeCol swoole_odbc_SQLDescribeCol #define SQLEndTran swoole_odbc_SQLEndTran #define SQLFreeHandle swoole_odbc_SQLFreeHandle #define SQLDisconnect swoole_odbc_SQLDisconnect #endif END_EXTERN_C() #endif #endif ================================================ FILE: ext-src/php_swoole_oracle.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_ORACLE_H #define PHP_SWOOLE_ORACLE_H #include "php_swoole.h" #ifdef SW_USE_ORACLE BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" #include "thirdparty/pdo_oci/php_pdo_oci_int.h" extern const pdo_driver_t swoole_pdo_oci_driver; void swoole_oracle_set_blocking(bool blocking); sword swoole_oci_session_begin(OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 credt, ub4 mode); sword swoole_oci_server_detach(OCIServer *srvhp, OCIError *errhp, ub4 mode); sword swoole_oci_stmt_prepare( OCIStmt *stmtp, OCIError *errhp, const OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode); sword swoole_oci_stmt_execute(OCISvcCtx *svchp, OCIStmt *stmtp, OCIError *errhp, ub4 iters, ub4 rowoff, const OCISnapshot *snap_in, OCISnapshot *snap_out, ub4 mode); sword swoole_oci_stmt_fetch(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, ub4 mode); sword swoole_oci_stmt_fetch2(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, sb4 scrollOffset, ub4 mode); sword swoole_oci_trans_commit(OCISvcCtx *svchp, OCIError *errhp, ub4 flags); sword swoole_oci_trans_rollback(OCISvcCtx *svchp, OCIError *errhp, ub4 flags); sword swoole_oci_ping(OCISvcCtx *svchp, OCIError *errhp, ub4 mode); #ifdef SW_USE_ORACLE_HOOK #define OCISessionBegin swoole_oci_session_begin #define OCIServerDetach swoole_oci_server_detach #define OCIStmtPrepare swoole_oci_stmt_prepare #define OCIStmtExecute swoole_oci_stmt_execute #define OCIStmtFetch swoole_oci_stmt_fetch #define OCIStmtFetch2 swoole_oci_stmt_fetch2 #define OCITransCommit swoole_oci_trans_commit #define OCITransRollback swoole_oci_trans_rollback #define OCIPing swoole_oci_ping #endif END_EXTERN_C() #endif #endif ================================================ FILE: ext-src/php_swoole_pgsql.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_PGSQL_H #define PHP_SWOOLE_PGSQL_H #include "php_swoole.h" #ifdef SW_USE_PGSQL BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" #if PHP_VERSION_ID < 80300 #include "thirdparty/php82/pdo_pgsql/php_pdo_pgsql_int.h" #elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h" #elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h" #else #include "thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h" #endif extern const pdo_driver_t swoole_pdo_pgsql_driver; #include #include void swoole_pgsql_set_blocking(bool blocking); PGconn *swoole_pgsql_connectdb(const char *conninfo); PGresult *swoole_pgsql_prepare(PGconn *conn, const char *stmt_name, const char *query, int n_params, const Oid *param_types); PGresult *swoole_pgsql_exec_prepared(PGconn *conn, const char *stmt_name, int n_params, const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format); PGresult *swoole_pgsql_exec(PGconn *conn, const char *query); PGresult *swoole_pgsql_exec_params(PGconn *conn, const char *command, int n_params, const Oid *param_types, const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format); #ifdef SW_USE_PGSQL_HOOK #define PQconnectdb swoole_pgsql_connectdb #define PQprepare swoole_pgsql_prepare #define PQexecPrepared swoole_pgsql_exec_prepared #define PQexec swoole_pgsql_exec #define PQexecParams swoole_pgsql_exec_params #endif END_EXTERN_C() #endif #endif ================================================ FILE: ext-src/php_swoole_private.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_PRIVATE_H #define PHP_SWOOLE_PRIVATE_H #include "php_swoole.h" #include "php_swoole_api.h" #include "swoole.h" #include "swoole_api.h" #include "swoole_coroutine_api.h" #ifdef SW_HAVE_ZLIB #include #endif BEGIN_EXTERN_C() #include #include #include #include #include #include #include #define PHP_SWOOLE_VERSION SWOOLE_VERSION #ifdef PHP_WIN32 #define PHP_SWOOLE_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 #define PHP_SWOOLE_API __attribute__((visibility("default"))) #else #define PHP_SWOOLE_API #endif #define SW_CHECK_RETURN(s) \ if (s < 0) { \ RETURN_FALSE; \ } else { \ RETURN_TRUE; \ } #define SW_LOCK_CHECK_RETURN(s) \ zend_long ___tmp_return_value = s; \ if (___tmp_return_value == 0) { \ RETURN_TRUE; \ } else { \ zend_update_property_long(NULL, SW_Z8_OBJ_P(ZEND_THIS), SW_STRL("errCode"), ___tmp_return_value); \ RETURN_FALSE; \ } #ifdef SW_THREAD #define SW_MUST_BE_MAIN_THREAD_EX(op) \ if (!tsrm_is_main_thread()) { \ swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); \ op; \ } #define SW_MUST_BE_MAIN_THREAD() SW_MUST_BE_MAIN_THREAD_EX(RETURN_TRUE) #else #define SW_MUST_BE_MAIN_THREAD_EX(op) #define SW_MUST_BE_MAIN_THREAD() #endif #define php_swoole_fatal_error(level, fmt_str, ...) \ swoole_set_last_error(SW_ERROR_PHP_FATAL_ERROR); \ php_error_docref(NULL, level, (const char *) (fmt_str), ##__VA_ARGS__) /** * The error occurred at the PHP layer and no error code was set */ #define php_swoole_error(level, fmt_str, ...) \ swoole_set_last_error(SW_ERROR_PHP_RUNTIME_NOTICE); \ if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) /** * The error occurred in the core must have error code */ #define php_swoole_core_error(level, fmt_str, ...) \ if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) #define php_swoole_error_ex(level, err_code, fmt_str, ...) \ swoole_set_last_error(err_code); \ if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) #define php_swoole_sys_error(level, fmt_str, ...) \ swoole_set_last_error(errno); \ if (SWOOLE_G(display_errors) || level == E_ERROR) \ php_error_docref(NULL, level, fmt_str ", Error: %s[%d]", ##__VA_ARGS__, strerror(errno), errno) #ifdef SW_USE_CARES #ifndef HAVE_CARES #error "Enable c-ares support, require c-ares library" #endif #endif #if defined(SW_HAVE_ZLIB) || defined(SW_HAVE_BROTLI) || defined(SW_HAVE_ZSTD) #define SW_HAVE_COMPRESSION #endif #ifdef SW_SOCKETS #include "ext/sockets/php_sockets.h" #define SWOOLE_SOCKETS_SUPPORT #endif #if PHP_VERSION_ID < 80200 #error "require PHP version 8.2 or later" #elif PHP_VERSION_ID >= 80600 #error "require PHP version 8.5 or earlier" #endif #if defined(ZTS) && defined(SW_USE_THREAD_CONTEXT) #error "thread context cannot be used with ZTS" #endif #if defined(SW_USE_IOURING) && !defined(__linux__) #error "only linux support iouring" #endif #if defined(SW_THREAD) && !defined(ZTS) #error "swoole thread must be used with ZTS" #endif //-------------------------------------------------------- #define SW_MAX_FIND_COUNT 100 // for swoole_server::connection_list #define SW_PHP_CLIENT_BUFFER_SIZE 65535 #define SW_ASYNC_FILE_PROTOCOL "async.file" //--------------------------------------------------------- enum php_swoole_fd_type { PHP_SWOOLE_FD_STREAM_CLIENT = SW_FD_STREAM_CLIENT, PHP_SWOOLE_FD_DGRAM_CLIENT, PHP_SWOOLE_FD_MYSQL, PHP_SWOOLE_FD_REDIS, PHP_SWOOLE_FD_HTTPCLIENT, PHP_SWOOLE_FD_PROCESS_STREAM, PHP_SWOOLE_FD_MYSQL_CORO, PHP_SWOOLE_FD_REDIS_CORO, PHP_SWOOLE_FD_POSTGRESQL, PHP_SWOOLE_FD_SOCKET, PHP_SWOOLE_FD_CO_CURL, }; //--------------------------------------------------------- enum php_swoole_req_status { PHP_SWOOLE_RINIT_BEGIN, PHP_SWOOLE_RINIT_END, PHP_SWOOLE_CALL_USER_SHUTDOWNFUNC_BEGIN, PHP_SWOOLE_RSHUTDOWN_BEGIN, PHP_SWOOLE_RSHUTDOWN_END, }; //--------------------------------------------------------- enum php_swoole_hook_type { PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK = SW_GLOBAL_HOOK_USER, PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK, PHP_SWOOLE_HOOK_BEFORE_REQUEST, PHP_SWOOLE_HOOK_AFTER_RESPONSE, }; //--------------------------------------------------------- extern zend_class_entry *swoole_event_ce; extern zend_class_entry *swoole_timer_ce; extern zend_class_entry *swoole_socket_coro_ce; extern zend_class_entry *swoole_client_ce; extern zend_object_handlers swoole_client_handlers; extern zend_class_entry *swoole_server_ce; extern zend_object_handlers swoole_server_handlers; extern zend_class_entry *swoole_redis_server_ce; extern zend_object_handlers swoole_redis_server_handlers; extern zend_class_entry *swoole_connection_iterator_ce; extern zend_class_entry *swoole_process_ce; extern zend_class_entry *swoole_http_server_ce; extern zend_object_handlers swoole_http_server_handlers; extern zend_class_entry *swoole_websocket_server_ce; extern zend_class_entry *swoole_websocket_frame_ce; extern zend_class_entry *swoole_server_port_ce; extern zend_class_entry *swoole_exception_ce; extern zend_object_handlers swoole_exception_handlers; extern zend_class_entry *swoole_error_ce; extern zend_class_entry *swoole_resolve_context_ce; extern zend_object_handlers swoole_resolve_context_handlers; PHP_FUNCTION(swoole_clear_dns_cache); PHP_FUNCTION(swoole_last_error); PHP_FUNCTION(swoole_set_process_name); //--------------------------------------------------------- // Coroutine API //--------------------------------------------------------- PHP_FUNCTION(swoole_coroutine_create); PHP_FUNCTION(swoole_coroutine_exec); PHP_FUNCTION(swoole_coroutine_gethostbyname); PHP_FUNCTION(swoole_coroutine_defer); PHP_FUNCTION(swoole_coroutine_socketpair); PHP_FUNCTION(swoole_test_kernel_coroutine); // for tests //--------------------------------------------------------- // event //--------------------------------------------------------- PHP_FUNCTION(swoole_client_select); //--------------------------------------------------------- // async[coro] //--------------------------------------------------------- PHP_FUNCTION(swoole_async_set); PHP_FUNCTION(swoole_async_dns_lookup_coro); //--------------------------------------------------------- // tracer //--------------------------------------------------------- PHP_FUNCTION(swoole_tracer_leak_detect); PHP_FUNCTION(swoole_tracer_prof_begin); PHP_FUNCTION(swoole_tracer_prof_end); //--------------------------------------------------------- // error //--------------------------------------------------------- #define SW_STRERROR_SYSTEM 0 #define SW_STRERROR_GAI 1 #define SW_STRERROR_DNS 2 #define SW_STRERROR_SWOOLE 9 /** * MINIT * ============================================================== */ void php_swoole_event_minit(int module_number); // base void php_swoole_atomic_minit(int module_number); void php_swoole_lock_minit(int module_number); int swoole_resolve_context_module_init(INIT_FUNC_ARGS); void php_swoole_process_minit(int module_number); void php_swoole_process_pool_minit(int module_number); void php_swoole_table_minit(int module_number); void php_swoole_timer_minit(int module_number); // coroutine void php_swoole_coroutine_minit(int module_number); void php_swoole_coroutine_system_minit(int module_number); void php_swoole_coroutine_scheduler_minit(int module_number); void php_swoole_coroutine_lock_minit(int module_number); void php_swoole_channel_coro_minit(int module_number); void php_swoole_runtime_minit(int module_number); // client void php_swoole_socket_coro_minit(int module_number); void php_swoole_client_minit(int module_number); void php_swoole_client_async_minit(int module_number); void php_swoole_client_coro_minit(int module_number); void php_swoole_http_client_coro_minit(int module_number); void php_swoole_http2_client_coro_minit(int module_number); #ifdef SW_USE_PGSQL void php_swoole_pgsql_minit(int module_number); #endif #ifdef SW_USE_ODBC int php_swoole_odbc_minit(int module_id); #endif #ifdef SW_USE_ORACLE void php_swoole_oracle_minit(int module_number); #endif #ifdef SW_USE_SQLITE void php_swoole_sqlite_minit(int module_number); #endif #ifdef SW_USE_FIREBIRD void php_swoole_firebird_minit(int module_number); #endif // server void php_swoole_server_minit(int module_number); void php_swoole_server_port_minit(int module_number); void php_swoole_http_request_minit(int module_number); void php_swoole_http_response_minit(int module_number); void php_swoole_http_cookie_minit(int module_number); void php_swoole_http_server_minit(int module_number); void php_swoole_http_server_coro_minit(int module_number); void php_swoole_websocket_server_minit(int module_number); void php_swoole_redis_server_minit(int module_number); void php_swoole_name_resolver_minit(int module_number); #ifdef SW_THREAD void php_swoole_thread_minit(int module_number); void php_swoole_thread_atomic_minit(int module_number); void php_swoole_thread_lock_minit(int module_number); void php_swoole_thread_barrier_minit(int module_number); void php_swoole_thread_queue_minit(int module_number); void php_swoole_thread_map_minit(int module_number); void php_swoole_thread_arraylist_minit(int module_number); #endif #ifdef SW_STDEXT void php_swoole_stdext_minit(int module_number); #endif void php_swoole_tracer_minit(int module_number); /** * RINIT * ============================================================== */ void php_swoole_http_server_rinit(); void php_swoole_coroutine_rinit(); void php_swoole_runtime_rinit(); #ifdef SW_USE_ORACLE void php_swoole_oracle_rinit(); #endif void php_swoole_thread_rinit(); void php_swoole_tracer_rinit(); /** * RSHUTDOWN * ============================================================== */ void php_swoole_http_server_rshutdown(); void php_swoole_http_response_rshutdown(); void php_swoole_async_coro_rshutdown(); void php_swoole_redis_server_rshutdown(); void php_swoole_coroutine_rshutdown(); void php_swoole_process_rshutdown(); void php_swoole_coroutine_scheduler_rshutdown(); void php_swoole_runtime_rshutdown(); void php_swoole_timer_rshutdown(); void php_swoole_server_rshutdown(); #ifdef SW_THREAD void php_swoole_thread_rshutdown(); #endif void php_swoole_tracer_rshutdown(); int php_swoole_reactor_init(); void php_swoole_set_global_option(zend_array *vht); void php_swoole_set_coroutine_option(zend_array *vht); void php_swoole_set_aio_option(const zend_array *vht); // shutdown void php_swoole_register_shutdown_function(const char *function); void php_swoole_register_shutdown_function_prepend(const char *function); // event void php_swoole_event_init(); void php_swoole_event_wait(); void php_swoole_event_exit(); /** * MSHUTDOWN * ============================================================== */ void php_swoole_runtime_mshutdown(); #ifdef SW_USE_PGSQL void php_swoole_pgsql_mshutdown(); #endif #ifdef SW_USE_ORACLE void php_swoole_oracle_mshutdown(); #endif #ifdef SW_USE_SQLITE void php_swoole_sqlite_mshutdown(); #endif #ifdef SW_USE_FIREBIRD void php_swoole_firebird_mshutdown(); #endif int php_swoole_convert_to_fd(zval *zsocket); int php_swoole_convert_to_fd_ex(zval *zsocket, int *async); #ifdef SWOOLE_SOCKETS_SUPPORT php_socket *php_swoole_convert_to_socket(int sock); #endif #ifdef HAVE_CPU_AFFINITY bool php_swoole_array_to_cpu_set(const zval *array, cpu_set_t *cpu_set); /** * Converts a cpu_set_t structure to a PHP array. * * Note: On Cygwin platform, CPU_ISSET is a function that takes a non-const pointer as its second parameter, * which is why the cpu_set parameter cannot be declared as const. * * @param array The PHP array to store the CPU set information * @param cpu_set The CPU set structure to convert */ void php_swoole_cpu_set_to_array(zval *array, cpu_set_t *cpu_set); #endif zend_bool php_swoole_signal_isset_handler(int signo); #define sw_zend7_object zend_object #define SW_Z8_OBJ_P(zobj) Z_OBJ_P(zobj) typedef ssize_t php_stream_size_t; typedef zend_string error_filename_t; //----------------------------------Zval API------------------------------------ // Deprecated: do not use it anymore // do not use sw_copy_to_stack(return_value, foo); #define sw_copy_to_stack(ptr, val) \ do { \ (val) = *(zval *) (ptr); \ (ptr) = &(val); \ } while (0) #define SW_ZVAL_SOCKET(return_value, result) ZVAL_OBJ(return_value, &result->std) #define SW_Z_SOCKET_P(zsocket) Z_SOCKET_P(zsocket) static sw_inline zend_bool ZVAL_IS_TRUE(const zval *v) { return Z_TYPE_P(v) == IS_TRUE; } static sw_inline zend_bool ZVAL_IS_FALSE(const zval *v) { return Z_TYPE_P(v) == IS_FALSE; } static sw_inline zend_bool ZVAL_IS_BOOL(const zval *v) { return ZVAL_IS_TRUE(v) || ZVAL_IS_FALSE(v); } static sw_inline zend_bool ZVAL_IS_UNDEF(const zval *v) { return Z_TYPE_P(v) == IS_UNDEF; } static sw_inline zend_bool ZVAL_IS_LONG(const zval *v) { return Z_TYPE_P(v) == IS_LONG; } static sw_inline zend_bool ZVAL_IS_STRING(const zval *v) { return Z_TYPE_P(v) == IS_STRING; } static sw_inline zend_bool ZVAL_IS_EMPTY_STRING(const zval *v) { return Z_TYPE_P(v) == IS_STRING && Z_STRLEN_P(v) == 0; } static sw_inline zend_bool Z_BVAL_P(const zval *v) { return Z_TYPE_P(v) == IS_TRUE; } static sw_inline zend_bool ZVAL_IS_ARRAY(const zval *v) { return Z_TYPE_P(v) == IS_ARRAY; } static sw_inline zend_bool ZVAL_IS_REF(const zval *v) { return Z_TYPE_P(v) == IS_REFERENCE; } static sw_inline zend_bool ZVAL_IS_OBJECT(const zval *v) { return Z_TYPE_P(v) == IS_OBJECT; } static sw_inline zval *sw_malloc_zval() { return static_cast(emalloc(sizeof(zval))); } static sw_inline zval *sw_zval_dup(zval *val) { zval *dup = sw_malloc_zval(); memcpy(dup, val, sizeof(zval)); return dup; } static sw_inline void sw_zval_free(zval *val) { zval_ptr_dtor(val); efree(val); } #ifdef SWOOLE_SOCKETS_SUPPORT static inline bool sw_zval_is_php_socket(zval *val) { return instanceof_function(Z_OBJCE_P(val), socket_ce); } #endif static inline bool sw_zval_is_co_socket(zval *val) { return instanceof_function(Z_OBJCE_P(val), swoole_socket_coro_ce); } static inline bool sw_zval_is_client(zval *val) { return instanceof_function(Z_OBJCE_P(val), swoole_client_ce); } static inline bool sw_zval_is_process(zval *val) { return instanceof_function(Z_OBJCE_P(val), swoole_process_ce); } bool sw_zval_is_serializable(const zval *struc); static inline bool sw_is_main_thread() { #ifdef SW_THREAD return tsrm_is_main_thread(); #else return true; #endif } int sw_module_number(); #ifdef SW_THREAD size_t sw_active_thread_count(void); #else static inline size_t sw_active_thread_count() { return 1; } #endif zend_refcounted *sw_get_refcount_ptr(zval *value); void sw_php_exit(int status); void sw_php_print_backtrace(zend_long cid = 0, zend_long options = 0, zend_long limit = 0, zval *return_value = nullptr); void sw_php_print_backtrace_impl(int skip_last = 1, int options = 0, int limit = 0, bool include_main = false); //----------------------------------Constant API------------------------------------ #define SW_REGISTER_NULL_CONSTANT(name) REGISTER_NULL_CONSTANT(name, CONST_CS | CONST_PERSISTENT) #define SW_REGISTER_BOOL_CONSTANT(name, value) REGISTER_BOOL_CONSTANT(name, value, CONST_CS | CONST_PERSISTENT) #define SW_REGISTER_LONG_CONSTANT(name, value) REGISTER_LONG_CONSTANT(name, value, CONST_CS | CONST_PERSISTENT) #define SW_REGISTER_DOUBLE_CONSTANT(name, value) REGISTER_DOUBLE_CONSTANT(name, value, CONST_CS | CONST_PERSISTENT) #define SW_REGISTER_STRING_CONSTANT(name, value) \ REGISTER_STRING_CONSTANT(name, (char *) value, CONST_CS | CONST_PERSISTENT) #define SW_REGISTER_STRINGL_CONSTANT(name, value) \ REGISTER_STRINGL_CONSTANT(name, (char *) value, CONST_CS | CONST_PERSISTENT) //----------------------------------Number API----------------------------------- #define sw_php_math_round(value, places, mode) _php_math_round(value, places, mode) //----------------------------------String API----------------------------------- #define SW_PHP_OB_START(zoutput) \ zval zoutput; \ do { \ php_output_start_user(NULL, 0, PHP_OUTPUT_HANDLER_STDFLAGS); #define SW_PHP_OB_END() \ php_output_get_contents(&zoutput); \ php_output_discard(); \ } \ while (0) static sw_inline zend_string *sw_zend_string_recycle(zend_string *s, size_t alloc_len, size_t real_len) { SW_ASSERT(!ZSTR_IS_INTERNED(s)); if (UNEXPECTED(alloc_len != real_len)) { if (alloc_len > swoole_pagesize() && alloc_len > real_len * 2) { s = zend_string_realloc(s, real_len, false); } else { ZSTR_LEN(s) = real_len; } } ZSTR_VAL(s)[real_len] = '\0'; return s; } //----------------------------------Array API------------------------------------ #define php_swoole_array_length(zarray) zend_hash_num_elements(Z_ARRVAL_P(zarray)) #define php_swoole_array_get_value(ht, str, v) ((v = zend_hash_str_find(ht, str, sizeof(str) - 1)) && !ZVAL_IS_NULL(v)) #define php_swoole_array_get_value_ex(ht, str, v) ((v = zend_hash_str_find(ht, str, strlen(str))) && !ZVAL_IS_NULL(v)) static sw_inline int php_swoole_array_length_safe(zval *zarray) { if (zarray && ZVAL_IS_ARRAY(zarray)) { return php_swoole_array_length(zarray); } else { return 0; } } void php_swoole_sha1(const char *str, int _len, uchar *digest); void php_swoole_sha256(const char *str, int _len, uchar *digest); #define SW_HASHTABLE_FOREACH_START(ht, _val) \ ZEND_HASH_FOREACH_VAL(ht, _val); \ { #define SW_HASHTABLE_FOREACH_START2(ht, k, klen, ktype, _val) \ zend_string *_foreach_key; \ ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _foreach_key, _val); \ if (!_foreach_key) { \ k = NULL; \ klen = 0; \ ktype = 0; \ } else { \ k = ZSTR_VAL(_foreach_key), klen = ZSTR_LEN(_foreach_key); \ ktype = 1; \ } \ { #define SW_HASHTABLE_FOREACH_END() \ } \ ZEND_HASH_FOREACH_END(); static sw_inline void add_assoc_ulong_safe_ex(zval *arg, const char *key, size_t key_len, zend_ulong value) { if (sw_likely(value <= ZEND_LONG_MAX)) { add_assoc_long_ex(arg, key, key_len, value); } else { char buf[MAX_LENGTH_OF_LONG + 1]; size_t len = sw_snprintf(buf, sizeof(buf), ZEND_ULONG_FMT, value); add_assoc_stringl_ex(arg, key, key_len, buf, len); } } static sw_inline void add_assoc_ulong_safe(zval *arg, const char *key, zend_ulong value) { add_assoc_ulong_safe_ex(arg, key, strlen(key), value); } //----------------------------------Class API------------------------------------ #define SW_Z_OBJCE_NAME_VAL_P(zobject) ZSTR_VAL(Z_OBJCE_P(zobject)->name) /* PHP 7 class declaration macros */ #define SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, short_name, methods, parent_ce) \ do { \ zend_class_entry _##module##_ce = {}; \ INIT_CLASS_ENTRY(_##module##_ce, namespace_name, methods); \ module##_ce = zend_register_internal_class_ex(&_##module##_ce, parent_ce); \ if (short_name) SW_CLASS_ALIAS_SHORT_NAME(short_name, module); \ } while (0) #define SW_INIT_CLASS_ENTRY_STD(module, namespace_name, methods) \ SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, nullptr, methods, NULL); \ memcpy(&module##_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)) #define SW_INIT_CLASS_ENTRY(module, namespace_name, short_name, methods) \ SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, short_name, methods, NULL); \ memcpy(&module##_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)) #define SW_INIT_CLASS_ENTRY_EX(module, namespace_name, short_name, methods, parent_module) \ SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, short_name, methods, parent_module##_ce); \ memcpy(&module##_handlers, &parent_module##_handlers, sizeof(zend_object_handlers)) #define SW_INIT_CLASS_ENTRY_EX2(module, namespace_name, short_name, methods, parent_module_ce, parent_module_handlers) \ SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, short_name, methods, parent_module_ce); \ memcpy(&module##_handlers, parent_module_handlers, sizeof(zend_object_handlers)) // Data Object: no methods, no parent #define SW_INIT_CLASS_ENTRY_DATA_OBJECT(module, namespace_name) \ SW_INIT_CLASS_ENTRY_BASE(module, namespace_name, NULL, NULL, NULL); \ memcpy(&module##_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)) #define SW_CLASS_ALIAS(name, module) \ do { \ if (name) { \ sw_zend_register_class_alias(ZEND_STRL(name), module##_ce); \ } \ } while (0) #define SW_CLASS_ALIAS_SHORT_NAME(short_name, module) \ do { \ if (SWOOLE_G(use_shortname)) { \ SW_CLASS_ALIAS(short_name, module); \ } \ } while (0) #define SW_SET_CLASS_NOT_SERIALIZABLE(module) module##_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; #define sw_zend_class_clone_deny NULL #define SW_SET_CLASS_CLONEABLE(module, _clone_obj) module##_handlers.clone_obj = _clone_obj #define SW_SET_CLASS_UNSET_PROPERTY_HANDLER(module, _unset_property) module##_handlers.unset_property = _unset_property #define SW_SET_CLASS_CREATE(module, _create_object) module##_ce->create_object = _create_object #define SW_SET_CLASS_DTOR(module, _dtor_obj) module##_handlers.dtor_obj = _dtor_obj #define SW_SET_CLASS_FREE(module, _free_obj) module##_handlers.free_obj = _free_obj #define SW_SET_CLASS_CREATE_AND_FREE(module, _create_object, _free_obj) \ SW_SET_CLASS_CREATE(module, _create_object); \ SW_SET_CLASS_FREE(module, _free_obj) #define SW_SET_CLASS_CUSTOM_OBJECT(module, _create_object, _free_obj, _struct, _std) \ SW_SET_CLASS_CREATE_AND_FREE(module, _create_object, _free_obj); \ module##_handlers.offset = XtOffsetOf(_struct, _std) #define SW_PREVENT_USER_DESTRUCT() \ do { \ if (sw_unlikely(!(GC_FLAGS(Z_OBJ_P(ZEND_THIS)) & IS_OBJ_DESTRUCTOR_CALLED))) { \ RETURN_NULL(); \ } \ } while (0) #define SW_FUNCTION_ALIAS(origin_function_table, origin, alias_function_table, alias, arg_info) \ sw_zend_register_function_alias( \ origin_function_table, ZEND_STRL(origin), alias_function_table, ZEND_STRL(alias), arg_info) static sw_inline int sw_zend_register_function_alias(zend_array *origin_function_table, const char *origin, size_t origin_length, zend_array *alias_function_table, const char *alias, size_t alias_length, const zend_internal_arg_info *arg_info) { zend_string *lowercase_origin = zend_string_alloc(origin_length, false); zend_str_tolower_copy(ZSTR_VAL(lowercase_origin), origin, origin_length); auto *origin_function = static_cast(zend_hash_find_ptr(origin_function_table, lowercase_origin)); zend_string_release(lowercase_origin); if (UNEXPECTED(!origin_function)) { return FAILURE; } SW_ASSERT(origin_function->common.type == ZEND_INTERNAL_FUNCTION); char *_alias = (char *) emalloc(alias_length + 1); ((char *) memcpy(_alias, alias, alias_length))[alias_length] = '\0'; zend_function_entry zfe[] = { {_alias, origin_function->internal_function.handler, arg_info, origin_function->common.num_args, 0}, PHP_FE_END}; int ret = zend_register_functions(nullptr, zfe, alias_function_table, origin_function->common.type); efree(_alias); return ret; } static sw_inline int sw_zend_register_class_alias(const char *name, size_t name_len, zend_class_entry *ce) { zend_string *_name; if (name[0] == '\\') { _name = zend_string_init(name, name_len, true); zend_str_tolower_copy(ZSTR_VAL(_name), name + 1, name_len - 1); } else { _name = zend_string_init(name, name_len, true); zend_str_tolower_copy(ZSTR_VAL(_name), name, name_len); } zend_string *_interned_name = zend_new_interned_string(_name); return zend_register_class_alias_ex(ZSTR_VAL(_interned_name), ZSTR_LEN(_interned_name), ce, true); } static sw_inline zend_object *sw_zend_create_object(zend_class_entry *ce, zend_object_handlers *handlers) { auto *object = static_cast(zend_object_alloc(sizeof(zend_object), ce)); zend_object_std_init(object, ce); object_properties_init(object, ce); object->handlers = handlers; return object; } static sw_inline zend_object *sw_zend_create_object_deny(zend_class_entry *ce) { zend_object *object = zend_objects_new(ce); /* Initialize default properties */ if (EXPECTED(ce->default_properties_count != 0)) { zval *p = object->properties_table; zval *end = p + ce->default_properties_count; do { ZVAL_UNDEF(p); p++; } while (p != end); } zend_throw_error(nullptr, "The object of %s can not be created for security reasons", ZSTR_VAL(ce->name)); return object; } static sw_inline void sw_zend_class_unset_property_deny(zend_object *object, zend_string *member, void **cache_slot) { zend_class_entry *ce = object->ce; while (ce->parent) { ce = ce->parent; } SW_ASSERT(ce->type == ZEND_INTERNAL_CLASS); if (EXPECTED(zend_hash_find(&ce->properties_info, member))) { zend_throw_error( nullptr, "Property %s of class %s cannot be unset", ZSTR_VAL(member), ZSTR_VAL(object->ce->name)); return; } std_object_handlers.unset_property(object, member, cache_slot); } static sw_inline zval *sw_zend_read_property(zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); if (UNEXPECTED(property == &EG(uninitialized_zval))) { zend_update_property_null(ce, SW_Z8_OBJ_P(obj), s, len); return zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); } return property; } static sw_inline void sw_zend_update_property_null_ex(zend_class_entry *scope, zval *object, zend_string *s) { zval tmp; ZVAL_NULL(&tmp); zend_update_property_ex(scope, SW_Z8_OBJ_P(object), s, &tmp); } static sw_inline zval *sw_zend_read_property_ex(zend_class_entry *ce, zval *zobject, zend_string *name, int silent) { zval *zv = zend_hash_find(&ce->properties_info, name); auto *property_info = (zend_property_info *) Z_PTR_P(zv); zval *property = OBJ_PROP(SW_Z8_OBJ_P(zobject), property_info->offset); if (UNEXPECTED(property == &EG(uninitialized_zval))) { ZVAL_NULL(property); } return property; } static sw_inline zval *sw_zend_read_property_not_null( zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); zend_uchar type = Z_TYPE_P(property); return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? nullptr : property; } static sw_inline zval *sw_zend_read_property_not_null_ex(zend_class_entry *ce, zval *obj, zend_string *s, int silent) { zval rv, *property = zend_read_property_ex(ce, SW_Z8_OBJ_P(obj), s, silent, &rv); zend_uchar type = Z_TYPE_P(property); return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? nullptr : property; } static sw_inline zval *sw_zend_update_and_read_property_array(zend_class_entry *ce, zval *obj, const char *s, size_t len) { zval ztmp; array_init(&ztmp); zend_update_property(ce, SW_Z8_OBJ_P(obj), s, len, &ztmp); zval_ptr_dtor(&ztmp); return zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, true, &ztmp); } static sw_inline zval *sw_zend_read_and_convert_property_array( zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); if (Z_TYPE_P(property) != IS_ARRAY) { // NOTICE: if user unset the property, zend_read_property will return uninitialized_zval instead of NULL pointer if (UNEXPECTED(property == &EG(uninitialized_zval))) { property = sw_zend_update_and_read_property_array(ce, obj, s, len); } else { zval_ptr_dtor(property); array_init(property); } } return property; } #define SW_RETURN_PROPERTY(name) \ do { \ RETURN_ZVAL(sw_zend_read_property(Z_OBJCE_P(ZEND_THIS), ZEND_THIS, ZEND_STRL(name), 0), 1, 0); \ } while (0) #define RETURN_SW_STRING(buf) \ do { \ RETURN_STRINGL(buf->str, buf->length); \ } while (0) //----------------------------------Function API------------------------------------ /** * Notice (sw_zend_call_method_with_%u_params): If you don't want to check the return value, please set retval to NULL */ #define sw_zend_call_method_with_0_params(zobj, obj_ce, fn_ptr_ptr, fn_name, retval) \ zend_call_method_with_0_params(SW_Z8_OBJ_P(zobj), obj_ce, fn_ptr_ptr, fn_name, retval) #define sw_zend_call_method_with_1_params(zobj, obj_ce, fn_ptr_ptr, fn_name, retval, v1) \ zend_call_method_with_1_params(SW_Z8_OBJ_P(zobj), obj_ce, fn_ptr_ptr, fn_name, retval, v1) #define sw_zend_call_method_with_2_params(zobj, obj_ce, fn_ptr_ptr, fn_name, retval, v1, v2) \ zend_call_method_with_2_params(SW_Z8_OBJ_P(zobj), obj_ce, fn_ptr_ptr, fn_name, retval, v1, v2) static sw_inline int sw_zend_function_max_num_args(zend_function *function) { // https://github.com/php/php-src/commit/2646f7bcb98dcdd322ea21701c8bb101104ea619 // zend_function.common.num_args don't include the variadic argument anymore. return (function->common.fn_flags & ZEND_ACC_VARIADIC) ? UINT32_MAX : function->common.num_args; } // TODO: remove it after remove async modules static sw_inline zend_bool sw_zend_is_callable(zval *callable, int check_flags, char **callable_name) { zend_string *name; zend_bool ret = zend_is_callable(callable, check_flags, &name); *callable_name = estrndup(ZSTR_VAL(name), ZSTR_LEN(name)); zend_string_release(name); return ret; } static sw_inline zend_bool sw_zend_is_callable_at_frame(zval *zcallable, zval *zobject, zend_execute_data *frame, uint check_flags, char **callable_name, size_t *callable_name_len, zend_fcall_info_cache *fci_cache, char **error) { zend_bool ret = zend_is_callable_at_frame( zcallable, zobject ? Z_OBJ_P(zobject) : nullptr, frame, check_flags, fci_cache, error); zend_string *name = zend_get_callable_name_ex(zcallable, zobject ? Z_OBJ_P(zobject) : nullptr); if (callable_name) { *callable_name = estrndup(ZSTR_VAL(name), ZSTR_LEN(name)); } if (callable_name_len) { *callable_name_len = ZSTR_LEN(name); } zend_string_release(name); return ret; } static sw_inline zend_bool sw_zend_is_callable_ex(zval *zcallable, zval *zobject, uint check_flags, char **callable_name, size_t *callable_name_len, zend_fcall_info_cache *fci_cache, char **error) { return sw_zend_is_callable_at_frame( zcallable, zobject, nullptr, check_flags, callable_name, callable_name_len, fci_cache, error); } /* this API can work well when retval is NULL */ static sw_inline int sw_zend_call_function_ex( zval *function_name, zend_fcall_info_cache *fci_cache, uint32_t param_count, zval *params, zval *retval) { zend_fcall_info fci; zval _retval; fci.size = sizeof(fci); fci.object = nullptr; if (!fci_cache || !fci_cache->function_handler) { if (!function_name) { php_swoole_fatal_error(E_WARNING, "Bad function"); return FAILURE; } ZVAL_COPY_VALUE(&fci.function_name, function_name); } else { ZVAL_UNDEF(&fci.function_name); } fci.retval = retval ? retval : &_retval; fci.param_count = param_count; fci.params = params; fci.named_params = nullptr; int ret = zend_call_function(&fci, fci_cache); if (!retval) { zval_ptr_dtor(&_retval); } return ret; } /* we must check for exception immediately if we don't have chances to go back to ZendVM (e.g event loop) */ static sw_inline int sw_zend_call_function_ex2( zval *function_name, zend_fcall_info_cache *fci_cache, uint32_t param_count, zval *params, zval *retval) { int ret = sw_zend_call_function_ex(function_name, fci_cache, param_count, params, retval); if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return ret; } static sw_inline int sw_zend_call_function_anyway(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) { zval retval; if (!fci->retval) { fci->retval = &retval; } int ret = zend_call_function(fci, fci_cache); if (fci->retval == &retval) { zval_ptr_dtor(&retval); } return ret; } static sw_inline void sw_zend_fci_params_persist(zend_fcall_info *fci) { if (fci->param_count > 0) { zval *params = (zval *) ecalloc(fci->param_count, sizeof(zval)); for (uint32_t i = 0; i < fci->param_count; i++) { ZVAL_COPY(¶ms[i], &fci->params[i]); } fci->params = params; } } static sw_inline void sw_zend_fci_params_discard(zend_fcall_info *fci) { if (fci->param_count > 0) { for (uint32_t i = 0; i < fci->param_count; i++) { zval_ptr_dtor(&fci->params[i]); } efree(fci->params); } } static sw_inline void sw_zend_fci_cache_persist(zend_fcall_info_cache *fci_cache) { if (fci_cache->object) { GC_ADDREF(fci_cache->object); } if (fci_cache->function_handler->op_array.fn_flags & ZEND_ACC_CLOSURE) { GC_ADDREF(ZEND_CLOSURE_OBJECT(fci_cache->function_handler)); } } static sw_inline void sw_zend_fci_cache_discard(zend_fcall_info_cache *fci_cache) { if (fci_cache->object) { OBJ_RELEASE(fci_cache->object); } if (fci_cache->function_handler->op_array.fn_flags & ZEND_ACC_CLOSURE) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(fci_cache->function_handler)); } } #define sw_php_spl_object_hash(o) php_spl_object_hash(Z_OBJ_P(o)) //----------------------------------Misc API------------------------------------ static sw_inline int php_swoole_check_reactor() { if (SWOOLE_G(req_status) == PHP_SWOOLE_RSHUTDOWN_BEGIN) { return -1; } if (sw_unlikely(!sw_reactor())) { return php_swoole_reactor_init() == SW_OK ? 1 : -1; } else { return 0; } } static sw_inline char *php_swoole_url_encode(const char *value, size_t value_len, size_t *exten) { zend_string *str = php_url_encode(value, value_len); *exten = ZSTR_LEN(str); char *return_str = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); return return_str; } static sw_inline char *php_swoole_http_build_query(zval *zdata, size_t *length, smart_str *formstr) { if (HASH_OF(zdata)) { #if PHP_VERSION_ID < 80300 php_url_encode_hash_ex( HASH_OF(zdata), formstr, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, nullptr, (int) PHP_QUERY_RFC1738); #else php_url_encode_hash_ex(HASH_OF(zdata), formstr, NULL, 0, NULL, NULL, NULL, (int) PHP_QUERY_RFC1738); #endif } else { if (formstr->s) { smart_str_free(formstr); } return nullptr; } if (!formstr->s) { return nullptr; } smart_str_0(formstr); *length = formstr->s->len; return formstr->s->val; } static inline const char *php_swoole_get_last_error_message() { return PG(last_error_message) ? PG(last_error_message)->val : nullptr; } static inline const char *php_swoole_get_last_error_file() { return PG(last_error_file) ? PG(last_error_file)->val : "-"; } END_EXTERN_C() #endif /* PHP_SWOOLE_PRIVATE_H */ ================================================ FILE: ext-src/php_swoole_process.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Twosee | | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #include "swoole_process_pool.h" enum PipeType { PIPE_TYPE_NONE = 0, PIPE_TYPE_STREAM = 1, PIPE_TYPE_DGRAM = 2, }; void php_swoole_process_clean(); int php_swoole_process_start(swoole::Worker *process, zval *zobject); swoole::Worker *php_swoole_process_get_worker(const zval *zobject); void php_swoole_process_set_worker(const zval *zobject, swoole::Worker *worker, bool enable_coroutine, int pipe_type); swoole::ProcessPool *sw_process_pool(); ================================================ FILE: ext-src/php_swoole_server.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #include "swoole_server.h" #include #include #include //-------------------------------------------------------- enum php_swoole_server_callback_type { SW_SERVER_CB_onStart, // master SW_SERVER_CB_onBeforeShutdown, // master SW_SERVER_CB_onShutdown, // master SW_SERVER_CB_onWorkerStart, // worker(event & task) SW_SERVER_CB_onWorkerStop, // worker(event & task) SW_SERVER_CB_onBeforeReload, // manager SW_SERVER_CB_onAfterReload, // manager SW_SERVER_CB_onTask, // worker(task) SW_SERVER_CB_onFinish, // worker(event & task) SW_SERVER_CB_onWorkerExit, // worker(event) SW_SERVER_CB_onWorkerError, // manager SW_SERVER_CB_onManagerStart, // manager SW_SERVER_CB_onManagerStop, // manager SW_SERVER_CB_onPipeMessage, // worker(event & task) }; //-------------------------------------------------------- enum php_swoole_server_port_callback_type { SW_SERVER_CB_onConnect, // stream, worker(event) SW_SERVER_CB_onReceive, // stream, worker(event) SW_SERVER_CB_onClose, // stream, worker(event) SW_SERVER_CB_onPacket, // dgram, worker(event) SW_SERVER_CB_onRequest, // http, worker(event) SW_SERVER_CB_onHandshake, // websocket, worker(event) SW_SERVER_CB_onBeforeHandshakeResponse, // websocket, worker(event) SW_SERVER_CB_onOpen, // websocket, worker(event) SW_SERVER_CB_onMessage, // websocket, worker(event) SW_SERVER_CB_onDisconnect, // websocket (non websocket connection), worker(event) SW_SERVER_CB_onBufferFull, // worker(event) SW_SERVER_CB_onBufferEmpty, // worker(event) }; #define PHP_SWOOLE_SERVER_CALLBACK_NUM (SW_SERVER_CB_onPipeMessage + 1) #define PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM (SW_SERVER_CB_onBufferEmpty + 1) namespace swoole { struct ServerPortProperty; struct TaskCo; }; // namespace swoole zval *php_swoole_server_zval_ptr(swoole::Server *serv); swoole::ServerPortProperty *php_swoole_server_get_port_property(swoole::ListenPort *port); void php_swoole_server_set_port_property(swoole::ListenPort *port, swoole::ServerPortProperty *property); namespace swoole { struct ServerPortProperty { zend::Callable *callbacks[PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM]; Server *serv; ListenPort *port; zval *zsetting; }; struct ServerProperty { std::vector ports; std::vector user_processes; zend::Callable *callbacks[PHP_SWOOLE_SERVER_CALLBACK_NUM]; std::unordered_map task_callbacks; std::unordered_map task_coroutine_map; std::unordered_map *> send_coroutine_map; std::vector command_callbacks; }; struct ServerObject { Server *serv; ServerProperty *property; zval init_arguments; zend_object std; zend_class_entry *get_ce() const { return Z_OBJCE_P(php_swoole_server_zval_ptr(serv)); } bool isset_callback(ListenPort *port, int event_type) const { return (php_swoole_server_get_port_property(port)->callbacks[event_type] || php_swoole_server_get_port_property(serv->get_primary_port())->callbacks[event_type]); } bool isset_callback(int event_type) const { return property->callbacks[event_type] != nullptr; } zend::Callable *get_callback(int event_type) const { return property->callbacks[event_type]; } zend_bool is_websocket_server() const { return instanceof_function(get_ce(), swoole_websocket_server_ce); } zend_bool is_http_server() const { return instanceof_function(get_ce(), swoole_http_server_ce); } zend_bool is_redis_server() const { return instanceof_function(get_ce(), swoole_redis_server_ce); } void register_callback() const; void on_before_start(); void copy_setting(zval *zsetting) const; }; struct TaskCo { Coroutine *co; TaskId *list; uint32_t count; zval *result; }; void register_admin_server_commands(Server *serv); } // namespace swoole void php_swoole_server_register_callbacks(swServer *serv); zend::Callable *php_swoole_server_get_callback(swServer *serv, int server_fd, int event_type); int php_swoole_create_dir(const char *path, size_t length); void php_swoole_server_before_start(swServer *serv, zval *zobject); bool php_swoole_server_isset_callback(swServer *serv, swoole::ListenPort *port, int event_type); bool php_swoole_server_send_yield(swServer *serv, swoole::SessionId session_id, zend_string *sdata); void php_swoole_get_recv_data(swServer *serv, zval *zdata, swoole::RecvData *req); void php_swoole_server_onConnect(swServer *, swoole::DataHead *); int php_swoole_server_onReceive(swServer *, swoole::RecvData *); int php_swoole_http_server_onReceive(swServer *, swoole::RecvData *); void php_swoole_http_server_onClose(swServer *serv, swoole::DataHead *info); void php_swoole_http2_server_onClose(swServer *serv, swoole::SessionId session_id); int php_swoole_redis_server_onReceive(swServer *serv, swoole::RecvData *req); int php_swoole_server_onPacket(swServer *, swoole::RecvData *); void php_swoole_server_onClose(swServer *, swoole::DataHead *); void php_swoole_server_onBufferFull(swServer *, swoole::DataHead *); void php_swoole_server_onBufferEmpty(swServer *, swoole::DataHead *); swServer *php_swoole_server_get_and_check_server(zval *zobject); void php_swoole_server_port_deref(zend_object *object); void php_swoole_server_set_websocket_option(swoole::ListenPort *port, zend_array *vht); swoole::ServerObject *php_swoole_server_get_zend_object(swoole::Server *serv); ================================================ FILE: ext-src/php_swoole_sqlite.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #ifndef SWOOLE_SRC_PHP_SWOOLE_SQLITE_H #define SWOOLE_SRC_PHP_SWOOLE_SQLITE_H #include "php_swoole.h" #ifdef SW_USE_SQLITE BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" #include "thirdparty/pdo_sqlite/php_pdo_sqlite_int.h" extern const pdo_driver_t swoole_pdo_sqlite_driver; void swoole_sqlite_set_blocking(bool blocking); int swoole_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs); int swoole_sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); int swoole_sqlite3_exec( sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg); int swoole_sqlite3_close(sqlite3 *db); int swoole_sqlite3_close_v2(sqlite3 *db); int swoole_sqlite3_step(sqlite3_stmt *stmt); #ifdef SW_USE_SQLITE_HOOK #define sqlite3_open_v2 swoole_sqlite3_open_v2 #define sqlite3_prepare_v2 swoole_sqlite3_prepare_v2 #define sqlite3_exec swoole_sqlite3_exec #define sqlite3_close swoole_sqlite3_close #define sqlite3_close_v2 swoole_sqlite3_close_v2 #define sqlite3_step swoole_sqlite3_step #endif END_EXTERN_C() #endif #endif ================================================ FILE: ext-src/php_swoole_ssh2.h ================================================ #pragma once #include "php_swoole_cxx.h" #include "php_swoole_ssh2_def.h" #include #include #include typedef struct _php_ssh2_session_data { /* Userspace callback functions */ zval *ignore_cb; zval *debug_cb; zval *macerror_cb; zval *disconnect_cb; SocketImpl *socket; } php_ssh2_session_data; static inline swoole::EventType ssh2_get_event_type(LIBSSH2_SESSION *session) { int dir = libssh2_session_block_directions(session); if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) { return SW_EVENT_WRITE; } else { return SW_EVENT_READ; } } static inline SocketImpl *ssh2_get_socket(LIBSSH2_SESSION *session) { auto session_data = (php_ssh2_session_data **) libssh2_session_abstract(session); return (*session_data)->socket; } static inline void ssh2_set_socket_timeout(LIBSSH2_SESSION *session, int timeout_ms) { auto sock = ssh2_get_socket(session); sock->set_timeout(timeout_ms / 1000, SW_TIMEOUT_ALL); } class ResourceGuard { zval zres_; public: ResourceGuard(zval *zres) { zval_addref_p(zres); zres_ = *zres; } ~ResourceGuard() { zval_ptr_dtor(&zres_); } }; static inline int ssh2_async_call(LIBSSH2_SESSION *session, const std::function &fn) { auto event = ssh2_get_event_type(session); auto socket = ssh2_get_socket(session); socket->check_bound_co(SW_EVENT_READ); socket->check_bound_co(SW_EVENT_WRITE); int rc = 0; while (1) { rc = fn(); if (rc == LIBSSH2_ERROR_EAGAIN) { if (!socket->poll(event)) { return LIBSSH2_ERROR_SOCKET_NONE; } continue; } break; } return rc; } template static inline T *ssh2_async_call_ex(LIBSSH2_SESSION *session, const std::function &fn) { auto event = ssh2_get_event_type(session); auto socket = ssh2_get_socket(session); socket->check_bound_co(SW_EVENT_READ); socket->check_bound_co(SW_EVENT_WRITE); T *handle; while (1) { handle = fn(); if (handle) { return handle; } if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN && socket->poll(event)) { continue; } break; } return nullptr; } ================================================ FILE: ext-src/php_swoole_ssh2_def.h ================================================ #include "php.h" BEGIN_EXTERN_C() ZEND_FUNCTION(ssh2_connect); ZEND_FUNCTION(ssh2_disconnect); ZEND_FUNCTION(ssh2_methods_negotiated); ZEND_FUNCTION(ssh2_fingerprint); ZEND_FUNCTION(ssh2_auth_none); ZEND_FUNCTION(ssh2_auth_password); ZEND_FUNCTION(ssh2_auth_pubkey_file); ZEND_FUNCTION(ssh2_auth_pubkey); ZEND_FUNCTION(ssh2_auth_hostbased_file); ZEND_FUNCTION(ssh2_forward_listen); ZEND_FUNCTION(ssh2_forward_accept); ZEND_FUNCTION(ssh2_shell); ZEND_FUNCTION(ssh2_shell_resize); ZEND_FUNCTION(ssh2_exec); ZEND_FUNCTION(ssh2_tunnel); ZEND_FUNCTION(ssh2_scp_recv); ZEND_FUNCTION(ssh2_scp_send); ZEND_FUNCTION(ssh2_fetch_stream); ZEND_FUNCTION(ssh2_send_eof); ZEND_FUNCTION(ssh2_sftp); ZEND_FUNCTION(ssh2_sftp_rename); ZEND_FUNCTION(ssh2_sftp_unlink); ZEND_FUNCTION(ssh2_sftp_mkdir); ZEND_FUNCTION(ssh2_sftp_rmdir); ZEND_FUNCTION(ssh2_sftp_chmod); ZEND_FUNCTION(ssh2_sftp_stat); ZEND_FUNCTION(ssh2_sftp_lstat); ZEND_FUNCTION(ssh2_sftp_symlink); ZEND_FUNCTION(ssh2_sftp_readlink); ZEND_FUNCTION(ssh2_sftp_realpath); ZEND_FUNCTION(ssh2_publickey_init); ZEND_FUNCTION(ssh2_publickey_add); ZEND_FUNCTION(ssh2_publickey_remove); ZEND_FUNCTION(ssh2_publickey_list); ZEND_FUNCTION(ssh2_auth_agent); END_EXTERN_C() int php_swoole_ssh2_mshutdown(); int php_swoole_ssh2_minit(int module_number); void php_swoole_ssh2_minfo(); ================================================ FILE: ext-src/php_swoole_ssh2_hook.h ================================================ #define SSH2_ASYNC_CALL(session, libssh2_func, ...) \ ssh2_async_call(session, [&](void) { return libssh2_func(__VA_ARGS__); }) #define SSH2_ASYNC_CALL_EX(T, session, libssh2_func, ...) \ ssh2_async_call_ex(session, [&](void) -> T * { return libssh2_func(__VA_ARGS__); }) #define libssh2_session_handshake(session, sockfd) SSH2_ASYNC_CALL(session, libssh2_session_handshake, session, sockfd) #undef libssh2_session_disconnect #define libssh2_session_disconnect(session, description) \ SSH2_ASYNC_CALL(session, libssh2_session_disconnect_ex, (session), SSH_DISCONNECT_BY_APPLICATION, (description), "") #undef libssh2_channel_open_session #define libssh2_channel_open_session(session) \ SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, \ session, \ libssh2_channel_open_ex, \ (session), \ "session", \ sizeof("session") - 1, \ LIBSSH2_CHANNEL_WINDOW_DEFAULT, \ LIBSSH2_CHANNEL_PACKET_DEFAULT, \ NULL, \ 0) #define libssh2_channel_setenv_ex(channel, name, name_len, value, value_len) \ SSH2_ASYNC_CALL(session, libssh2_channel_setenv_ex, channel, name, name_len, value, value_len) #define libssh2_channel_request_pty_ex(channel, term, term_len, modes, modes_len, width, height, width_px, height_px) \ SSH2_ASYNC_CALL(session, \ libssh2_channel_request_pty_ex, \ channel, \ term, \ term_len, \ modes, \ modes_len, \ width, \ height, \ width_px, \ height_px) #undef libssh2_channel_shell #define libssh2_channel_shell(channel) \ SSH2_ASYNC_CALL(session, libssh2_channel_process_startup, channel, "shell", sizeof("shell") - 1, NULL, 0) #undef libssh2_channel_exec #define libssh2_channel_exec(channel, command) \ SSH2_ASYNC_CALL(session, \ libssh2_channel_process_startup, \ channel, \ "exec", \ sizeof("exec") - 1, \ (command), \ (unsigned int) strlen(command)) #define libssh2_channel_flush_ex(channel, streamid) \ SSH2_ASYNC_CALL(session, libssh2_channel_flush_ex, (channel), (streamid)) #define libssh2_channel_read_ex(channel, streamid, buf, len) \ SSH2_ASYNC_CALL(session, libssh2_channel_read_ex, channel, streamid, buf, len) #undef libssh2_channel_write_ex #define libssh2_channel_write_ex(channel, streamid, buf, len) \ SSH2_ASYNC_CALL(session, libssh2_channel_write_ex, channel, streamid, buf, len) #undef libssh2_channel_read #define libssh2_channel_read(channel, buf, buflen) libssh2_channel_read_ex((channel), 0, (buf), (buflen)) #undef libssh2_channel_write #define libssh2_channel_write(channel, buf, buflen) libssh2_channel_write_ex((channel), 0, (buf), (buflen)) #undef libssh2_channel_eof #define libssh2_channel_eof(channel) SSH2_ASYNC_CALL(session, libssh2_channel_eof, channel) #undef libssh2_channel_close #define libssh2_channel_close(channel) SSH2_ASYNC_CALL(session, libssh2_channel_close, channel) #undef libssh2_channel_send_eof #define libssh2_channel_send_eof(channel) SSH2_ASYNC_CALL(session, libssh2_channel_send_eof, channel) #undef libssh2_channel_get_exit_status #define libssh2_channel_get_exit_status(channel) SSH2_ASYNC_CALL(session, libssh2_channel_get_exit_status, channel) #undef libssh2_channel_request_pty_size_ex #define libssh2_channel_request_pty_size_ex(channel, width, height, width_px, height_px) \ SSH2_ASYNC_CALL(session, libssh2_channel_request_pty_size_ex, channel, width, height, width_px, height_px) #undef libssh2_channel_forward_listen_ex #define libssh2_channel_forward_listen_ex(session, host, port, addr, num_connections) \ SSH2_ASYNC_CALL_EX( \ LIBSSH2_LISTENER, session, libssh2_channel_forward_listen_ex, session, host, port, addr, num_connections) #undef libssh2_channel_forward_accept #define libssh2_channel_forward_accept(listener) \ SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_channel_forward_accept, listener) #undef libssh2_channel_forward_cancel #define libssh2_channel_forward_cancel(listener) SSH2_ASYNC_CALL(session, libssh2_channel_forward_cancel, listener) #undef libssh2_channel_direct_tcpip #define libssh2_channel_direct_tcpip(session, host, port) \ SSH2_ASYNC_CALL_EX( \ LIBSSH2_CHANNEL, session, libssh2_channel_direct_tcpip_ex, (session), (host), (port), "127.0.0.1", 22) #undef libssh2_sftp_fstat #define libssh2_sftp_fstat(handle, attrs) SSH2_ASYNC_CALL(session, libssh2_sftp_fstat_ex, handle, attrs, 0) #define libssh2_sftp_stat_ex(sftp, path, path_len, stat_type, attrs) \ SSH2_ASYNC_CALL(session, libssh2_sftp_stat_ex, sftp, path, path_len, stat_type, attrs) #define libssh2_sftp_symlink_ex(sftp, path, path_len, target, target_len, link_type) \ SSH2_ASYNC_CALL(session, libssh2_sftp_symlink_ex, sftp, path, path_len, target, target_len, link_type) #undef libssh2_sftp_open #define libssh2_sftp_open(sftp, filename, flags, mode) \ SSH2_ASYNC_CALL_EX(LIBSSH2_SFTP_HANDLE, \ session, \ libssh2_sftp_open_ex, \ (sftp), \ (filename), \ strlen(filename), \ (flags), \ (mode), \ LIBSSH2_SFTP_OPENFILE) #undef libssh2_sftp_opendir #define libssh2_sftp_opendir(sftp, path) \ SSH2_ASYNC_CALL_EX( \ LIBSSH2_SFTP_HANDLE, session, libssh2_sftp_open_ex, (sftp), (path), strlen(path), 0, 0, LIBSSH2_SFTP_OPENDIR) #undef libssh2_sftp_readdir #define libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs) \ SSH2_ASYNC_CALL(session, libssh2_sftp_readdir_ex, (handle), (buffer), (buffer_maxlen), NULL, 0, (attrs)) #undef libssh2_sftp_tell #define libssh2_sftp_tell(handle) SSH2_ASYNC_CALL(session, libssh2_sftp_tell, handle) #undef libssh2_sftp_read #define libssh2_sftp_read(handle, buffer, count) \ SSH2_ASYNC_CALL(session, libssh2_sftp_read, (handle), (buffer), (count)) #undef libssh2_sftp_write #define libssh2_sftp_write(handle, buffer, count) \ SSH2_ASYNC_CALL(session, libssh2_sftp_write, (handle), (buffer), (count)) #define libssh2_sftp_init(session) SSH2_ASYNC_CALL_EX(LIBSSH2_SFTP, session, libssh2_sftp_init, session) #undef libssh2_scp_recv #define libssh2_scp_recv(session, path, stat) \ SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_scp_recv, session, path, stat) #undef libssh2_scp_send_ex #define libssh2_scp_send_ex(session, path, mode, size, atime, mtime) \ SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_scp_send_ex, session, path, mode, size, atime, mtime) #undef libssh2_sftp_close #define libssh2_sftp_close(handle) SSH2_ASYNC_CALL(session, libssh2_sftp_close_handle, handle) #define libssh2_sftp_unlink_ex(sftp, filename, filename_len) \ SSH2_ASYNC_CALL(session, libssh2_sftp_unlink_ex, (sftp), (filename), filename_len) #undef libssh2_sftp_unlink #define libssh2_sftp_unlink(sftp, filename) libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) #define libssh2_sftp_rename_ex(sftp, source_filename, srouce_filename_len, dest_filename, dest_filename_len, flags) \ SSH2_ASYNC_CALL(session, \ libssh2_sftp_rename_ex, \ sftp, \ source_filename, \ srouce_filename_len, \ dest_filename, \ dest_filename_len, \ flags) #undef libssh2_sftp_rename #define libssh2_sftp_rename(sftp, sourcefile, destfile) \ libssh2_sftp_rename_ex((sftp), \ (sourcefile), \ strlen(sourcefile), \ (destfile), \ strlen(destfile), \ LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE) #define libssh2_sftp_mkdir_ex(sftp, path, path_len, mode) \ SSH2_ASYNC_CALL(session, libssh2_sftp_mkdir_ex, (sftp), (path), path_len, (mode)) #undef libssh2_sftp_mkdir #define libssh2_sftp_mkdir(sftp, path, mode) libssh2_sftp_mkdir_ex((sftp), (path), strlen(path), (mode)) #define libssh2_sftp_rmdir_ex(sftp, path, path_len) \ SSH2_ASYNC_CALL(session, libssh2_sftp_rmdir_ex, (sftp), (path), path_len) #undef libssh2_sftp_rmdir #define libssh2_sftp_rmdir(sftp, path) libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) /* Agent related functions */ #undef libssh2_agent_connect #define libssh2_agent_connect(agent) SSH2_ASYNC_CALL(session, libssh2_agent_connect, agent) #undef libssh2_agent_list_identities #define libssh2_agent_list_identities(agent) SSH2_ASYNC_CALL(session, libssh2_agent_list_identities, agent) #undef libssh2_agent_get_identity #define libssh2_agent_get_identity(agent, identity, prev_identity) \ SSH2_ASYNC_CALL(session, libssh2_agent_get_identity, agent, identity, prev_identity) #undef libssh2_agent_userauth #define libssh2_agent_userauth(agent, username, identity) \ SSH2_ASYNC_CALL(session, libssh2_agent_userauth, agent, username, identity) #undef libssh2_agent_disconnect #define libssh2_agent_disconnect(agent) SSH2_ASYNC_CALL(session, libssh2_agent_disconnect, agent) /* libssh2_agent_free is just memory operation, no network IO, so don't async it */ /* User authentication functions */ #undef libssh2_userauth_list #define libssh2_userauth_list(session, username, username_len) \ SSH2_ASYNC_CALL_EX(char, session, libssh2_userauth_list, session, username, username_len) #undef libssh2_userauth_password_ex #define libssh2_userauth_password_ex(session, username, username_len, password, password_len, change_cb) \ SSH2_ASYNC_CALL( \ session, libssh2_userauth_password_ex, session, username, username_len, password, password_len, change_cb) #undef libssh2_userauth_publickey_fromfile_ex #define libssh2_userauth_publickey_fromfile_ex(session, username, username_len, publickey, privatekey, passphrase) \ SSH2_ASYNC_CALL(session, \ libssh2_userauth_publickey_fromfile_ex, \ (session), \ (username), \ (username_len), \ (publickey), \ (privatekey), \ (passphrase)) #undef libssh2_userauth_publickey_fromfile #define libssh2_userauth_publickey_fromfile(session, username, publickey, privatekey, passphrase) \ libssh2_userauth_publickey_fromfile_ex( \ (session), (username), (unsigned int) strlen(username), (publickey), (privatekey), (passphrase)) #undef libssh2_userauth_publickey_frommemory #define libssh2_userauth_publickey_frommemory( \ session, username, username_len, pubkeydata, pubkeydata_len, privkeydata, privkeydata_len, passphrase) \ SSH2_ASYNC_CALL(session, \ libssh2_userauth_publickey_frommemory, \ session, \ username, \ username_len, \ pubkeydata, \ pubkeydata_len, \ privkeydata, \ privkeydata_len, \ passphrase) #undef libssh2_userauth_hostbased_fromfile_ex #define libssh2_userauth_hostbased_fromfile_ex(session, \ username, \ username_len, \ pubkeyfile, \ privkeyfile, \ passphrase, \ hostname, \ hostname_len, \ local_username, \ local_username_len) \ SSH2_ASYNC_CALL(session, \ libssh2_userauth_hostbased_fromfile_ex, \ session, \ username, \ username_len, \ pubkeyfile, \ privkeyfile, \ passphrase, \ hostname, \ hostname_len, \ local_username, \ local_username_len) #undef libssh2_userauth_keyboard_interactive #define libssh2_userauth_keyboard_interactive(session, username, response_callback) \ SSH2_ASYNC_CALL(session, \ libssh2_userauth_keyboard_interactive_ex, \ (session), \ (username), \ (unsigned int) strlen(username), \ (response_callback)) #undef libssh2_userauth_authenticated #define libssh2_userauth_authenticated(session) SSH2_ASYNC_CALL(session, libssh2_userauth_authenticated, session) ================================================ FILE: ext-src/php_swoole_stdext.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" SW_EXTERN_C_BEGIN PHP_FUNCTION(swoole_call_array_method); PHP_FUNCTION(swoole_call_string_method); PHP_FUNCTION(swoole_call_stream_method); PHP_FUNCTION(swoole_array_search); PHP_FUNCTION(swoole_array_contains); PHP_FUNCTION(swoole_array_join); PHP_FUNCTION(swoole_array_key_exists); PHP_FUNCTION(swoole_array_map); PHP_FUNCTION(swoole_array_is_typed); PHP_FUNCTION(swoole_array_is_empty); PHP_FUNCTION(swoole_str_split); PHP_FUNCTION(swoole_str_is_empty); PHP_FUNCTION(swoole_str_match); PHP_FUNCTION(swoole_str_match_all); PHP_FUNCTION(swoole_str_json_decode); PHP_FUNCTION(swoole_str_json_decode_to_object); PHP_FUNCTION(swoole_parse_str); PHP_FUNCTION(swoole_hash); PHP_FUNCTION(swoole_typed_array); PHP_FUNCTION(swoole_str_replace); PHP_FUNCTION(swoole_str_ireplace); PHP_FUNCTION(swoole_array_replace_str); PHP_FUNCTION(swoole_array_ireplace_str); SW_EXTERN_C_END ================================================ FILE: ext-src/php_swoole_thread.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Twosee | | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #ifdef SW_THREAD #include "swoole_thread.h" typedef uint32_t ThreadResourceId; class ThreadResource; class ZendArray; extern zend_class_entry *swoole_thread_ce; extern zend_class_entry *swoole_thread_error_ce; extern zend_class_entry *swoole_thread_arraylist_ce; extern zend_class_entry *swoole_thread_atomic_ce; extern zend_class_entry *swoole_thread_atomic_long_ce; extern zend_class_entry *swoole_thread_barrier_ce; extern zend_class_entry *swoole_thread_lock_ce; extern zend_class_entry *swoole_thread_map_ce; extern zend_class_entry *swoole_thread_queue_ce; void php_swoole_thread_start(std::shared_ptr thread, zend_string *file, ZendArray *argv); void php_swoole_thread_bailout(); ThreadResource *php_swoole_thread_arraylist_cast(const zval *zobject); ThreadResource *php_swoole_thread_map_cast(const zval *zobject); ThreadResource *php_swoole_thread_queue_cast(const zval *zobject); ThreadResource *php_swoole_thread_lock_cast(const zval *zobject); ThreadResource *php_swoole_thread_atomic_cast(const zval *zobject); ThreadResource *php_swoole_thread_atomic_long_cast(const zval *zobject); ThreadResource *php_swoole_thread_barrier_cast(const zval *zobject); void php_swoole_thread_arraylist_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_map_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_queue_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_lock_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_atomic_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_atomic_long_create(zval *return_value, ThreadResource *resource); void php_swoole_thread_barrier_create(zval *return_value, ThreadResource *resource); int php_swoole_thread_stream_cast(zval *zstream); void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd); int php_swoole_thread_co_socket_cast(zval *zstream, swSocketType *type); void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type); #define EMSG_NO_RESOURCE "resource not found" #define ECODE_NO_RESOURCE (-2) enum { IS_ARRAYLIST = 80, IS_QUEUE = 81, IS_LOCK = 82, IS_MAP = 83, IS_BARRIER = 84, IS_ATOMIC = 85, IS_ATOMIC_LONG = 86, IS_PHP_SOCKET = 96, IS_CO_SOCKET = 97, IS_STREAM_SOCKET = 98, IS_SERIALIZED_OBJECT = 99, }; class ThreadResource { sw_atomic_t ref_count; public: ThreadResource() { ref_count = 1; } void add_ref() { sw_atomic_add_fetch(&ref_count, 1); } void del_ref() { if (sw_atomic_sub_fetch(&ref_count, 1) == 0) { delete this; } } protected: virtual ~ThreadResource() = default; }; struct ArrayItem { uint32_t type = IS_UNDEF; zend_string *key = nullptr; union { zend_string *str; zend_long lval; double dval; struct { int fd; swSocketType type; } socket; zend_string *serialized_object; ThreadResource *resource; } value; explicit ArrayItem(zval *zvalue) { value = {}; store(zvalue); } void setKey(zend::String &_key) { key = zend_string_init(_key.val(), _key.len(), true); } void setKey(const zend_string *_key) { key = zend_string_init(ZSTR_VAL(_key), ZSTR_LEN(_key), true); } void store(zval *zvalue); void fetch(zval *return_value) const; void release(); bool equals(const zval *zvalue) const; static int compare(Bucket *a, Bucket *b); ~ArrayItem() { if (value.str) { release(); } if (key) { zend_string_release(key); } } }; class ZendArray : public ThreadResource { protected: swoole::RWLock lock_; zend_array ht; static void item_dtor(zval *pDest) { auto item = static_cast(Z_PTR_P(pDest)); delete item; } public: ZendArray() : lock_(false) { zend_hash_init(&ht, 0, NULL, item_dtor, 1); } ~ZendArray() override { zend_hash_destroy(&ht); } void clean() { lock_.lock(); zend_hash_clean(&ht); lock_.unlock(); } void append(zval *zvalue); void add(const zend_string *skey, zval *zvalue) { auto item = new ArrayItem(zvalue); item->setKey(skey); zend_hash_add_ptr(&ht, item->key, item); } void add(zend::String &skey, zval *zvalue) { auto item = new ArrayItem(zvalue); item->setKey(skey); zend_hash_add_ptr(&ht, item->key, item); } void add(zend_long index, zval *zvalue) { auto item = new ArrayItem(zvalue); zend_hash_index_add_ptr(&ht, index, item); } bool index_exists(zend_long index) const { return index < (zend_long) zend_hash_num_elements(&ht); } bool strkey_exists(zend::String &skey) const { return zend_hash_find_ptr(&ht, skey.get()) != nullptr; } bool intkey_exists(zend_long index) const { return zend_hash_index_find_ptr(&ht, index) != nullptr; } void strkey_offsetGet(zval *zkey, zval *return_value) { zend::String skey(zkey); lock_.lock_rd(); auto item = static_cast(zend_hash_find_ptr(&ht, skey.get())); if (item) { item->fetch(return_value); } lock_.unlock(); } void strkey_offsetExists(zval *zkey, zval *return_value) { zend::String skey(zkey); lock_.lock_rd(); RETVAL_BOOL(strkey_exists(skey)); lock_.unlock(); } void strkey_offsetUnset(zval *zkey) { zend::String skey(zkey); lock_.lock(); zend_hash_del(&ht, skey.get()); lock_.unlock(); } void strkey_offsetSet(zval *zkey, zval *zvalue) { zend::String skey(zkey); auto item = new ArrayItem(zvalue); item->setKey(skey); lock_.lock(); zend_hash_update_ptr(&ht, item->key, item); lock_.unlock(); } void strkey_incr(zval *zkey, zval *zvalue, zval *return_value); void intkey_incr(zend_long index, zval *zvalue, zval *return_value); void strkey_decr(zval *zkey, zval *zvalue, zval *return_value); void intkey_decr(zend_long index, zval *zvalue, zval *return_value); bool index_incr(zval *zkey, zval *zvalue, zval *return_value); bool index_decr(zval *zkey, zval *zvalue, zval *return_value); void strkey_add(zval *zkey, zval *zvalue, zval *return_value); void intkey_add(zend_long index, zval *zvalue, zval *return_value); void strkey_update(zval *zkey, zval *zvalue, zval *return_value); void intkey_update(zend_long index, zval *zvalue, zval *return_value); void count(zval *return_value) { lock_.lock_rd(); RETVAL_LONG(zend_hash_num_elements(&ht)); lock_.unlock(); } void keys(zval *return_value); void values(zval *return_value); void to_array(zval *return_value); void find(const zval *search, zval *return_value); void sort(bool renumber); void intkey_offsetGet(zend_long index, zval *return_value) { lock_.lock_rd(); auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); if (item) { item->fetch(return_value); } lock_.unlock(); } void intkey_offsetExists(zend_long index, zval *return_value) { lock_.lock_rd(); RETVAL_BOOL(intkey_exists(index)); lock_.unlock(); } void intkey_offsetUnset(zend_long index) { lock_.lock(); zend_hash_index_del(&ht, index); lock_.unlock(); } void intkey_offsetSet(zend_long index, zval *zvalue) { auto item = new ArrayItem(zvalue); lock_.lock(); zend_hash_index_update_ptr(&ht, index, item); lock_.unlock(); } bool index_offsetGet(zend_long index, zval *return_value); bool index_offsetSet(zend_long index, zval *zvalue); void index_offsetUnset(zend_long index); void index_offsetExists(zend_long index, zval *return_value); static void incr_update(ArrayItem *item, zval *zvalue, zval *return_value); static ArrayItem *incr_create(zval *zvalue, zval *return_value); static ZendArray *from(zend_array *ht); }; #define INIT_ARRAY_INCR_PARAMS \ zval *zkey; \ zval zvalue_, *zvalue = NULL; \ \ ZEND_PARSE_PARAMETERS_START(1, 2) \ Z_PARAM_ZVAL(zkey) \ Z_PARAM_OPTIONAL \ Z_PARAM_ZVAL(zvalue) \ ZEND_PARSE_PARAMETERS_END(); \ \ if (!zvalue) { \ zvalue = &zvalue_; \ ZVAL_LONG(zvalue, 1); \ } #endif ================================================ FILE: ext-src/php_swoole_websocket.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #include "swoole_websocket.h" #define SW_WEBSOCKET_DEFAULT_BUFFER 4096 namespace swoole { namespace websocket { void apply_setting(WebSocketSettings &settings, zend_array *vht, bool in_server); void recv_frame(const WebSocketSettings &settings, std::shared_ptr &frame_buffer, SocketImpl *sock, zval *return_value, double timeout); ssize_t send_frame(const WebSocketSettings &settings, SocketImpl *sock, uchar opcode, uchar flags, const char *payload, size_t payload_length); void construct_frame(zval *zframe, zend_long opcode, zval *zpayload, uint8_t flags); #ifdef SW_HAVE_ZLIB bool message_compress(String *buffer, const char *data, size_t length, int level); bool message_uncompress(String *buffer, const char *in, size_t in_len); #endif struct FrameObject { uint8_t opcode; uint8_t flags; uint16_t code; zval *data; explicit FrameObject(zval *data, zend_long _opcode = 0, zend_long _flags = 0, zend_long _code = 0); size_t get_data_size() const { return (data && ZVAL_IS_STRING(data)) ? Z_STRLEN_P(data) : 0; } bool pack(String *buffer); static bool uncompress(zval *zpayload, const char *data, size_t length); }; } // namespace websocket } // namespace swoole ================================================ FILE: ext-src/stubs/php_swoole.stub.php ================================================ |null * @refcount 1 */ function ftp_raw(FTP\Connection $ftp, string $command): ?array {} function ftp_mkdir(FTP\Connection $ftp, string $directory): string|false {} function ftp_rmdir(FTP\Connection $ftp, string $directory): bool {} function ftp_chmod(FTP\Connection $ftp, int $permissions, string $filename): int|false {} /** @param string $response */ function ftp_alloc(FTP\Connection $ftp, int $size, &$response = null): bool {} /** * @return array|false * @refcount 1 */ function ftp_nlist(FTP\Connection $ftp, string $directory): array|false {} /** * @return array|false * @refcount 1 */ function ftp_rawlist(FTP\Connection $ftp, string $directory, bool $recursive = false): array|false {} /** * @return array|false * @refcount 1 */ function ftp_mlsd(FTP\Connection $ftp, string $directory): array|false {} function ftp_systype(FTP\Connection $ftp): string|false {} /** @param resource $stream */ function ftp_fget(FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} /** @param resource $stream */ function ftp_nb_fget(FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): int {} function ftp_pasv(FTP\Connection $ftp, bool $enable): bool {} function ftp_get(FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} function ftp_nb_get(FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): int|false {} function ftp_nb_continue(FTP\Connection $ftp): int {} /** @param resource $stream */ function ftp_fput(FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): bool {} /** @param resource $stream */ function ftp_nb_fput(FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): int {} function ftp_put(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} function ftp_append(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY): bool {} function ftp_nb_put(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): int|false {} function ftp_size(FTP\Connection $ftp, string $filename): int {} function ftp_mdtm(FTP\Connection $ftp, string $filename): int {} function ftp_rename(FTP\Connection $ftp, string $from, string $to): bool {} function ftp_delete(FTP\Connection $ftp, string $filename): bool {} function ftp_site(FTP\Connection $ftp, string $command): bool {} function ftp_close(FTP\Connection $ftp): bool {} /** @alias ftp_close */ function ftp_quit(FTP\Connection $ftp): bool {} /** @param int|bool $value */ function ftp_set_option(FTP\Connection $ftp, int $option, $value): bool {} function ftp_get_option(FTP\Connection $ftp, int $option): int|bool {} } namespace FTP { /** * @strict-properties * @not-serializable */ final class Connection { } } ================================================ FILE: ext-src/stubs/php_swoole_ftp_arginfo.h ================================================ /* This is a generated file, edit the .stub.php file instead. * Stub hash: 4884776ce0fa7815e35882ffc34cbbccd0ec21ad */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ftp_connect, 0, 1, FTP\\Connection, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "21") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "90") ZEND_END_ARG_INFO() #if defined(SW_HAVE_FTP_SSL) ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ftp_ssl_connect, 0, 1, FTP\\Connection, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "21") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "90") ZEND_END_ARG_INFO() #endif ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_login, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, username, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_pwd, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_cdup, 0, 1, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_chdir, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_exec, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, command, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_raw, 0, 2, IS_ARRAY, 1) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, command, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_mkdir, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) ZEND_END_ARG_INFO() #define arginfo_ftp_rmdir arginfo_ftp_chdir ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_chmod, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, permissions, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_alloc, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, response, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nlist, 0, 2, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_rawlist, 0, 2, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, recursive, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() #define arginfo_ftp_mlsd arginfo_ftp_nlist #define arginfo_ftp_systype arginfo_ftp_pwd ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_fget, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_INFO(0, stream) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_fget, 0, 3, IS_LONG, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_INFO(0, stream) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_pasv, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_get, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nb_get, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_continue, 0, 1, IS_LONG, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_fput, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_INFO(0, stream) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_fput, 0, 3, IS_LONG, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_INFO(0, stream) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_put, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_append, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nb_put, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_size, 0, 2, IS_LONG, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) ZEND_END_ARG_INFO() #define arginfo_ftp_mdtm arginfo_ftp_size ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_rename, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, to, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_delete, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) ZEND_END_ARG_INFO() #define arginfo_ftp_site arginfo_ftp_exec #define arginfo_ftp_close arginfo_ftp_cdup #define arginfo_ftp_quit arginfo_ftp_cdup ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_set_option, 0, 3, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_get_option, 0, 2, MAY_BE_LONG|MAY_BE_BOOL) ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) ZEND_END_ARG_INFO() ================================================ FILE: ext-src/stubs/php_swoole_http2_client_coro.stub.php ================================================ | +----------------------------------------------------------------------+ */ #include "php_swoole_server.h" #include "swoole_process_pool.h" #include "php_swoole_http.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_ex_arginfo.h" END_EXTERN_C() #include #include #include "nlohmann/json.hpp" using json = nlohmann::json; namespace swoole { #ifdef TCP_INFO static json get_socket_info(int fd); #endif static std::string handle_get_all_unix_sockets(Server *_server, const std::string &msg) { auto _result = json::parse(msg); if (!_result.is_object() || _result.find("type") == _result.end()) { json return_value{ {"data", "require parameter type"}, {"code", 4003}, }; return return_value.dump(); } std::string _type = _result["type"]; Worker *workers; uint32_t worker_num; if (_type == "event") { workers = _server->get_event_worker_pool()->workers; worker_num = _server->worker_num; } else { workers = _server->get_task_worker_pool()->workers; worker_num = _server->task_worker_num; } json sockets = json::array(); SW_LOOP_N(worker_num) { auto master_socket = workers[i].pipe_object->get_socket(true); json master_socket_info = json::object({ {"fd", master_socket->fd}, {"events", master_socket->events}, {"total_recv_bytes", master_socket->total_recv_bytes}, {"total_send_bytes", master_socket->total_send_bytes}, {"out_buffer_size", master_socket->get_out_buffer_length()}, }); sockets.push_back(master_socket_info); auto worker_socket = workers[i].pipe_object->get_socket(false); json worker_socket_info = json::object({ {"fd", worker_socket->fd}, {"events", worker_socket->events}, {"total_recv_bytes", worker_socket->total_recv_bytes}, {"total_send_bytes", worker_socket->total_send_bytes}, {"out_buffer_size", worker_socket->get_out_buffer_length()}, }); sockets.push_back(worker_socket_info); } json return_value{ {"data", sockets}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_all_sockets(Server *, const std::string &msg) { if (sw_reactor() == nullptr) { json return_value{ {"data", "No event loop created"}, {"code", 4004}, }; return return_value.dump(); } json sockets = json::array(); sw_reactor()->foreach_socket([&sockets](int fd, network::Socket *socket) { if (socket->socket_type > SW_SOCK_UNIX_DGRAM || socket->socket_type < SW_SOCK_TCP) { #ifdef SO_DOMAIN struct stat fdstat; if (fstat(fd, &fdstat) == -1) { return; } mode_t type = fdstat.st_mode & S_IFMT; if (type == S_IFSOCK) { int domain; if (socket->get_option(SOL_SOCKET, SO_DOMAIN, &domain) < 0) { return; } int _type; if (socket->get_option(SOL_SOCKET, SO_TYPE, &_type) < 0) { return; } socket->get_name(); } #else return; #endif } json info = json::object({ {"fd", socket->get_fd()}, {"address", socket->get_addr()}, {"port", socket->get_port()}, {"events", socket->events}, {"socket_type", socket->socket_type}, {"fd_type", socket->fd_type}, {"total_recv_bytes", socket->total_recv_bytes}, {"total_send_bytes", socket->total_send_bytes}, {"out_buffer_size", socket->get_out_buffer_length()}, }); sockets.push_back(info); }); json return_value{ {"data", sockets}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_all_commands(Server *serv, const std::string &msg) { json command_list = json::array(); for (auto &kv : serv->commands) { json info = json::object({ {"id", kv.second.id}, {"name", kv.second.name}, {"accepted_process_types", kv.second.accepted_process_types}, }); command_list.push_back(info); }; json return_value{ {"data", command_list}, {"code", 0}, }; return return_value.dump(); } #ifdef TCP_INFO static json get_socket_info(int fd) { tcp_info info; socklen_t len = sizeof(info); if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len) < 0) { json return_value{ {"data", "failed to getsockopt(TCP_INFO) for socket"}, {"code", 5001}, }; return return_value.dump(); } auto info_map = sw_socket_parse_tcp_info(&info); json jinfo; for (const auto &iter : info_map) { jinfo[iter.first] = iter.second; } return jinfo; } #endif static json get_connection_info(Server *serv, Connection *conn) { auto server_socket = serv->get_port_by_server_fd(conn->server_fd)->socket; json info = json::object({ {"session_id", conn->session_id}, {"reactor_id", conn->reactor_id}, {"fd", conn->fd}, {"server_port", std::string(server_socket->info.get_addr()) + ":" + std::to_string(server_socket->info.get_port())}, {"address", conn->info.get_addr()}, {"port", conn->info.get_port()}, {"overflow", conn->overflow}, {"connect_time", conn->connect_time}, {"last_recv_time", conn->last_recv_time}, {"last_send_time", conn->last_send_time}, {"last_dispatch_time", conn->last_dispatch_time}, {"recv_queued_bytes", conn->recv_queued_bytes}, {"send_queued_bytes", conn->send_queued_bytes}, {"total_recv_bytes", conn->socket->total_recv_bytes}, {"total_send_bytes", conn->socket->total_send_bytes}, {"uid", conn->uid}, }); return info; } static std::string handle_get_socket_info(Server *serv, const std::string &msg) { auto _result = json::parse(msg); if (!_result.is_object() || _result.find("fd") == _result.end()) { json return_value{ {"data", "require parameter fd"}, {"code", 4003}, }; return return_value.dump(); } #ifndef TCP_INFO json return_value{ {"data", "platform unsupported"}, {"code", 5001}, }; #else std::string _fd = _result["fd"]; int fd = sw_atoi(_fd.c_str()); json return_value{ {"data", get_socket_info(fd)}, {"code", 0}, }; #endif return return_value.dump(); } static std::string handle_get_thread_info(Server *serv, const std::string &msg) { ReactorThread *thread = serv->get_thread(SwooleTG.id); std::stringstream ss; ss << std::this_thread::get_id(); json jinfo{ {"tid", ss.str()}, {"id", thread->id}, {"dispatch_count", thread->dispatch_count}, {"event_num", SwooleTG.reactor->get_event_num()}, {"timer_num", SwooleTG.timer ? SwooleTG.timer->count() : 0}, }; json return_value{ {"data", jinfo}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_manager_info(Server *serv, const std::string &msg) { ProcessPool *pool = serv->get_event_worker_pool(); json jinfo{ {"pid", getpid()}, {"reload_count", pool->reload_count}, {"reload_last_time", pool->reload_last_time}, }; json return_value{ {"data", jinfo}, {"code", 0}, }; return return_value.dump(); } static size_t get_socket_out_buffer_total_size() { if (!sw_reactor()) { return 0; } size_t size = 0; for (auto &s : sw_reactor()->get_sockets()) { size += s.second->get_out_buffer_length(); } return size; } static std::string handle_get_memory_info(Server *serv, const std::string &msg) { bool is_thread = serv->is_reactor_thread(); json jinfo{ {"server", sizeof(Server)}, {"workers", serv->get_all_worker_num() * sizeof(Worker)}, {"connection_list", serv->get_max_connection() * sizeof(Connection)}, {"session_list", SW_SESSION_LIST_SIZE * sizeof(Session)}, {"global_memory", dynamic_cast(sw_mem_pool())->get_memory_size()}, {"thread_global_memory", sw_tg_buffer()->size}, {"message_bus", is_thread ? serv->get_thread(SwooleTG.id)->message_bus.get_memory_size() : serv->message_bus.get_memory_size()}, {"socket_list", sw_reactor() ? sw_reactor()->get_sockets().size() * sizeof(network::Socket) : 0}, {"socket_out_buffer", get_socket_out_buffer_total_size()}, {"php_memory", is_thread ? 0 : zend_memory_usage(true)}, }; json return_value{ {"data", jinfo}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_connections(Server *serv, const std::string &msg) { json list = json::array(); serv->foreach_connection([serv, &list](Connection *conn) { if (serv->is_process_mode() && conn->reactor_id != SwooleTG.id) { return; } if (serv->is_base_mode() && sw_worker() && conn->reactor_id != sw_worker()->id) { return; } list.push_back(get_connection_info(serv, conn)); }); json return_value{ {"data", list}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_connection_info(Server *serv, const std::string &msg) { auto _result = json::parse(msg); if (!_result.is_object() || _result.find("session_id") == _result.end()) { json return_value{ {"data", "require parameter session_id"}, {"code", 4003}, }; return return_value.dump(); } std::string _session_id = _result["session_id"]; int session_id = sw_atoi(_session_id.c_str()); Connection *conn = serv->get_connection_verify(session_id); if (!conn) { json return_value{ {"data", "connection not exists"}, {"code", 4004}, }; return return_value.dump(); } json return_value{ {"data", get_connection_info(serv, conn)}, {"code", 0}, }; return return_value.dump(); } static std::string handle_get_all_ports(Server *serv, const std::string &msg) { json _list = json::array(); for (auto port : serv->ports) { json info = json::object({ {"host", port->host}, {"port", port->port}, {"backlog", port->backlog}, {"type", port->type}, {"ssl", port->ssl}, {"protocols", port->get_protocols()}, {"connection_num", (long) port->get_connection_num()}, }); _list.push_back(info); }; json return_value{ {"data", _list}, {"code", 0}, }; return return_value.dump(); } void register_admin_server_commands(Server *serv) { serv->add_command("get_all_sockets", Server::Command::ALL_PROCESS, handle_get_all_sockets); serv->add_command("get_all_commands", Server::Command::ALL_PROCESS, handle_get_all_commands); serv->add_command("get_socket_info", Server::Command::ALL_PROCESS, handle_get_socket_info); serv->add_command("get_thread_info", Server::Command::ALL_PROCESS, handle_get_thread_info); serv->add_command("get_manager_info", Server::Command::MANAGER, handle_get_manager_info); serv->add_command("get_thread_info", Server::Command::ALL_PROCESS, handle_get_thread_info); serv->add_command("get_memory_info", Server::Command::ALL_PROCESS, handle_get_memory_info); serv->add_command("get_all_unix_sockets", Server::Command::ALL_PROCESS, handle_get_all_unix_sockets); serv->add_command("get_all_ports", Server::Command::MASTER, handle_get_all_ports); int accepted_process_types; if (serv->is_base_mode() || serv->single_thread) { accepted_process_types = Server::Command::EVENT_WORKER | Server::Command::MASTER; } else { accepted_process_types = Server::Command::REACTOR_THREAD; } serv->add_command("get_connections", accepted_process_types, handle_get_connections); serv->add_command("get_connection_info", accepted_process_types, handle_get_connection_info); } } // namespace swoole typedef std::function objects_store_iterator; static inline bool object_valid(zend_object *obj) { return obj && IS_OBJ_VALID(obj) && obj->handlers && obj->handlers->get_class_name; } static void objects_store_foreach(const objects_store_iterator &fn) { for (uint32_t i = 0; i < EG(objects_store).top; i++) { zend_object *obj = EG(objects_store).object_buckets[i]; if (object_valid(obj)) { fn(obj); } } } static uint32_t object_store_count() { uint32_t count = 0; objects_store_foreach([&count](zend_object *obj) { count++; }); return count; } #ifdef TCP_INFO // clang-format off std::unordered_map sw_socket_parse_tcp_info(tcp_info *info) { #if defined(__FreeBSD__) || defined(__NetBSD__) return { {"state", info->tcpi_state}, {"ca_state", info->__tcpi_ca_state}, {"retransmits", info->__tcpi_retransmits}, {"probes", info->__tcpi_probes}, {"backoff", info->__tcpi_backoff}, {"options", info->tcpi_options}, {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, {"rto", info->tcpi_rto}, {"ato", info->__tcpi_ato}, {"snd_mss", info->tcpi_snd_mss}, {"rcv_mss", info->tcpi_rcv_mss}, {"unacked", info->__tcpi_unacked}, {"sacked", info->__tcpi_sacked}, {"lost", info->__tcpi_lost}, {"retrans", info->__tcpi_retrans}, {"fackets", info->__tcpi_fackets}, {"last_data_sent", info->__tcpi_last_data_sent}, {"last_ack_sent", info->__tcpi_last_ack_sent}, {"last_data_recv", info->tcpi_last_data_recv}, {"last_ack_recv", info->__tcpi_last_ack_recv}, {"pmtu", info->__tcpi_pmtu}, {"rcv_ssthresh", info->__tcpi_rcv_ssthresh}, {"rtt", info->tcpi_rtt}, {"rttvar", info->tcpi_rttvar}, {"snd_ssthresh", info->tcpi_snd_ssthresh}, {"snd_cwnd", info->tcpi_snd_cwnd}, {"advmss", info->__tcpi_advmss}, {"reordering", info->__tcpi_reordering}, {"rcv_rtt", info->__tcpi_rcv_rtt}, {"rcv_space", info->tcpi_rcv_space}, {"snd_wnd", info->tcpi_snd_wnd}, {"snd_nxt", info->tcpi_snd_nxt}, {"rcv_nxt", info->tcpi_rcv_nxt}, {"toe_tid", info->tcpi_toe_tid}, {"total_retrans", info->tcpi_snd_rexmitpack}, {"rcv_ooopack", info->tcpi_rcv_ooopack}, {"snd_zerowin", info->tcpi_snd_zerowin}, }; #elif defined(__OpenBSD__) return { {"state", info->tcpi_state}, {"ca_state", info->__tcpi_ca_state}, {"retransmits", info->__tcpi_retransmits}, {"probes", info->__tcpi_probes}, {"backoff", info->__tcpi_backoff}, {"options", info->tcpi_options}, {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, {"rto", info->tcpi_rto}, {"ato", info->__tcpi_ato}, {"snd_mss", info->tcpi_snd_mss}, {"rcv_mss", info->tcpi_rcv_mss}, {"unacked", info->__tcpi_unacked}, {"sacked", info->__tcpi_sacked}, {"lost", info->__tcpi_lost}, {"retrans", info->__tcpi_retrans}, {"fackets", info->__tcpi_fackets}, {"last_data_sent", info->tcpi_last_data_sent}, {"last_ack_sent", info->tcpi_last_ack_sent}, {"last_data_recv", info->tcpi_last_data_recv}, {"last_ack_recv", info->tcpi_last_ack_recv}, {"pmtu", info->__tcpi_pmtu}, {"rcv_ssthresh", info->__tcpi_rcv_ssthresh}, {"rtt", info->tcpi_rtt}, {"rttvar", info->tcpi_rttvar}, {"snd_ssthresh", info->tcpi_snd_ssthresh}, {"snd_cwnd", info->tcpi_snd_cwnd}, {"advmss", info->__tcpi_advmss}, {"reordering", info->__tcpi_reordering}, {"rcv_rtt", info->__tcpi_rcv_rtt}, {"rcv_space", info->tcpi_rcv_space}, {"snd_wnd", info->tcpi_snd_wnd}, {"snd_nxt", info->tcpi_snd_nxt}, {"rcv_nxt", info->tcpi_rcv_nxt}, {"toe_tid", info->tcpi_toe_tid}, {"total_retrans", info->tcpi_snd_rexmitpack}, {"rcv_ooopack", info->tcpi_rcv_ooopack}, {"snd_zerowin", info->tcpi_snd_zerowin}, }; #elif defined(__linux__) return { {"state", info->tcpi_state}, {"ca_state", info->tcpi_ca_state}, {"retransmits", info->tcpi_retransmits}, {"probes", info->tcpi_probes}, {"backoff", info->tcpi_backoff}, {"options", info->tcpi_options}, {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, {"rto", info->tcpi_rto}, {"ato", info->tcpi_ato}, {"snd_mss", info->tcpi_snd_mss}, {"rcv_mss", info->tcpi_rcv_mss}, {"unacked", info->tcpi_unacked}, {"sacked", info->tcpi_sacked}, {"lost", info->tcpi_lost}, {"retrans", info->tcpi_retrans}, {"fackets", info->tcpi_fackets}, {"last_data_sent", info->tcpi_last_data_sent}, {"last_ack_sent", info->tcpi_last_ack_sent}, {"last_data_recv", info->tcpi_last_data_recv}, {"last_ack_recv", info->tcpi_last_ack_recv}, {"pmtu", info->tcpi_pmtu}, {"rcv_ssthresh", info->tcpi_rcv_ssthresh}, {"rtt", info->tcpi_rtt}, {"rttvar", info->tcpi_rttvar}, {"snd_ssthresh", info->tcpi_snd_ssthresh}, {"snd_cwnd", info->tcpi_snd_cwnd}, {"advmss", info->tcpi_advmss}, {"reordering", info->tcpi_reordering}, {"rcv_rtt", info->tcpi_rcv_rtt}, {"rcv_space", info->tcpi_rcv_space}, {"total_retrans", info->tcpi_total_retrans}, }; #elif defined(__APPLE__) return { {"state", (uint32_t) info->tcpi_state}, {"snd_wscale", (uint32_t) info->tcpi_snd_wscale}, {"rcv_wscale", (uint32_t) info->tcpi_rcv_wscale}, {"options", (uint32_t) info->tcpi_options}, {"flags", (uint32_t) info->tcpi_flags}, {"rto", info->tcpi_rto}, {"maxseg", info->tcpi_maxseg}, {"snd_ssthresh", info->tcpi_snd_ssthresh}, {"snd_cwnd", info->tcpi_snd_cwnd}, {"snd_wnd", info->tcpi_snd_wnd}, {"snd_sbbytes", info->tcpi_snd_sbbytes}, {"rcv_wnd", info->tcpi_rcv_wnd}, {"srtt", info->tcpi_srtt}, {"rttvar", info->tcpi_rttvar}, }; #else return {}; #endif } // clang-format on #endif ZEND_FUNCTION(swoole_get_vm_status) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("object_num"), object_store_count()); add_assoc_long_ex(return_value, ZEND_STRL("resource_num"), zend_array_count(&EG(regular_list))); } ZEND_FUNCTION(swoole_get_objects) { zend_objects_store *objects = &EG(objects_store); if (objects->top <= 1) { RETURN_FALSE; } array_init(return_value); objects_store_foreach([return_value](zend_object *obj) { zval zobject; ZVAL_OBJ(&zobject, obj); zval_addref_p(&zobject); add_next_index_zval(return_value, &zobject); }); } ZEND_FUNCTION(swoole_get_object_by_handle) { zend_long handle; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(handle) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_objects_store *objects = &EG(objects_store); if (objects->top <= 1 || handle >= objects->top) { RETURN_FALSE; } zend_object *obj = objects->object_buckets[handle]; if (!object_valid(obj)) { RETURN_FALSE; } GC_ADDREF(obj); RETURN_OBJ(obj); } ================================================ FILE: ext-src/swoole_async_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "swoole_socket.h" #include #include #include using std::string; using std::vector; using swoole::Coroutine; using swoole::PHPCoroutine; using swoole::Timer; struct DNSCacheEntity { char address[INET6_ADDRSTRLEN]; time_t update_time; }; static SW_THREAD_LOCAL std::unordered_map request_cache_map; void php_swoole_async_coro_rshutdown() { for (auto i = request_cache_map.begin(); i != request_cache_map.end(); ++i) { efree(i->second); } } void php_swoole_set_aio_option(const HashTable *vht) { zval *ztmp; /* AIO */ if (php_swoole_array_get_value(vht, "aio_core_worker_num", ztmp)) { zend_long v = zval_get_long(ztmp); v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); SwooleG.aio_core_worker_num = v; } if (php_swoole_array_get_value(vht, "aio_worker_num", ztmp)) { zend_long v = zval_get_long(ztmp); v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); SwooleG.aio_worker_num = v; } if (php_swoole_array_get_value(vht, "aio_max_wait_time", ztmp)) { SwooleG.aio_max_wait_time = zval_get_double(ztmp); } if (php_swoole_array_get_value(vht, "aio_max_idle_time", ztmp)) { SwooleG.aio_max_idle_time = zval_get_double(ztmp); } #ifdef SW_USE_IOURING if (php_swoole_array_get_value(vht, "iouring_entries", ztmp)) { zend_long v = zval_get_long(ztmp); SwooleG.iouring_entries = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "iouring_workers", ztmp)) { zend_long v = zval_get_long(ztmp); SwooleG.iouring_workers = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "iouring_flag", ztmp)) { SwooleG.iouring_flag = zval_get_long(ztmp); } #endif } PHP_FUNCTION(swoole_async_set) { SW_MUST_BE_MAIN_THREAD(); if (sw_reactor()) { php_swoole_fatal_error(E_ERROR, "eventLoop has already been created. unable to change settings"); swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); RETURN_FALSE; } zval *zset = nullptr; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HashTable *vht = Z_ARRVAL_P(zset); php_swoole_set_global_option(vht); php_swoole_set_aio_option(vht); if (php_swoole_array_get_value(vht, "wait_signal", ztmp)) { SwooleG.wait_signal = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "dns_cache_refresh_time", ztmp)) { SwooleG.dns_cache_refresh_time = zval_get_double(ztmp); } if (php_swoole_array_get_value(vht, "thread_num", ztmp) || php_swoole_array_get_value(vht, "min_thread_num", ztmp)) { zend_long v = zval_get_long(ztmp); v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); SwooleG.aio_core_worker_num = v; } if (php_swoole_array_get_value(vht, "max_thread_num", ztmp)) { zend_long v = zval_get_long(ztmp); v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); SwooleG.aio_worker_num = v; } if (php_swoole_array_get_value(vht, "dns_lookup_random", ztmp)) { SwooleG.dns_lookup_random = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "use_async_resolver", ztmp)) { SwooleG.use_async_resolver = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { SwooleG.enable_coroutine = zval_is_true(ztmp); } RETURN_TRUE; } PHP_FUNCTION(swoole_async_dns_lookup_coro) { Coroutine::get_current_safe(); zval *domain; zend_long type = AF_INET; double timeout = swoole::network::Socket::default_dns_timeout; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(domain) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (Z_TYPE_P(domain) != IS_STRING) { php_swoole_fatal_error(E_WARNING, "invalid domain name"); RETURN_FALSE; } if (Z_STRLEN_P(domain) == 0) { php_swoole_fatal_error(E_WARNING, "domain name empty"); RETURN_FALSE; } // find cache std::string key(Z_STRVAL_P(domain), Z_STRLEN_P(domain)); DNSCacheEntity *cache; if (request_cache_map.find(key) != request_cache_map.end()) { cache = request_cache_map[key]; if (cache->update_time > Timer::get_absolute_msec()) { RETURN_STRING(cache->address); } } php_swoole_check_reactor(); vector result = swoole::coroutine::dns_lookup(Z_STRVAL_P(domain), type, timeout); if (result.empty()) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); RETURN_FALSE; } if (SwooleG.dns_lookup_random) { RETVAL_STRING(result[swoole_rand() % result.size()].c_str()); } else { RETVAL_STRING(result[0].c_str()); } auto cache_iterator = request_cache_map.find(key); if (cache_iterator == request_cache_map.end()) { cache = (DNSCacheEntity *) emalloc(sizeof(DNSCacheEntity)); request_cache_map[key] = cache; } else { cache = cache_iterator->second; } memcpy(cache->address, Z_STRVAL_P(return_value), Z_STRLEN_P(return_value)); cache->address[Z_STRLEN_P(return_value)] = '\0'; cache->update_time = Timer::get_absolute_msec() + (int64_t) (SwooleG.dns_cache_refresh_time * 1000); } ================================================ FILE: ext-src/swoole_atomic.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "swoole_memory.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_atomic_arginfo.h" END_EXTERN_C() zend_class_entry *swoole_atomic_ce; static zend_object_handlers swoole_atomic_handlers; zend_class_entry *swoole_atomic_long_ce; static zend_object_handlers swoole_atomic_long_handlers; struct AtomicObject { sw_atomic_t *ptr; zend_object std; }; static sw_inline AtomicObject *atomic_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_atomic_handlers.offset); } static sw_atomic_t *atomic_get_ptr(const zval *zobject) { return atomic_fetch_object(Z_OBJ_P(zobject))->ptr; } static void atomic_free_object(zend_object *object) { sw_mem_pool()->free((void *) atomic_fetch_object(object)->ptr); zend_object_std_dtor(object); } static zend_object *atomic_create_object(zend_class_entry *ce) { auto *atomic = static_cast(zend_object_alloc(sizeof(AtomicObject), ce)); if (atomic == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } zend_object_std_init(&atomic->std, ce); object_properties_init(&atomic->std, ce); atomic->std.handlers = &swoole_atomic_handlers; atomic->ptr = (sw_atomic_t *) sw_mem_pool()->alloc(sizeof(sw_atomic_t)); if (atomic->ptr == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } return &atomic->std; } struct AtomicLongObject { sw_atomic_long_t *ptr; zend_object std; }; static AtomicLongObject *atomic_long_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_atomic_long_handlers.offset); } static sw_atomic_long_t *atomic_long_get_ptr(const zval *zobject) { return atomic_long_fetch_object(Z_OBJ_P(zobject))->ptr; } static void atomic_long_free_object(zend_object *object) { sw_mem_pool()->free((void *) atomic_long_fetch_object(object)->ptr); zend_object_std_dtor(object); } static zend_object *atomic_long_create_object(zend_class_entry *ce) { auto *atomic_long = static_cast(zend_object_alloc(sizeof(AtomicLongObject), ce)); if (atomic_long == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } zend_object_std_init(&atomic_long->std, ce); object_properties_init(&atomic_long->std, ce); atomic_long->std.handlers = &swoole_atomic_long_handlers; atomic_long->ptr = (sw_atomic_long_t *) sw_mem_pool()->alloc(sizeof(sw_atomic_long_t)); if (atomic_long->ptr == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } return &atomic_long->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_atomic, __construct); static PHP_METHOD(swoole_atomic, add); static PHP_METHOD(swoole_atomic, sub); static PHP_METHOD(swoole_atomic, get); static PHP_METHOD(swoole_atomic, set); static PHP_METHOD(swoole_atomic, cmpset); static PHP_METHOD(swoole_atomic, wait); static PHP_METHOD(swoole_atomic, wakeup); static PHP_METHOD(swoole_atomic_long, __construct); static PHP_METHOD(swoole_atomic_long, add); static PHP_METHOD(swoole_atomic_long, sub); static PHP_METHOD(swoole_atomic_long, get); static PHP_METHOD(swoole_atomic_long, set); static PHP_METHOD(swoole_atomic_long, cmpset); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_atomic_methods[] = { PHP_ME(swoole_atomic, __construct, arginfo_class_Swoole_Atomic___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, add, arginfo_class_Swoole_Atomic_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, sub, arginfo_class_Swoole_Atomic_sub, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, get, arginfo_class_Swoole_Atomic_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, set, arginfo_class_Swoole_Atomic_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, wait, arginfo_class_Swoole_Atomic_wait, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, wakeup, arginfo_class_Swoole_Atomic_wakeup, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic, cmpset, arginfo_class_Swoole_Atomic_cmpset, ZEND_ACC_PUBLIC) PHP_FE_END }; static const zend_function_entry swoole_atomic_long_methods[] = { PHP_ME(swoole_atomic_long, __construct, arginfo_class_Swoole_Atomic_Long___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic_long, add, arginfo_class_Swoole_Atomic_Long_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic_long, sub, arginfo_class_Swoole_Atomic_Long_sub, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic_long, get, arginfo_class_Swoole_Atomic_Long_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic_long, set, arginfo_class_Swoole_Atomic_Long_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_atomic_long, cmpset, arginfo_class_Swoole_Atomic_Long_cmpset, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_atomic_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_atomic, "Swoole\\Atomic", nullptr, swoole_atomic_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_atomic); SW_SET_CLASS_CLONEABLE(swoole_atomic, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_atomic, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_atomic, atomic_create_object, atomic_free_object, AtomicObject, std); SW_INIT_CLASS_ENTRY(swoole_atomic_long, "Swoole\\Atomic\\Long", nullptr, swoole_atomic_long_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_atomic_long); SW_SET_CLASS_CLONEABLE(swoole_atomic_long, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_atomic_long, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_atomic_long, atomic_long_create_object, atomic_long_free_object, AtomicLongObject, std); } PHP_METHOD(swoole_atomic, __construct) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long value = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic = (sw_atomic_t) value; } PHP_METHOD(swoole_atomic, add) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(add_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_add_fetch(atomic, (uint32_t) add_value)); } PHP_METHOD(swoole_atomic, sub) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(sub_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_sub_fetch(atomic, (uint32_t) sub_value)); } PHP_METHOD(swoole_atomic, get) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); RETURN_LONG(*atomic); } PHP_METHOD(swoole_atomic, set) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic = (uint32_t) set_value; } PHP_METHOD(swoole_atomic, cmpset) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(cmp_value) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(sw_atomic_cmp_set(atomic, (sw_atomic_t) cmp_value, (sw_atomic_t) set_value)); } PHP_METHOD(swoole_atomic, wait) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); double timeout = 1.0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_RETURN(sw_atomic_futex_wait(atomic, timeout)); } PHP_METHOD(swoole_atomic, wakeup) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long n = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(n) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_RETURN(sw_atomic_futex_wakeup(atomic, (int) n)); } PHP_METHOD(swoole_atomic_long, __construct) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long value = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic_long = (sw_atomic_long_t) value; RETURN_TRUE; } PHP_METHOD(swoole_atomic_long, add) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(add_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_add_fetch(atomic_long, (sw_atomic_long_t) add_value)); } PHP_METHOD(swoole_atomic_long, sub) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(sub_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_sub_fetch(atomic_long, (sw_atomic_long_t) sub_value)); } PHP_METHOD(swoole_atomic_long, get) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); RETURN_LONG(*atomic_long); } PHP_METHOD(swoole_atomic_long, set) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic_long = (sw_atomic_long_t) set_value; } PHP_METHOD(swoole_atomic_long, cmpset) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(cmp_value) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(sw_atomic_cmp_set(atomic_long, (sw_atomic_long_t) cmp_value, (sw_atomic_long_t) set_value)); } ================================================ FILE: ext-src/swoole_channel_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "swoole_coroutine_channel.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_channel_coro_arginfo.h" END_EXTERN_C() using swoole::coroutine::Channel; static zend_class_entry *swoole_channel_coro_ce; static zend_object_handlers swoole_channel_coro_handlers; struct ChannelObject { Channel *chan; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_channel_coro, __construct); static PHP_METHOD(swoole_channel_coro, push); static PHP_METHOD(swoole_channel_coro, pop); static PHP_METHOD(swoole_channel_coro, close); static PHP_METHOD(swoole_channel_coro, stats); static PHP_METHOD(swoole_channel_coro, length); static PHP_METHOD(swoole_channel_coro, isEmpty); static PHP_METHOD(swoole_channel_coro, isFull); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_channel_coro_methods[] = { PHP_ME(swoole_channel_coro, __construct, arginfo_class_Swoole_Coroutine_Channel___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, push, arginfo_class_Swoole_Coroutine_Channel_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, pop, arginfo_class_Swoole_Coroutine_Channel_pop, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, isEmpty, arginfo_class_Swoole_Coroutine_Channel_isEmpty, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, isFull, arginfo_class_Swoole_Coroutine_Channel_isFull, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, close, arginfo_class_Swoole_Coroutine_Channel_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, stats, arginfo_class_Swoole_Coroutine_Channel_stats, ZEND_ACC_PUBLIC) PHP_ME(swoole_channel_coro, length, arginfo_class_Swoole_Coroutine_Channel_length, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on static ChannelObject *channel_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_channel_coro_handlers.offset); } static Channel *channel_coro_get_ptr(const zval *zobject) { Channel *chan = channel_coro_fetch_object(Z_OBJ_P(zobject))->chan; if (UNEXPECTED(!chan)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return chan; } static void channel_coro_dtor_object(zend_object *object) { zend_objects_destroy_object(object); ChannelObject *chan_object = channel_coro_fetch_object(object); Channel *chan = chan_object->chan; if (chan) { zval *data; while ((data = (zval *) chan->pop_data())) { sw_zval_free(data); } delete chan; chan_object->chan = nullptr; } } static void channel_coro_free_object(zend_object *object) { auto *chan_object = channel_coro_fetch_object(object); delete chan_object->chan; zend_object_std_dtor(object); } static zend_object *channel_coro_create_object(zend_class_entry *ce) { auto *chan_object = static_cast(zend_object_alloc(sizeof(ChannelObject), ce)); zend_object_std_init(&chan_object->std, ce); object_properties_init(&chan_object->std, ce); chan_object->std.handlers = &swoole_channel_coro_handlers; return &chan_object->std; } void php_swoole_channel_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_channel_coro, "Swoole\\Coroutine\\Channel", "Co\\Channel", swoole_channel_coro_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_channel_coro); SW_SET_CLASS_CLONEABLE(swoole_channel_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_channel_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_channel_coro, channel_coro_create_object, channel_coro_free_object, ChannelObject, std); SW_SET_CLASS_DTOR(swoole_channel_coro, channel_coro_dtor_object); if (SWOOLE_G(use_shortname)) { SW_CLASS_ALIAS("Chan", swoole_channel_coro); } zend_declare_property_long(swoole_channel_coro_ce, ZEND_STRL("capacity"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_channel_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); SW_REGISTER_LONG_CONSTANT("SWOOLE_CHANNEL_OK", Channel::ERROR_OK); SW_REGISTER_LONG_CONSTANT("SWOOLE_CHANNEL_TIMEOUT", Channel::ERROR_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_CHANNEL_CLOSED", Channel::ERROR_CLOSED); SW_REGISTER_LONG_CONSTANT("SWOOLE_CHANNEL_CANCELED", Channel::ERROR_CANCELED); } static PHP_METHOD(swoole_channel_coro, __construct) { zend_long capacity = 1; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(capacity) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (capacity <= 0) { capacity = 1; } ChannelObject *chan_t = channel_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); chan_t->chan = new Channel(capacity); zend_update_property_long(swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("capacity"), capacity); } static PHP_METHOD(swoole_channel_coro, push) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); zval *zdata; double timeout = -1; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Z_TRY_ADDREF_P(zdata); zdata = sw_zval_dup(zdata); if (chan->push(zdata, timeout)) { zend_update_property_long( swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), Channel::ERROR_OK); RETURN_TRUE; } else { zend_update_property_long( swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), chan->get_error()); Z_TRY_DELREF_P(zdata); efree(zdata); RETURN_FALSE; } } static PHP_METHOD(swoole_channel_coro, pop) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); double timeout = -1; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval *zdata = (zval *) chan->pop(timeout); if (zdata) { RETVAL_ZVAL(zdata, 0, 0); efree(zdata); zend_update_property_long( swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), Channel::ERROR_OK); } else { zend_update_property_long( swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), chan->get_error()); RETURN_FALSE; } } static PHP_METHOD(swoole_channel_coro, close) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->close()); } static PHP_METHOD(swoole_channel_coro, length) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_LONG(chan->length()); } static PHP_METHOD(swoole_channel_coro, isEmpty) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->is_empty()); } static PHP_METHOD(swoole_channel_coro, isFull) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->is_full()); } static PHP_METHOD(swoole_channel_coro, stats) { Channel *chan = channel_coro_get_ptr(ZEND_THIS); array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("consumer_num"), chan->consumer_num()); add_assoc_long_ex(return_value, ZEND_STRL("producer_num"), chan->producer_num()); add_assoc_long_ex(return_value, ZEND_STRL("queue_num"), chan->length()); } ================================================ FILE: ext-src/swoole_client.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_client.h" #include "swoole_mqtt.h" #include #include #include BEGIN_EXTERN_C() #include "stubs/php_swoole_client_arginfo.h" END_EXTERN_C() using swoole::HttpProxy; using swoole::PacketLength; using swoole::Protocol; using swoole::SocketType; using swoole::Socks5Proxy; using swoole::String; using swoole::network::Address; using swoole::network::Client; using swoole::network::Socket; static std::unordered_map *> long_connections; zend_class_entry *swoole_client_ce; zend_object_handlers swoole_client_handlers; static zend_class_entry *swoole_client_exception_ce; static zend_object_handlers swoole_client_exception_handlers; static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, int port); static sw_inline void php_swoole_client_set_cli(const zval *zobject, Client *cli) { php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cli = cli; } #ifdef SWOOLE_SOCKETS_SUPPORT static zval *client_get_zsocket(const zval *zobject) { return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->zsocket; } static void client_set_zsocket(const zval *zobject, zval *zsocket) { php_swoole_client_fetch_object(Z_OBJ_P(zobject))->zsocket = zsocket; } #endif static void client_free_object(zend_object *object) { auto client_obj = php_swoole_client_fetch_object(object); if (client_obj->async) { php_swoole_client_async_free_object(client_obj); } zend_object_std_dtor(object); } static zend_object *client_create_object(zend_class_entry *ce) { auto *client = static_cast(zend_object_alloc(sizeof(ClientObject), ce)); zend_object_std_init(&client->std, ce); object_properties_init(&client->std, ce); client->std.handlers = &swoole_client_handlers; client->async = nullptr; return &client->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_client, __construct); static PHP_METHOD(swoole_client, __destruct); static PHP_METHOD(swoole_client, set); static PHP_METHOD(swoole_client, connect); static PHP_METHOD(swoole_client, recv); static PHP_METHOD(swoole_client, send); static PHP_METHOD(swoole_client, sendfile); static PHP_METHOD(swoole_client, sendto); static PHP_METHOD(swoole_client, enableSSL); static PHP_METHOD(swoole_client, getPeerCert); static PHP_METHOD(swoole_client, verifyPeerCert); static PHP_METHOD(swoole_client, isConnected); static PHP_METHOD(swoole_client, getsockname); static PHP_METHOD(swoole_client, getpeername); static PHP_METHOD(swoole_client, close); static PHP_METHOD(swoole_client, shutdown); #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_client, getSocket); #endif SW_EXTERN_C_END static uint32_t client_poll_add(const zval *sock_array, uint32_t index, pollfd *fds, int maxevents, int event); static int client_poll_wait(zval *sock_array, const pollfd *fds, int maxevents, int n_event, int revent); Client *php_swoole_client_get_cli_safe(const zval *zobject) { Client *cli = php_swoole_client_get_cli(zobject); if (cli && cli->socket) { if (cli->active) { return cli; } if (cli->async_connect) { cli->async_connect = false; int error = -1; if (cli->get_socket()->get_option(SOL_SOCKET, SO_ERROR, &error) == 0) { if (error == 0) { cli->active = true; return cli; } } php_swoole_client_free(zobject, cli); } } swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION); zend_update_property_long(swoole_client_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); php_swoole_error(E_WARNING, "client is not connected to server"); return nullptr; } // clang-format off static const zend_function_entry swoole_client_methods[] = { PHP_ME(swoole_client, __construct, arginfo_class_Swoole_Client___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, __destruct, arginfo_class_Swoole_Client___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, set, arginfo_class_Swoole_Client_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, connect, arginfo_class_Swoole_Client_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, recv, arginfo_class_Swoole_Client_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, send, arginfo_class_Swoole_Client_send, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, sendfile, arginfo_class_Swoole_Client_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, sendto, arginfo_class_Swoole_Client_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, shutdown, arginfo_class_Swoole_Client_shutdown, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, enableSSL, arginfo_class_Swoole_Client_enableSSL, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getPeerCert, arginfo_class_Swoole_Client_getPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, verifyPeerCert, arginfo_class_Swoole_Client_verifyPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, isConnected, arginfo_class_Swoole_Client_isConnected, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getsockname, arginfo_class_Swoole_Client_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getpeername, arginfo_class_Swoole_Client_getpeername, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, close, arginfo_class_Swoole_Client_close, ZEND_ACC_PUBLIC) #ifdef SWOOLE_SOCKETS_SUPPORT PHP_ME(swoole_client, getSocket, arginfo_class_Swoole_Client_getSocket, ZEND_ACC_PUBLIC) #endif PHP_FE_END }; // clang-format on void php_swoole_client_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_client, "Swoole\\Client", nullptr, swoole_client_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client); SW_SET_CLASS_CLONEABLE(swoole_client, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_client, client_create_object, client_free_object, ClientObject, std); SW_INIT_CLASS_ENTRY_EX(swoole_client_exception, "Swoole\\Client\\Exception", nullptr, nullptr, swoole_exception); zend_declare_property_long(swoole_client_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_client_ce, ZEND_STRL("reuse"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_ce, ZEND_STRL("reuseCount"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_client_ce, ZEND_STRL("id"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_client_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("MSG_OOB"), MSG_OOB); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("MSG_PEEK"), MSG_PEEK); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("MSG_DONTWAIT"), MSG_DONTWAIT); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("MSG_WAITALL"), MSG_WAITALL); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_RDWR"), SHUT_RDWR); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_RD"), SHUT_RD); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_WR"), SHUT_WR); zend::add_constant("SWOOLE_SYNC", SW_FLAG_SYNC); zend::add_constant("SWOOLE_ASYNC", SW_FLAG_ASYNC); zend::add_constant("SWOOLE_KEEP", SW_FLAG_KEEP); } void php_swoole_client_check_ssl_setting(const Client *cli, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { cli->set_ssl_protocols(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { cli->set_ssl_disable_compress(!zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { zend::String str_v(ztmp); if (!cli->set_ssl_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl cert file[%s] not found", str_v.val()); return; } } if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { zend::String str_v(ztmp); if (!cli->set_ssl_key_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl key file[%s] not found", str_v.val()); return; } } if (php_swoole_array_get_value(vht, "ssl_passphrase", ztmp)) { zend::String str_v(ztmp); cli->set_ssl_passphrase(str_v.to_std_string()); } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (php_swoole_array_get_value(vht, "ssl_host_name", ztmp)) { zend::String str_v(ztmp); cli->set_tls_host_name(str_v.to_std_string()); } #endif if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { cli->set_ssl_verify_peer(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { cli->set_ssl_allow_self_signed(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { zend::String str_v(ztmp); cli->set_ssl_cafile(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { zend::String str_v(ztmp); cli->set_ssl_capath(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { zend_long v = zval_get_long(ztmp); cli->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { zend::String str_v(ztmp); cli->set_ssl_ciphers(str_v.to_std_string()); } if (!cli->get_ssl_cert_file().empty() && cli->get_ssl_key_file().empty()) { php_swoole_fatal_error(E_ERROR, "ssl require key file"); return; } if (!cli->get_ssl_key_file().empty() && cli->get_ssl_cert_file().empty()) { php_swoole_fatal_error(E_ERROR, "ssl require cert file"); return; } } bool php_swoole_client_check_setting(Client *cli, const zval *zset) { zval *ztmp; int value = 1; HashTable *vht = Z_ARRVAL_P(zset); // buffer: eof check if (php_swoole_array_get_value(vht, "open_eof_check", ztmp)) { cli->open_eof_check = zval_is_true(ztmp); } // buffer: split package with eof if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { cli->protocol.split_by_eof = zval_is_true(ztmp); if (cli->protocol.split_by_eof) { cli->open_eof_check = true; } } // package eof if (php_swoole_array_get_value(vht, "package_eof", ztmp)) { zend::String str_v(ztmp); cli->protocol.package_eof_len = str_v.len(); if (cli->protocol.package_eof_len == 0) { php_swoole_fatal_error(E_ERROR, "package_eof cannot be an empty string"); return false; } else if (cli->protocol.package_eof_len > SW_DATA_EOF_MAXLEN) { php_swoole_fatal_error(E_ERROR, "package_eof max length is %d", SW_DATA_EOF_MAXLEN); return false; } memcpy(cli->protocol.package_eof, str_v.val(), str_v.len()); } // open mqtt protocol if (php_swoole_array_get_value(vht, "open_mqtt_protocol", ztmp)) { cli->open_length_check = zval_is_true(ztmp); if (zval_is_true(ztmp)) { swoole::mqtt::set_protocol(&cli->protocol); } } // open length check if (php_swoole_array_get_value(vht, "open_length_check", ztmp)) { cli->open_length_check = zval_is_true(ztmp); cli->protocol.get_package_length = Protocol::default_length_func; } // package length size if (php_swoole_array_get_value(vht, "package_length_type", ztmp)) { zend::String str_v(ztmp); cli->protocol.package_length_type = str_v.val()[0]; cli->protocol.package_length_size = swoole_type_size(cli->protocol.package_length_type); if (cli->protocol.package_length_size == 0) { php_swoole_fatal_error(E_ERROR, "Unknown package_length_type name '%c', see pack(). Link: https://php.net/pack", cli->protocol.package_length_type); return false; } } // package length offset if (php_swoole_array_get_value(vht, "package_length_offset", ztmp)) { zend_long v = zval_get_long(ztmp); cli->protocol.package_length_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } // package body start if (php_swoole_array_get_value(vht, "package_body_offset", ztmp)) { zend_long v = zval_get_long(ztmp); cli->protocol.package_body_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { auto fci_cache = sw_callable_create(ztmp); if (!fci_cache) { return false; } cli->protocol.get_package_length = php_swoole_length_func; if (cli->protocol.private_data_1) { sw_callable_free(cli->protocol.private_data_1); } cli->protocol.private_data_1 = fci_cache; cli->protocol.package_length_size = 0; cli->protocol.package_length_type = '\0'; cli->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; } /** * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); cli->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } else { cli->protocol.package_max_length = SW_INPUT_BUFFER_SIZE; } /** * socket send/recv buffer size */ if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(1, SW_MIN(v, INT_MAX)); cli->socket->set_buffer_size(value); cli->socket->buffer_size = value; } if (php_swoole_array_get_value(vht, "buffer_high_watermark", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(0, SW_MIN(v, UINT32_MAX)); cli->buffer_high_watermark = value; } if (php_swoole_array_get_value(vht, "buffer_low_watermark", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(0, SW_MIN(v, UINT32_MAX)); cli->buffer_low_watermark = value; } /** * bind port */ std::string bind_address; int bind_port = 0; if (php_swoole_array_get_value(vht, "bind_port", ztmp)) { zend_long v = zval_get_long(ztmp); bind_port = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } /** * bind address */ if (php_swoole_array_get_value(vht, "bind_address", ztmp)) { bind_address = zend::String(ztmp).to_std_string(); } if (!bind_address.empty() && cli->bind(bind_address, bind_port) < 0) { return false; } /** * client: tcp_nodelay */ if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { if (zval_is_true(ztmp)) { goto _open_tcp_nodelay; } } else { _open_tcp_nodelay: // The failure to set tcp_nodelay does not affect the normal operation of the client; // therefore, only an error log is printed without returning false. if (cli->socket->is_tcp() && !cli->socket->set_tcp_nodelay()) { php_swoole_sys_error(E_WARNING, "setsockopt(%d, TCP_NODELAY) failed", cli->socket->fd); } } /** * socks5 proxy */ if (php_swoole_array_get_value(vht, "socks5_host", ztmp)) { zend::String host(ztmp); if (php_swoole_array_get_value(vht, "socks5_port", ztmp)) { auto socks5_port = zval_get_long(ztmp); std::string username, password; if (php_swoole_array_get_value(vht, "socks5_username", ztmp)) { zend::String _value(ztmp); username = _value.to_std_string(); } if (php_swoole_array_get_value(vht, "socks5_password", ztmp)) { zend::String _value(ztmp); password = _value.to_std_string(); } cli->set_socks5_proxy(host.to_std_string(), socks5_port, username, password); } else { php_swoole_fatal_error(E_WARNING, "socks5_port should not be null"); return false; } } /** * http proxy */ else if (php_swoole_array_get_value(vht, "http_proxy_host", ztmp)) { zend::String host(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_port", ztmp)) { std::string username, password; auto http_proxy_port = zval_get_long(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_username", ztmp) || php_swoole_array_get_value(vht, "http_proxy_user", ztmp)) { zend::String _value(ztmp); username = _value.to_std_string(); } if (php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { zend::String _value(ztmp); password = _value.to_std_string(); } cli->set_http_proxy(host.to_std_string(), http_proxy_port, username, password); } else { php_swoole_fatal_error(E_WARNING, "http_proxy_port should not be null"); return false; } } /** * ssl */ if (cli->open_ssl) { php_swoole_client_check_ssl_setting(cli, zset); } return true; } void php_swoole_client_free(const zval *zobject, Client *cli) { if (cli->timer) { swoole_timer_del(cli->timer); cli->timer = nullptr; } if (cli->protocol.private_data_1) { sw_callable_free(cli->protocol.private_data_1); cli->protocol.private_data_1 = nullptr; } // long tcp connection, delete from connection pool if (cli->keep) { auto i = long_connections.find(cli->server_id); if (i != long_connections.end()) { std::queue *q = i->second; if (q->empty()) { delete q; long_connections.erase(cli->server_id); } } } delete cli; #ifdef SWOOLE_SOCKETS_SUPPORT zval *zsocket = client_get_zsocket(zobject); if (zsocket) { sw_zval_free(zsocket); client_set_zsocket(zobject, nullptr); } #endif // unset object php_swoole_client_set_cli(zobject, nullptr); } ssize_t php_swoole_length_func(const Protocol *protocol, Socket *_socket, PacketLength *pl) { auto *cb = static_cast(protocol->private_data_1); zval zdata; zval retval; ssize_t ret = -1; // TODO: reduce memory copy ZVAL_STRINGL(&zdata, pl->buf, pl->buf_size); if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, cb->ptr(), 1, &zdata, &retval) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "length function handler error"); } else { ret = zval_get_long(&retval); zval_ptr_dtor(&retval); } zval_ptr_dtor(&zdata); return ret; } static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, int port) { zval *ztype = sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TYPE), 0); if (ztype == nullptr || ZVAL_IS_NULL(ztype)) { php_swoole_fatal_error(E_ERROR, "failed to get swoole_client->type"); return nullptr; } long type = Z_LVAL_P(ztype); int socket_type = php_swoole_client_get_type(type); if (Socket::is_tcp(static_cast(socket_type)) && !Address::verify_port(port)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); swoole_set_last_error(SW_ERROR_INVALID_PARAMS); return nullptr; } Client *cli; std::string conn_key; zval *zconnection_id = sw_zend_read_property_not_null_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_ID), 1); if (zconnection_id && Z_TYPE_P(zconnection_id) == IS_STRING && Z_STRLEN_P(zconnection_id) > 0) { conn_key = std::string(Z_STRVAL_P(zconnection_id), Z_STRLEN_P(zconnection_id)); } else { size_t size = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, "%s:%d", host, port); conn_key = std::string(sw_tg_buffer()->str, size); } // keep the tcp connection if (type & SW_FLAG_KEEP) { auto i = long_connections.find(conn_key); if (i == long_connections.end() || i->second->empty()) { goto _create_socket; } else { std::queue *q = i->second; cli = q->front(); q->pop(); if (!cli->socket->check_liveness()) { cli->close(); php_swoole_client_free(zobject, cli); goto _create_socket; } cli->reuse_count++; zend_update_property_long( Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("reuseCount"), cli->reuse_count); } } else { _create_socket: cli = new Client(php_swoole_client_get_type(type), false); if (cli->socket == nullptr) { php_swoole_sys_error(E_WARNING, "Client_create() failed"); zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); delete cli; return nullptr; } cli->server_id = std::string(conn_key.c_str(), conn_key.length()); } zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("sock"), cli->socket->fd); if (type & SW_FLAG_KEEP) { cli->keep = true; } if (type & SW_SOCK_SSL) { cli->enable_ssl_encrypt(); } return cli; } static PHP_METHOD(swoole_client, __construct) { zend_long type = 0; zend_bool async = false; char *id = nullptr; size_t len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|bs", &type, &async, &id, &len) == FAILURE) { zend_throw_error(nullptr, "socket type param is required"); RETURN_FALSE; } if (async) { zend_throw_error(nullptr, "The $async parameter is not supported"); RETURN_FALSE; } int client_type = php_swoole_client_get_type(type); if (client_type < SW_SOCK_TCP || client_type > SW_SOCK_UNIX_DGRAM) { const char *space, *class_name = get_active_class_name(&space); zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", class_name, space, get_active_function_name(), 1, type); RETURN_FALSE; } zend_update_property_long(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("type"), type); if (id) { zend_update_property_stringl(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("id"), id, len); } RETURN_TRUE; } static PHP_METHOD(swoole_client, __destruct) { SW_PREVENT_USER_DESTRUCT(); Client *cli = php_swoole_client_get_cli(ZEND_THIS); // no keep connection if (cli) { sw_zend_call_method_with_0_params(ZEND_THIS, swoole_client_ce, nullptr, "close", nullptr); } } static PHP_METHOD(swoole_client, set) { zval *zset; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zset) == FAILURE) { RETURN_FALSE; } if (!ZVAL_IS_ARRAY(zset)) { RETURN_FALSE; } zval *zsetting = sw_zend_read_and_convert_property_array(swoole_client_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); RETURN_TRUE; } static PHP_METHOD(swoole_client, connect) { char *host; size_t host_len; zend_long port = 0; double timeout = SW_CLIENT_CONNECT_TIMEOUT; zend_long sock_flag = 0; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(sock_flag) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (host_len == 0) { php_swoole_fatal_error(E_WARNING, "The host is empty"); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli(ZEND_THIS); if (cli) { php_swoole_fatal_error(E_WARNING, "connection to the server has already been established"); RETURN_FALSE; } cli = php_swoole_client_new(ZEND_THIS, host, host_len, port); if (cli == nullptr) { RETURN_FALSE; } php_swoole_client_set_cli(ZEND_THIS, cli); if (cli->keep && cli->active) { zend_update_property_bool(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("reuse"), 1); RETURN_TRUE; } else if (cli->active == 1) { php_swoole_fatal_error(E_WARNING, "connection to the server has already been established"); RETURN_FALSE; } zval *zset = sw_zend_read_property_ex(swoole_client_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (zset && ZVAL_IS_ARRAY(zset)) { swoole_set_last_error(0); if (!php_swoole_client_check_setting(cli, zset)) { zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); RETURN_FALSE; } } if (cli->connect(host, port, timeout, sock_flag) < 0) { zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); // async connect if (cli->async_connect) { RETURN_TRUE; } php_swoole_core_error(E_WARNING, "connect to server[%s:%d] failed. Error: %s[%d]", host, (int) port, swoole_strerror(swoole_get_last_error()), swoole_get_last_error()); php_swoole_client_free(ZEND_THIS, cli); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client, send) { char *data; size_t data_len; zend_long flags = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(data, data_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (data_len == 0) { php_swoole_fatal_error(E_WARNING, "data to send is empty"); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } // clear errno swoole_set_last_error(0); ssize_t ret = cli->send(data, data_len, flags); if (ret < 0) { php_swoole_sys_error(E_WARNING, "failed to send(%d) %zu bytes", cli->socket->fd, data_len); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); RETVAL_FALSE; } else { RETURN_LONG(ret); } } static PHP_METHOD(swoole_client, sendto) { char *host; size_t host_len; long port; char *data; size_t len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &host, &host_len, &port, &data, &len) == FAILURE) { RETURN_FALSE; } if (len == 0) { php_swoole_error(E_WARNING, "data to send is empty"); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli) { cli = php_swoole_client_new(ZEND_THIS, host, host_len, port); if (cli == nullptr) { RETURN_FALSE; } cli->active = true; php_swoole_client_set_cli(ZEND_THIS, cli); } auto rv = cli->sendto(std::string(host, host_len), port, data, len); if (rv < 0) { zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), swoole_get_last_error()); } SW_CHECK_RETURN(rv); } static PHP_METHOD(swoole_client, sendfile) { char *file; size_t file_len; zend_long offset = 0; zend_long length = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &file, &file_len, &offset, &length) == FAILURE) { RETURN_FALSE; } if (file_len == 0) { php_swoole_fatal_error(E_WARNING, "file to send is empty"); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } // only stream socket can sendfile if (!(cli->socket->is_stream())) { php_swoole_error(E_WARNING, "dgram socket cannot use sendfile"); RETURN_FALSE; } // clear errno swoole_set_last_error(0); int ret = cli->sendfile(file, offset, length); if (ret < 0) { swoole_set_last_error(errno); php_swoole_fatal_error(E_WARNING, "sendfile() failed. Error: %s [%d]", swoole_strerror(swoole_get_last_error()), swoole_get_last_error()); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); RETVAL_FALSE; } else { RETVAL_TRUE; } } static PHP_METHOD(swoole_client, recv) { zend_long buf_len = SW_PHP_CLIENT_BUFFER_SIZE; zend_long flags = 0; int ret; zend_string *strbuf = nullptr; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(buf_len) Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); // waitall if (flags == 1) { flags = MSG_WAITALL; } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } Protocol *protocol = &cli->protocol; if (cli->open_eof_check) { if (cli->buffer == nullptr) { cli->buffer = swoole::make_string(SW_BUFFER_SIZE_BIG, sw_zend_string_allocator()); } String *buffer = cli->buffer; ssize_t eof = -1; char *buf = nullptr; if (buffer->length > 0) { goto _find_eof; } while (true) { buf = buffer->str + buffer->length; buf_len = buffer->size - buffer->length; if (buf_len > SW_BUFFER_SIZE_BIG) { buf_len = SW_BUFFER_SIZE_BIG; } ret = cli->recv(buf, buf_len, 0); if (ret < 0) { php_swoole_sys_error(E_WARNING, "recv() failed"); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); buffer->length = 0; RETURN_FALSE; } else if (ret == 0) { buffer->length = 0; RETURN_EMPTY_STRING(); } buffer->length += ret; if (buffer->length < protocol->package_eof_len) { continue; } _find_eof: eof = swoole_strnpos(buffer->str, buffer->length, protocol->package_eof, protocol->package_eof_len); if (eof >= 0) { eof += protocol->package_eof_len; if ((ssize_t) buffer->length > eof) { cli->buffer = swoole::make_string(SW_BUFFER_SIZE_BIG, sw_zend_string_allocator()); cli->buffer->length = buffer->length - eof; memcpy(cli->buffer->str, buffer->str + eof, cli->buffer->length); } else { cli->buffer = nullptr; buffer->length = 0; } zend::assign_zend_string_by_val(return_value, buffer->str, eof); buffer->str = nullptr; delete buffer; return; } else { if (buffer->length == protocol->package_max_length) { php_swoole_error(E_WARNING, "no package eof"); buffer->length = 0; RETURN_FALSE; } else if (buffer->length == buffer->size) { if (buffer->size < protocol->package_max_length) { uint32_t new_size = buffer->size * 2; if (new_size > protocol->package_max_length) { new_size = protocol->package_max_length; } buffer->extend(new_size); } } } } buffer->length = 0; RETURN_FALSE; } else if (cli->open_length_check) { if (cli->buffer == nullptr) { cli->buffer = new String(SW_BUFFER_SIZE_STD); } else { cli->buffer->clear(); } String *buffer = cli->buffer; uint32_t header_len = protocol->package_length_offset + protocol->package_length_size; while (true) { auto retval = cli->recv(buffer->str + buffer->length, header_len - buffer->length, 0); if (retval <= 0) { break; } buffer->length += retval; PacketLength pl{ buffer->str, (uint32_t) buffer->length, }; buf_len = protocol->get_package_length(protocol, cli->socket, &pl); if (buf_len == 0) { continue; } else if (buf_len < 0) { break; } else { break; } } // error package if (buf_len < 0) { RETURN_EMPTY_STRING(); } // empty package else if (buf_len == header_len) { RETURN_STRINGL(buffer->str, header_len); } else if (buf_len > protocol->package_max_length) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "Package is too big. package_length=%d", (int) buf_len); RETURN_EMPTY_STRING(); } else if (buf_len == (zend_long) buffer->length) { RETURN_STRINGL(buffer->str, buffer->length); } else if (buf_len < (zend_long) buffer->length) { RETVAL_STRINGL(buffer->str, buf_len); memmove(buffer->str, buffer->str + buf_len, buffer->length - buf_len); buffer->length -= buf_len; return; } strbuf = zend_string_alloc(buf_len, false); memcpy(strbuf->val, buffer->str, buffer->length); swoole_set_last_error(0); ret = cli->recv(strbuf->val + header_len, buf_len - buffer->length, MSG_WAITALL); if (ret > 0) { ret += header_len; if (ret != buf_len) { ret = 0; } } } else { if (!(flags & MSG_WAITALL) && buf_len > SW_PHP_CLIENT_BUFFER_SIZE) { buf_len = SW_PHP_CLIENT_BUFFER_SIZE; } strbuf = zend_string_alloc(buf_len, false); swoole_set_last_error(0); ret = cli->recv(strbuf->val, buf_len, flags); } if (ret < 0) { php_swoole_sys_error(E_WARNING, "recv() failed"); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); if (strbuf) { zend_string_free(strbuf); } RETURN_FALSE; } else { if (ret == 0) { if (strbuf) { zend_string_free(strbuf); } RETURN_EMPTY_STRING(); } else { strbuf->len = ret; strbuf->val[ret] = 0; RETVAL_STR(strbuf); } } } static PHP_METHOD(swoole_client, isConnected) { Client *cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!cli->socket) { RETURN_FALSE; } RETURN_BOOL(cli->active); } static PHP_METHOD(swoole_client, getsockname) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (cli->socket->get_name() < 0) { php_swoole_sys_error(E_WARNING, "getsockname() failed"); zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), errno); RETURN_FALSE; } array_init(return_value); add_assoc_long(return_value, "port", cli->socket->get_port()); add_assoc_string(return_value, "host", cli->socket->get_addr()); } #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_client, getSocket) { zval *zsocket = client_get_zsocket(ZEND_THIS); if (zsocket) { RETURN_ZVAL(zsocket, 1, 0); } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (cli->keep) { php_swoole_fatal_error(E_WARNING, "the 'getSocket' method can't be used on persistent connection"); RETURN_FALSE; } php_socket *socket_object = php_swoole_convert_to_socket(cli->socket->fd); if (!socket_object) { RETURN_FALSE; } SW_ZVAL_SOCKET(return_value, socket_object); zsocket = sw_zval_dup(return_value); Z_TRY_ADDREF_P(zsocket); client_set_zsocket(ZEND_THIS, zsocket); } #endif static PHP_METHOD(swoole_client, getpeername) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } Address addr; if (cli->get_peer_name(&addr) < 0) { php_swoole_sys_error(E_WARNING, "getpeername() failed"); zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), errno); RETURN_FALSE; } array_init(return_value); add_assoc_long(return_value, "port", addr.get_port()); add_assoc_string(return_value, "host", addr.get_addr()); } static PHP_METHOD(swoole_client, close) { int ret = 1; zend_bool force = false; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(force) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Client *cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli || !cli->socket) { php_swoole_fatal_error(E_WARNING, "client is not connected to the server"); RETURN_FALSE; } if (cli->closed) { php_swoole_error(E_WARNING, "client socket is closed"); RETURN_FALSE; } // Connection error, or short tcp connection. // No keep connection if (force || !cli->keep || cli->socket->catch_error(swoole_get_last_error()) == SW_CLOSE) { ret = cli->close(); php_swoole_client_free(ZEND_THIS, cli); } else { if (cli->keep) { std::queue *q; auto i = long_connections.find(cli->server_id); if (i == long_connections.end()) { q = new std::queue; long_connections[cli->server_id] = q; } else { q = i->second; } q->push(cli); } // unset object php_swoole_client_set_cli(ZEND_THIS, nullptr); } SW_CHECK_RETURN(ret); } bool php_swoole_client_enable_ssl_encryption(Client *cli, zval *zobject) { if (cli->socket->socket_type != SW_SOCK_TCP && cli->socket->socket_type != SW_SOCK_TCP6) { php_swoole_fatal_error(E_WARNING, "cannot use enableSSL"); return false; } if (cli->socket->ssl) { php_swoole_fatal_error(E_WARNING, "SSL has been enabled"); return false; } cli->open_ssl = true; zval *zset = sw_zend_read_property_ex(swoole_client_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (ZVAL_IS_ARRAY(zset)) { php_swoole_client_check_ssl_setting(cli, zset); } return cli->enable_ssl_encrypt() == SW_OK; } static PHP_METHOD(swoole_client, enableSSL) { zval *zcallback = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(zcallback) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zcallback) { zend_throw_exception( swoole_exception_ce, "sync client does not support `onSslReady` callback", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!php_swoole_client_enable_ssl_encryption(cli, ZEND_THIS)) { RETURN_FALSE; } RETURN_BOOL(cli->ssl_handshake() == SW_OK); } static PHP_METHOD(swoole_client, getPeerCert) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!cli->socket->ssl) { php_swoole_fatal_error(E_WARNING, "SSL is not ready"); RETURN_FALSE; } if (!cli->socket->ssl_get_peer_certificate(sw_tg_buffer())) { RETURN_FALSE; } RETURN_SW_STRING(sw_tg_buffer()); } static PHP_METHOD(swoole_client, verifyPeerCert) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!cli->socket->ssl) { php_swoole_fatal_error(E_WARNING, "SSL is not ready"); RETURN_FALSE; } zend_bool allow_self_signed = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_self_signed) == FAILURE) { RETURN_FALSE; } SW_CHECK_RETURN(cli->ssl_verify(allow_self_signed)); } static PHP_METHOD(swoole_client, shutdown) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } long _how; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &_how) == FAILURE) { RETURN_FALSE; } SW_CHECK_RETURN(cli->shutdown(_how)); } PHP_FUNCTION(swoole_client_select) { zval *r_array, *w_array, *e_array; int retval; uint32_t index = 0; double timeout = SW_CLIENT_CONNECT_TIMEOUT; ZEND_PARSE_PARAMETERS_START(3, 4) Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0) Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0) Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END(); int maxevents = SW_MAX(SW_MAX(php_swoole_array_length_safe(r_array), php_swoole_array_length_safe(w_array)), php_swoole_array_length_safe(e_array)); auto *fds = static_cast(ecalloc(maxevents, sizeof(struct pollfd))); if (r_array != nullptr && php_swoole_array_length(r_array) > 0) { index = client_poll_add(r_array, index, fds, maxevents, POLLIN); } if (w_array != nullptr && php_swoole_array_length(w_array) > 0) { index = client_poll_add(w_array, index, fds, maxevents, POLLOUT); } if (e_array != nullptr && php_swoole_array_length(e_array) > 0) { index = client_poll_add(e_array, index, fds, maxevents, POLLHUP); } if (index == 0) { efree(fds); php_swoole_fatal_error(E_WARNING, "no resource arrays were passed to select"); RETURN_FALSE; } do { retval = poll(fds, maxevents, (int) (timeout * 1000)); } while (retval < 0 && errno == EINTR); if (retval == -1) { efree(fds); php_swoole_sys_error(E_WARNING, "unable to poll()"); RETURN_FALSE; } if (r_array != nullptr && php_swoole_array_length(r_array) > 0) { client_poll_wait(r_array, fds, maxevents, retval, POLLIN); } if (w_array != nullptr && php_swoole_array_length(w_array) > 0) { client_poll_wait(w_array, fds, maxevents, retval, POLLOUT); } if (e_array != nullptr && php_swoole_array_length(e_array) > 0) { client_poll_wait(e_array, fds, maxevents, retval, POLLHUP); } efree(fds); RETURN_LONG(retval); } static inline int client_poll_get(const pollfd *fds, int maxevents, int fd) { for (int i = 0; i < maxevents; i++) { if (fds[i].fd == fd) { return i; } } return -1; } static int client_poll_wait(zval *sock_array, const pollfd *fds, int maxevents, int n_event, int revent) { zval *element = nullptr; ulong_t num = 0; if (!ZVAL_IS_ARRAY(sock_array)) { return 0; } zval new_array; array_init(&new_array); zend_ulong num_key; zend_string *key; zval *dest_element; ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(sock_array), num_key, key, element) { int sock = php_swoole_convert_to_fd(element); if (sock < 0) { continue; } int poll_key = client_poll_get(fds, maxevents, sock); if (poll_key == -1) { php_swoole_fatal_error(E_WARNING, "bad fd[%d]", sock); continue; } if (!(fds[poll_key].revents & revent)) { continue; } if (key) { dest_element = zend_hash_add(Z_ARRVAL(new_array), key, element); } else { dest_element = zend_hash_index_update(Z_ARRVAL(new_array), num_key, element); } if (dest_element) { Z_ADDREF_P(dest_element); } num++; } ZEND_HASH_FOREACH_END(); zval_ptr_dtor(sock_array); ZVAL_COPY_VALUE(sock_array, &new_array); return num; } static uint32_t client_poll_add(const zval *sock_array, uint32_t index, struct pollfd *fds, int maxevents, int event) { zval *element = nullptr; if (!ZVAL_IS_ARRAY(sock_array)) { return 0; } int key = -1; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(sock_array), element) int sock = php_swoole_convert_to_fd(element); if (sock < 0) { continue; } if (event != POLLIN) { key = client_poll_get(fds, maxevents, sock); } if (key < 0) { fds[index].fd = sock; fds[index].events = event; index++; } else { fds[key].fd = sock; fds[key].events |= event; } SW_HASHTABLE_FOREACH_END(); return index; } ================================================ FILE: ext-src/swoole_client_async.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_client.h" #include "swoole_mqtt.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_client_async_arginfo.h" END_EXTERN_C() #include "ext/standard/basic_functions.h" using swoole::SocketType; using swoole::network::Address; using swoole::network::Client; using swoole::network::Socket; static PHP_METHOD(swoole_client_async, __construct); static PHP_METHOD(swoole_client_async, __destruct); static PHP_METHOD(swoole_client_async, connect); static PHP_METHOD(swoole_client_async, sleep); static PHP_METHOD(swoole_client_async, wakeup); static PHP_METHOD(swoole_client_async, enableSSL); static PHP_METHOD(swoole_client_async, isConnected); static PHP_METHOD(swoole_client_async, close); static PHP_METHOD(swoole_client_async, on); enum AsyncClientCallbackType { CLIENT_CB_onConnect = 1, CLIENT_CB_onReceive, CLIENT_CB_onClose, CLIENT_CB_onError, CLIENT_CB_onBufferFull, CLIENT_CB_onBufferEmpty, CLIENT_CB_onSSLReady, }; static void client_onConnect(Client *cli); static void client_onReceive(const Client *cli, const char *data, size_t length); static void client_onClose(Client *cli); static void client_onError(Client *cli); static void client_onBufferFull(Client *cli); static void client_onBufferEmpty(Client *cli); zend_class_entry *swoole_client_async_ce; static zend_object_handlers swoole_client_async_handlers; void php_swoole_client_async_free_object(const ClientObject *client_obj) { if (client_obj->async->onConnect) { sw_callable_free(client_obj->async->onConnect); } if (client_obj->async->onReceive) { sw_callable_free(client_obj->async->onReceive); } if (client_obj->async->onClose) { sw_callable_free(client_obj->async->onClose); } if (client_obj->async->onError) { sw_callable_free(client_obj->async->onError); } if (client_obj->async->onBufferFull) { sw_callable_free(client_obj->async->onBufferFull); } if (client_obj->async->onBufferEmpty) { sw_callable_free(client_obj->async->onBufferEmpty); } if (client_obj->async->onSSLReady) { sw_callable_free(client_obj->async->onSSLReady); } delete client_obj->async; } static sw_inline void client_execute_callback(zval *zobject, AsyncClientCallbackType type) { auto client_obj = php_swoole_client_fetch_object(zobject); const char *callback_name; zend::Callable *cb; switch (type) { case CLIENT_CB_onConnect: callback_name = "onConnect"; cb = client_obj->async->onConnect; break; case CLIENT_CB_onError: callback_name = "onError"; cb = client_obj->async->onError; break; case CLIENT_CB_onClose: callback_name = "onClose"; cb = client_obj->async->onClose; break; case CLIENT_CB_onBufferFull: callback_name = "onBufferFull"; cb = client_obj->async->onBufferFull; break; case CLIENT_CB_onBufferEmpty: callback_name = "onBufferEmpty"; cb = client_obj->async->onBufferEmpty; break; case CLIENT_CB_onSSLReady: callback_name = "onSSLReady"; cb = client_obj->async->onSSLReady; break; default: abort(); return; } if (!cb) { php_swoole_fatal_error(E_WARNING, "%s has no %s callback", SW_Z_OBJCE_NAME_VAL_P(zobject), callback_name); return; } if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, cb->ptr(), 1, zobject, nullptr) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "%s->%s handler error", SW_Z_OBJCE_NAME_VAL_P(zobject), callback_name); } } static sw_inline void client_execute_callback_and_dtor(Client *cli, zval *zobject, AsyncClientCallbackType type) { client_execute_callback(zobject, type); php_swoole_client_free(zobject, cli); zval_delref_p(zobject); } // clang-format off static const zend_function_entry swoole_client_async_methods[] = { PHP_ME(swoole_client_async, __construct, arginfo_class_Swoole_Async_Client___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, __destruct, arginfo_class_Swoole_Async_Client___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, connect, arginfo_class_Swoole_Async_Client_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, sleep, arginfo_class_Swoole_Async_Client_sleep, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, wakeup, arginfo_class_Swoole_Async_Client_wakeup, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_client_async, pause, sleep, arginfo_class_Swoole_Async_Client_sleep, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_client_async, resume, wakeup, arginfo_class_Swoole_Async_Client_wakeup, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, enableSSL, arginfo_class_Swoole_Async_Client_enableSSL, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, isConnected, arginfo_class_Swoole_Async_Client_isConnected, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, close, arginfo_class_Swoole_Async_Client_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_async, on, arginfo_class_Swoole_Async_Client_on, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_client_async_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX( swoole_client_async, "Swoole\\Async\\Client", nullptr, swoole_client_async_methods, swoole_client); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client_async); SW_SET_CLASS_CLONEABLE(swoole_client_async, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client_async, sw_zend_class_unset_property_deny); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onConnect"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onError"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onReceive"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onClose"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onBufferFull"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onBufferEmpty"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onSSLReady"), ZEND_ACC_PRIVATE); } static void client_onReceive(const Client *cli, const char *data, size_t length) { auto zobject = static_cast(cli->object); auto client_obj = php_swoole_client_fetch_object(zobject); zend_fcall_info_cache *fci_cache = client_obj->async->onReceive->ptr(); zval args[2]; args[0] = *zobject; ZVAL_STRINGL(&args[1], data, length); if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, fci_cache, 2, args, nullptr) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "%s->onReceive handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } zval_ptr_dtor(&args[1]); } static void client_onConnect(Client *cli) { auto zobject = static_cast(cli->object); if (cli->ssl_wait_handshake) { cli->ssl_wait_handshake = false; client_execute_callback(zobject, CLIENT_CB_onSSLReady); return; } client_execute_callback(zobject, CLIENT_CB_onConnect); } static void client_onClose(Client *cli) { auto zobject = static_cast(cli->object); client_execute_callback_and_dtor(cli, zobject, CLIENT_CB_onClose); } static void client_onError(Client *cli) { auto zobject = static_cast(cli->object); zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); client_execute_callback_and_dtor(cli, zobject, CLIENT_CB_onError); } static void client_onBufferFull(Client *cli) { auto zobject = static_cast(cli->object); client_execute_callback(zobject, CLIENT_CB_onBufferFull); } static void client_onBufferEmpty(Client *cli) { auto zobject = static_cast(cli->object); client_execute_callback(zobject, CLIENT_CB_onBufferEmpty); } static PHP_METHOD(swoole_client_async, __construct) { zend_long type = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &type) == FAILURE) { zend_throw_error(nullptr, "socket type param is required"); RETURN_FALSE; } int client_type = php_swoole_client_get_type(type); if (client_type < SW_SOCK_TCP || client_type > SW_SOCK_UNIX_DGRAM) { const char *space, *class_name = get_active_class_name(&space); zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", class_name, space, get_active_function_name(), 1, type); RETURN_FALSE; } php_swoole_check_reactor(); zend_update_property_long(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("type"), type); RETURN_TRUE; } static PHP_METHOD(swoole_client_async, __destruct) { SW_PREVENT_USER_DESTRUCT(); const auto cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli) { return; } if (!cli->closed) { cli->close(); } php_swoole_client_free(ZEND_THIS, cli); } static Client *php_swoole_client_async_new(zval *zobject, char *host, int host_len, int port) { zval *ztype = sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TYPE), 0); if (ztype == nullptr || ZVAL_IS_NULL(ztype)) { php_swoole_fatal_error(E_ERROR, "failed to get client type"); return nullptr; } long type = Z_LVAL_P(ztype); int client_type = php_swoole_client_get_type(type); if (Socket::is_tcp(static_cast(client_type)) && !Address::verify_port(port, true)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); swoole_set_last_error(SW_ERROR_INVALID_PARAMS); return nullptr; } auto *cli = new Client(php_swoole_client_get_type(type), true); if (!cli->ready()) { php_swoole_sys_error(E_WARNING, "failed to create client"); zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); delete cli; return nullptr; } zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("sock"), cli->socket->fd); if (type & SW_SOCK_SSL) { cli->enable_ssl_encrypt(); } return cli; } static PHP_METHOD(swoole_client_async, connect) { char *host; size_t host_len; zend_long port = 0; double timeout = SW_CLIENT_CONNECT_TIMEOUT; zend_long sock_flag = 0; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(sock_flag) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (host_len == 0) { php_swoole_fatal_error(E_WARNING, "The host is empty"); RETURN_FALSE; } auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); if (client_obj->cli) { php_swoole_fatal_error(E_WARNING, "connection to the server has already been established"); RETURN_FALSE; } if (!client_obj->async) { php_swoole_fatal_error(E_WARNING, "async client is not initialized"); RETURN_FALSE; } auto cli = php_swoole_client_async_new(ZEND_THIS, host, host_len, port); if (cli == nullptr) { RETURN_FALSE; } zval *zset = sw_zend_read_property(swoole_client_async_ce, ZEND_THIS, ZEND_STRL("setting"), 0); if (zset && ZVAL_IS_ARRAY(zset)) { php_swoole_client_check_setting(cli, zset); } if (!client_obj->async->onReceive) { php_swoole_fatal_error(E_ERROR, "no 'onReceive' callback function"); RETURN_FALSE; } if (cli->get_socket()->is_stream()) { if (!client_obj->async->onConnect) { php_swoole_fatal_error(E_ERROR, "no 'onConnect' callback function"); RETURN_FALSE; } if (!client_obj->async->onError) { php_swoole_fatal_error(E_ERROR, "no 'onError' callback function"); RETURN_FALSE; } if (!client_obj->async->onClose) { php_swoole_fatal_error(E_ERROR, "no 'onClose' callback function"); RETURN_FALSE; } cli->onConnect = client_onConnect; cli->onClose = client_onClose; cli->onError = client_onError; cli->onReceive = client_onReceive; if (client_obj->async->onBufferFull) { cli->onBufferFull = client_onBufferFull; } if (client_obj->async->onBufferEmpty) { cli->onBufferEmpty = client_onBufferEmpty; } } else { if (client_obj->async->onConnect) { cli->onConnect = client_onConnect; } if (client_obj->async->onClose) { cli->onClose = client_onClose; } if (client_obj->async->onError) { cli->onError = client_onError; } cli->onReceive = client_onReceive; } client_obj->async->_zobject = *ZEND_THIS; client_obj->cli = cli; cli->object = &client_obj->async->_zobject; zval_addref_p(ZEND_THIS); // nonblock async if (cli->connect(host, port, timeout, sock_flag) < 0) { if (errno == 0) { const auto error = swoole_get_last_error(); if (error == SW_ERROR_DNSLOOKUP_RESOLVE_FAILED) { php_swoole_error(E_WARNING, "connect to server[%s:%d] failed. Error: %s[%d]", host, (int) port, swoole_strerror(error), error); } zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), error); } else { php_swoole_sys_error(E_WARNING, "connect to server[%s:%d] failed", host, (int) port); zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), errno); } auto _cli = php_swoole_client_get_cli(ZEND_THIS); if (_cli && _cli->onError == nullptr) { php_swoole_client_free(ZEND_THIS, _cli); zval_delref_p(ZEND_THIS); } RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client_async, isConnected) { auto cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!cli->socket) { RETURN_FALSE; } RETURN_BOOL(cli->active); } static PHP_METHOD(swoole_client_async, close) { auto cli = php_swoole_client_get_cli(ZEND_THIS); if (!cli) { php_swoole_fatal_error(E_WARNING, "client is not connected to the server"); RETURN_FALSE; } SW_CHECK_RETURN(cli->close()); } static PHP_METHOD(swoole_client_async, on) { char *cb_name; size_t cb_name_len; zval *zcallback; if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &cb_name, &cb_name_len, &zcallback) == FAILURE) { RETURN_FALSE; } auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); auto cb = sw_callable_create(zcallback); if (!cb) { return; } if (!client_obj->async) { client_obj->async = new AsyncClientObject(); } if (strncasecmp("connect", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onConnect"), zcallback); if (client_obj->async->onConnect) { sw_callable_free(client_obj->async->onConnect); } client_obj->async->onConnect = cb; } else if (strncasecmp("receive", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onReceive"), zcallback); if (client_obj->async->onReceive) { sw_callable_free(client_obj->async->onReceive); } client_obj->async->onReceive = cb; } else if (strncasecmp("close", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onClose"), zcallback); if (client_obj->async->onClose) { sw_callable_free(client_obj->async->onClose); } client_obj->async->onClose = cb; } else if (strncasecmp("error", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onError"), zcallback); if (client_obj->async->onError) { sw_callable_free(client_obj->async->onError); } client_obj->async->onError = cb; } else if (strncasecmp("bufferFull", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onBufferFull"), zcallback); if (client_obj->async->onBufferFull) { sw_callable_free(client_obj->async->onBufferFull); } client_obj->async->onBufferFull = cb; } else if (strncasecmp("bufferEmpty", cb_name, cb_name_len) == 0) { zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onBufferEmpty"), zcallback); if (client_obj->async->onBufferEmpty) { sw_callable_free(client_obj->async->onBufferEmpty); } client_obj->async->onBufferEmpty = cb; } else { php_swoole_fatal_error(E_WARNING, "Unknown event callback type name '%s'", cb_name); sw_callable_free(cb); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client_async, sleep) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } SW_CHECK_RETURN(cli->sleep()); } static PHP_METHOD(swoole_client_async, wakeup) { Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } SW_CHECK_RETURN(cli->wakeup()); } static PHP_METHOD(swoole_client_async, enableSSL) { zval *zcallback = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(zcallback) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zcallback == nullptr) { zend_throw_exception(swoole_exception_ce, "require `onSslReady` callback", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } if (!php_swoole_client_enable_ssl_encryption(cli, ZEND_THIS)) { RETURN_FALSE; } auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); if (swoole_event_set(cli->socket, SW_EVENT_WRITE) < 0) { RETURN_FALSE; } if (client_obj->async->onSSLReady) { sw_callable_free(client_obj->async->onSSLReady); } auto cb = sw_callable_create(zcallback); if (!cb) { RETURN_FALSE; } zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onSSLReady"), zcallback); client_obj->async->onSSLReady = cb; cli->ssl_wait_handshake = true; cli->socket->ssl_state = SW_SSL_STATE_WAIT_STREAM; RETURN_TRUE; } ================================================ FILE: ext-src/swoole_client_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_client.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_client_coro_arginfo.h" END_EXTERN_C() using swoole::SSLContext; using swoole::String; using swoole::network::Address; static zend_class_entry *swoole_client_coro_ce; static zend_object_handlers swoole_client_coro_handlers; struct ClientCoroObject { SocketImpl *socket; zval zsocket; /* safety zval */ zval zobject; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_client_coro, __construct); static PHP_METHOD(swoole_client_coro, __destruct); static PHP_METHOD(swoole_client_coro, set); static PHP_METHOD(swoole_client_coro, connect); static PHP_METHOD(swoole_client_coro, recv); static PHP_METHOD(swoole_client_coro, peek); static PHP_METHOD(swoole_client_coro, send); static PHP_METHOD(swoole_client_coro, sendfile); static PHP_METHOD(swoole_client_coro, sendto); static PHP_METHOD(swoole_client_coro, recvfrom); static PHP_METHOD(swoole_client_coro, enableSSL); static PHP_METHOD(swoole_client_coro, getPeerCert); static PHP_METHOD(swoole_client_coro, verifyPeerCert); static PHP_METHOD(swoole_client_coro, exportSocket); static PHP_METHOD(swoole_client_coro, isConnected); static PHP_METHOD(swoole_client_coro, getsockname); static PHP_METHOD(swoole_client_coro, getpeername); static PHP_METHOD(swoole_client_coro, close); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_client_coro_methods[] = { PHP_ME(swoole_client_coro, __construct, arginfo_class_Swoole_Coroutine_Client___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, __destruct, arginfo_class_Swoole_Coroutine_Client___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, set, arginfo_class_Swoole_Coroutine_Client_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, connect, arginfo_class_Swoole_Coroutine_Client_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, recv, arginfo_class_Swoole_Coroutine_Client_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, peek, arginfo_class_Swoole_Coroutine_Client_peek, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, send, arginfo_class_Swoole_Coroutine_Client_send, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, sendfile, arginfo_class_Swoole_Coroutine_Client_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, sendto, arginfo_class_Swoole_Coroutine_Client_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, recvfrom, arginfo_class_Swoole_Coroutine_Client_recvfrom, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, enableSSL, arginfo_class_Swoole_Coroutine_Client_enableSSL, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getPeerCert, arginfo_class_Swoole_Coroutine_Client_getPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, verifyPeerCert, arginfo_class_Swoole_Coroutine_Client_verifyPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, isConnected, arginfo_class_Swoole_Coroutine_Client_isConnected, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getsockname, arginfo_class_Swoole_Coroutine_Client_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getpeername, arginfo_class_Swoole_Coroutine_Client_getpeername, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, close, arginfo_class_Swoole_Coroutine_Client_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, exportSocket, arginfo_class_Swoole_Coroutine_Client_exportSocket, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on static ClientCoroObject *client_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_client_coro_handlers.offset); } static ClientCoroObject *client_coro_get_client(const zval *zobject) { return client_coro_fetch_object(Z_OBJ_P(zobject)); } static SocketImpl *client_coro_get_socket(const zval *zobject) { return client_coro_get_client(zobject)->socket; } static void client_coro_free_object(zend_object *object) { ClientCoroObject *client = client_coro_fetch_object(object); if (client->socket) { client->socket->close(); } zend_object_std_dtor(&client->std); } #define CLIENT_CORO_GET_SOCKET_SAFE(__sock) \ SW_CLIENT_GET_SOCKET_SAFE(__sock, &client_coro_get_client(ZEND_THIS)->zsocket); \ if (!__sock) { \ php_swoole_socket_set_error_properties( \ ZEND_THIS, SW_ERROR_CLIENT_NO_CONNECTION, swoole_strerror(SW_ERROR_CLIENT_NO_CONNECTION)); \ RETURN_FALSE; \ } static zend_object *client_coro_create_object(zend_class_entry *ce) { auto *object = static_cast(zend_object_alloc(sizeof(ClientCoroObject), ce)); zend_object_std_init(&object->std, ce); object_properties_init(&object->std, ce); object->std.handlers = &swoole_client_coro_handlers; ZVAL_OBJ(&object->zobject, &object->std); ZVAL_NULL(&object->zsocket); return &object->std; } static void client_coro_socket_dtor(ClientCoroObject *client) { if (client->socket->protocol.private_data_1) { sw_callable_free(client->socket->protocol.private_data_1); client->socket->protocol.private_data_1 = nullptr; } client->socket = nullptr; zend_update_property_null(Z_OBJCE_P(&client->zobject), SW_Z8_OBJ_P(&client->zobject), ZEND_STRL("socket")); zend_update_property_bool(Z_OBJCE_P(&client->zobject), SW_Z8_OBJ_P(&client->zobject), ZEND_STRL("connected"), 0); zval_ptr_dtor(&client->zsocket); ZVAL_NULL(&client->zsocket); } static SocketImpl *client_coro_create_socket(zval *zobject, zend_long type) { auto socket_type = php_swoole_client_get_type(type); auto object = php_swoole_create_socket(socket_type); if (UNEXPECTED(!object)) { php_swoole_socket_set_error_properties(zobject, errno); return nullptr; } auto client = client_coro_get_client(zobject); ZVAL_OBJ(&client->zsocket, object); auto *socket = php_swoole_get_socket(&client->zsocket); socket->set_dtor([client](CoSocket *_socket) { client_coro_socket_dtor(client); }); zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("fd"), socket->get_fd()); zend_update_property(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &client->zsocket); socket->set_buffer_allocator(sw_zend_string_allocator()); socket->set_zero_copy(true); if ((type & SW_SOCK_SSL) && !socket->enable_ssl_encrypt()) { php_swoole_socket_set_error_properties(zobject, EISCONN); client_coro_socket_dtor(client); return nullptr; } return socket; } void php_swoole_client_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_client_coro, "Swoole\\Coroutine\\Client", "Co\\Client", swoole_client_coro_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client_coro); SW_SET_CLASS_CLONEABLE(swoole_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_client_coro, client_coro_create_object, client_coro_free_object, ClientCoroObject, std); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_client_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("type"), SW_SOCK_TCP, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); zend_declare_class_constant_long(swoole_client_coro_ce, ZEND_STRL("MSG_OOB"), MSG_OOB); zend_declare_class_constant_long(swoole_client_coro_ce, ZEND_STRL("MSG_PEEK"), MSG_PEEK); zend_declare_class_constant_long(swoole_client_coro_ce, ZEND_STRL("MSG_DONTWAIT"), MSG_DONTWAIT); zend_declare_class_constant_long(swoole_client_coro_ce, ZEND_STRL("MSG_WAITALL"), MSG_WAITALL); } static sw_inline SocketImpl *client_coro_get_socket_for_connect(zval *zobject, int port) { auto client = client_coro_get_client(zobject); if (client->socket) { php_swoole_socket_set_error_properties(zobject, EISCONN, strerror(EISCONN)); return nullptr; } zval *ztype = sw_zend_read_property(swoole_client_coro_ce, zobject, ZEND_STRL("type"), 1); auto socket_type = php_swoole_client_get_type(zval_get_long(ztype)); if (NetSocket::is_tcp(socket_type) && !Address::verify_port(port, true)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); return nullptr; } auto sock = client_coro_create_socket(zobject, zval_get_long(ztype)); if (!sock) { return nullptr; } client->socket = sock; zval *zset = sw_zend_read_property_ex(swoole_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (zset && ZVAL_IS_ARRAY(zset)) { php_swoole_socket_set(sock, zset); } return sock; } static PHP_METHOD(swoole_client_coro, __construct) { zend_long type = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto socket_type = php_swoole_client_get_type(type); if (socket_type < SW_SOCK_TCP || socket_type > SW_SOCK_UNIX_DGRAM) { const char *space, *class_name = get_active_class_name(&space); zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", class_name, space, get_active_function_name(), 1, type); RETURN_FALSE; } php_swoole_check_reactor(); zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("type"), type); RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, __destruct) {} static PHP_METHOD(swoole_client_coro, set) { zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_array_length(zset) == 0) { RETURN_FALSE; } zval *zsetting = sw_zend_read_and_convert_property_array(swoole_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); SocketImpl *cli = client_coro_get_socket(ZEND_THIS); if (cli) { RETURN_BOOL(php_swoole_socket_set(cli, zset)); } RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, connect) { char *host; size_t host_len; zend_long port = 0; double timeout = 0; zend_long sock_flag = 0; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(sock_flag) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (host_len == 0) { php_swoole_fatal_error(E_WARNING, "The host is empty"); RETURN_FALSE; } SocketImpl *socket = client_coro_get_socket_for_connect(ZEND_THIS, port); if (!socket) { RETURN_FALSE; } socket->set_timeout(timeout, SW_TIMEOUT_CONNECT); if (!socket->connect(host, port, sock_flag)) { php_swoole_socket_set_error_properties(ZEND_THIS, socket); socket->close(); RETURN_FALSE; } socket->set_timeout(timeout, SW_TIMEOUT_RDWR); zend_update_property_bool(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 1); RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, send) { char *data; size_t data_len; double timeout = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(data, data_len) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (data_len == 0) { php_swoole_fatal_error(E_WARNING, "data to send is empty"); RETURN_FALSE; } CLIENT_CORO_GET_SOCKET_SAFE(cli); SocketImpl::TimeoutSetter ts(cli, timeout, SW_TIMEOUT_WRITE); ssize_t ret = cli->send_all(data, data_len); if (ret < 0) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } if ((size_t) ret < data_len && cli->errCode) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); } RETURN_LONG(ret); } static PHP_METHOD(swoole_client_coro, sendto) { char *host; size_t host_len; zend_long port; char *data; size_t len; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STRING(host, host_len) Z_PARAM_LONG(port) Z_PARAM_STRING(data, len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (len == 0) { RETURN_FALSE; } SocketImpl *socket = nullptr; const auto client = client_coro_get_client(ZEND_THIS); if (client->socket == nullptr) { socket = client_coro_get_socket_for_connect(ZEND_THIS, 0); } else { socket = client->socket; } if (!socket) { RETURN_FALSE; } if (socket->get_socket()->is_tcp() && !Address::verify_port(port, true)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); RETURN_FALSE; } auto ret = socket->sendto(std::string(host, host_len), port, data, len); if (ret < 0) { php_swoole_socket_set_error_properties(ZEND_THIS, socket); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, recvfrom) { zend_long length; zval *address, *port; if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz/|z/", &length, &address, &port) == FAILURE) { RETURN_FALSE; } if (length <= 0) { RETURN_FALSE; } SocketImpl *socket = nullptr; auto client = client_coro_get_client(ZEND_THIS); if (client->socket == nullptr) { socket = client_coro_get_socket_for_connect(ZEND_THIS, 0); } else { socket = client->socket; } if (!socket) { RETURN_FALSE; } zend_string *retval = zend_string_alloc(length, false); ssize_t n_bytes = socket->recvfrom(ZSTR_VAL(retval), length); if (n_bytes < 0) { zend_string_free(retval); php_swoole_socket_set_error_properties(ZEND_THIS, socket); RETURN_FALSE; } else { zval_ptr_dtor(address); ZVAL_STRING(address, socket->get_addr()); if (port) { zval_ptr_dtor(port); ZVAL_LONG(port, socket->get_port()); } ZSTR_LEN(retval) = n_bytes; ZSTR_VAL(retval)[ZSTR_LEN(retval)] = '\0'; RETURN_STR(retval); } } static PHP_METHOD(swoole_client_coro, sendfile) { char *file; size_t file_len; zend_long offset = 0; zend_long length = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &file, &file_len, &offset, &length) == FAILURE) { RETURN_FALSE; } if (file_len == 0) { php_swoole_fatal_error(E_WARNING, "file to send is empty"); RETURN_FALSE; } CLIENT_CORO_GET_SOCKET_SAFE(cli); // only stream socket can sendfile if (!(cli->get_type() == SW_SOCK_TCP || cli->get_type() == SW_SOCK_TCP6 || cli->get_type() == SW_SOCK_UNIX_STREAM)) { zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), EINVAL); zend_update_property_string( swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), "dgram socket cannot use sendfile"); RETURN_FALSE; } if (!cli->sendfile(file, offset, length)) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETVAL_FALSE; } else { RETVAL_TRUE; } } static PHP_METHOD(swoole_client_coro, recv) { double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); CLIENT_CORO_GET_SOCKET_SAFE(cli); ssize_t retval; zend_string *result = nullptr; if (cli->open_length_check || cli->open_eof_check) { retval = cli->recv_packet(timeout); if (retval > 0) { auto strval = cli->pop_packet(); if (strval == nullptr) { retval = -1; cli->set_err(ENOMEM); } else { result = zend::fetch_zend_string_by_val(strval); } } } else { result = zend_string_alloc(SW_PHP_CLIENT_BUFFER_SIZE - sizeof(zend_string), false); SocketImpl::TimeoutSetter ts(cli, timeout, SW_TIMEOUT_READ); retval = cli->recv(ZSTR_VAL(result), SW_PHP_CLIENT_BUFFER_SIZE - sizeof(zend_string)); if (retval <= 0) { zend_string_free(result); } } if (retval < 0) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } else if (retval == 0) { RETURN_EMPTY_STRING(); } else { ZSTR_VAL(result)[retval] = '\0'; ZSTR_LEN(result) = retval; RETURN_STR(result); } } static PHP_METHOD(swoole_client_coro, peek) { zend_long buf_len = SW_PHP_CLIENT_BUFFER_SIZE; char *buf = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(buf_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); CLIENT_CORO_GET_SOCKET_SAFE(cli); buf = static_cast(emalloc((size_t) buf_len + 1)); auto ret = cli->peek(buf, buf_len); if (ret < 0) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); efree(buf); RETURN_FALSE; } else { buf[ret] = 0; RETVAL_STRINGL(buf, ret); efree(buf); } } static PHP_METHOD(swoole_client_coro, isConnected) { SocketImpl *cli = client_coro_get_socket(ZEND_THIS); if (cli && cli->is_connected()) { RETURN_TRUE; } else { RETURN_FALSE; } } static PHP_METHOD(swoole_client_coro, getsockname) { CLIENT_CORO_GET_SOCKET_SAFE(cli); if (!cli->getsockname()) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } array_init(return_value); zval zaddress; ZVAL_STRING(&zaddress, cli->get_addr()); add_assoc_zval(return_value, "host", &zaddress); /* backward compatibility */ zend::array_set(return_value, SW_STRL("address"), &zaddress); add_assoc_long(return_value, "port", cli->get_port()); } /** * export Swoole\Coroutine\Socket object */ static PHP_METHOD(swoole_client_coro, exportSocket) { auto cli = client_coro_get_client(ZEND_THIS); RETURN_ZVAL(&cli->zsocket, 1, 0); } static PHP_METHOD(swoole_client_coro, getpeername) { CLIENT_CORO_GET_SOCKET_SAFE(cli); Address sa; if (!cli->getpeername(&sa)) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } array_init(return_value); zval zaddress; ZVAL_STRING(&zaddress, sa.get_addr()); add_assoc_zval(return_value, "host", &zaddress); /* backward compatibility */ Z_ADDREF(zaddress); add_assoc_zval(return_value, "address", &zaddress); add_assoc_long(return_value, "port", sa.get_port()); } static PHP_METHOD(swoole_client_coro, close) { CLIENT_CORO_GET_SOCKET_SAFE(_socket); zend_update_property_bool(Z_OBJCE_P(ZEND_THIS), SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 0); if (!_socket->close()) { php_swoole_socket_set_error_properties(ZEND_THIS, _socket); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, enableSSL) { CLIENT_CORO_GET_SOCKET_SAFE(cli); if (cli->get_type() != SW_SOCK_TCP && cli->get_type() != SW_SOCK_TCP6) { php_swoole_socket_set_error_properties(ZEND_THIS, ESOCKTNOSUPPORT); RETURN_FALSE; } if (cli->get_ssl()) { php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); RETURN_FALSE; } if (!cli->enable_ssl_encrypt()) { php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); RETURN_FALSE; } zval *zset = sw_zend_read_property_ex(swoole_client_coro_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (php_swoole_array_length_safe(zset) > 0) { php_swoole_socket_set_ssl(cli, zset); } if (!cli->ssl_handshake()) { php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, getPeerCert) { CLIENT_CORO_GET_SOCKET_SAFE(cli); if (!cli->get_ssl()) { php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); RETURN_FALSE; } if (!cli->get_socket()->ssl_get_peer_certificate(sw_tg_buffer())) { RETURN_FALSE; } RETURN_SW_STRING(sw_tg_buffer()); } static PHP_METHOD(swoole_client_coro, verifyPeerCert) { CLIENT_CORO_GET_SOCKET_SAFE(cli); if (!cli->get_ssl()) { php_swoole_socket_set_error_properties(ZEND_THIS, ENOTCONN); RETURN_FALSE; } zend_bool allow_self_signed = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_self_signed) == FAILURE) { RETURN_FALSE; } RETURN_BOOL(cli->ssl_verify(allow_self_signed)); } ================================================ FILE: ext-src/swoole_coroutine.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | shiguangqi | | Twosee | | Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_coroutine_system.h" #include "swoole_server.h" #include "swoole_signal.h" #include "swoole_async.h" #include "swoole_iouring.h" BEGIN_EXTERN_C() #include "zend_builtin_functions.h" #include "ext/standard/basic_functions.h" #include "ext/spl/spl_array.h" #include "stubs/php_swoole_coroutine_arginfo.h" END_EXTERN_C() #include #include /** * The coroutine canceled exception must be explicitly caught in the php code. * If the underlying layer implicitly catches this exception, `Co::cancel()` may be abused, * resulting in transactional problems and serious bugs. */ #define SW_RECOVER_CANCELED_EXCEPTION 0 #define INVALID_PTR -1 using std::unordered_map; using swoole::Coroutine; using swoole::PHPContext; using swoole::PHPCoroutine; using swoole::coroutine::System; enum sw_exit_flags { SW_EXIT_IN_COROUTINE = 1 << 1, SW_EXIT_IN_SERVER = 1 << 2 }; SW_THREAD_LOCAL bool PHPCoroutine::activated = false; SW_THREAD_LOCAL zend_array *PHPCoroutine::options = nullptr; SW_THREAD_LOCAL PHPCoroutine::Config PHPCoroutine::config{ SW_DEFAULT_MAX_CORO_NUM, 0, false, true, }; SW_THREAD_LOCAL PHPContext PHPCoroutine::main_context{}; SW_THREAD_LOCAL std::thread PHPCoroutine::interrupt_thread; SW_THREAD_LOCAL bool PHPCoroutine::interrupt_thread_running = false; extern void php_swoole_load_library(); static SW_THREAD_LOCAL zend_atomic_bool *zend_vm_interrupt = nullptr; static SW_THREAD_LOCAL unordered_map user_yield_coros; #if PHP_VERSION_ID < 80400 static user_opcode_handler_t ori_exit_handler = nullptr; #endif static user_opcode_handler_t ori_begin_silence_handler = nullptr; static user_opcode_handler_t ori_end_silence_handler = nullptr; static void (*orig_interrupt_function)(zend_execute_data *execute_data) = nullptr; static zend_class_entry *swoole_coroutine_util_ce; static zend_class_entry *swoole_exit_exception_ce; static zend_object_handlers swoole_exit_exception_handlers; static zend_class_entry *swoole_coroutine_iterator_ce; static zend_class_entry *swoole_coroutine_context_ce; static zend_class_entry *swoole_coroutine_canceled_exception_ce; static zend_object_handlers swoole_coroutine_canceled_exception_handlers; static zend_class_entry *swoole_coroutine_timeout_exception_ce; static zend_object_handlers swoole_coroutine_timeout_exception_handlers; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_coroutine, exists); static PHP_METHOD(swoole_coroutine, yield); static PHP_METHOD(swoole_coroutine, resume); static PHP_METHOD(swoole_coroutine, join); static PHP_METHOD(swoole_coroutine, cancel); static PHP_METHOD(swoole_coroutine, setTimeLimit); static PHP_METHOD(swoole_coroutine, isCanceled); static PHP_METHOD(swoole_coroutine, stats); static PHP_METHOD(swoole_coroutine, getCid); static PHP_METHOD(swoole_coroutine, getPcid); static PHP_METHOD(swoole_coroutine, getContext); static PHP_METHOD(swoole_coroutine, getBackTrace); static PHP_METHOD(swoole_coroutine, printBackTrace); static PHP_METHOD(swoole_coroutine, getElapsed); static PHP_METHOD(swoole_coroutine, getStackUsage); static PHP_METHOD(swoole_coroutine, list); static PHP_METHOD(swoole_coroutine, enableScheduler); static PHP_METHOD(swoole_coroutine, disableScheduler); #ifdef SW_CORO_TIME static PHP_METHOD(swoole_coroutine, getExecuteTime); #endif SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_coroutine_methods[] = { /** * Coroutine Core API */ ZEND_FENTRY(create, ZEND_FN(swoole_coroutine_create), arginfo_class_Swoole_Coroutine_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(defer, ZEND_FN(swoole_coroutine_defer), arginfo_class_Swoole_Coroutine_defer, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_scheduler, set, arginfo_class_Swoole_Coroutine_set, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_scheduler, getOptions, arginfo_class_Swoole_Coroutine_getOptions, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, exists, arginfo_class_Swoole_Coroutine_exists, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, yield, arginfo_class_Swoole_Coroutine_yield, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, cancel, arginfo_class_Swoole_Coroutine_cancel, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, join, arginfo_class_Swoole_Coroutine_join, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, isCanceled, arginfo_class_Swoole_Coroutine_isCanceled, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, setTimeLimit, arginfo_class_Swoole_Coroutine_setTimeLimit, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_MALIAS(swoole_coroutine, suspend, yield, arginfo_class_Swoole_Coroutine_suspend, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, resume, arginfo_class_Swoole_Coroutine_resume, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, stats, arginfo_class_Swoole_Coroutine_stats, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getCid, arginfo_class_Swoole_Coroutine_getCid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_MALIAS(swoole_coroutine, getuid, getCid, arginfo_class_Swoole_Coroutine_getuid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getPcid, arginfo_class_Swoole_Coroutine_getPcid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getContext, arginfo_class_Swoole_Coroutine_getContext, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getBackTrace, arginfo_class_Swoole_Coroutine_getBackTrace, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, printBackTrace, arginfo_class_Swoole_Coroutine_printBackTrace, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getElapsed, arginfo_class_Swoole_Coroutine_getElapsed, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, getStackUsage, arginfo_class_Swoole_Coroutine_getStackUsage, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, list, arginfo_class_Swoole_Coroutine_list, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_MALIAS(swoole_coroutine, listCoroutines, list, arginfo_class_Swoole_Coroutine_listCoroutines, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, enableScheduler, arginfo_class_Swoole_Coroutine_enableScheduler, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, disableScheduler, arginfo_class_Swoole_Coroutine_disableScheduler, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #ifdef SW_CORO_TIME PHP_ME(swoole_coroutine, getExecuteTime, arginfo_class_Swoole_Coroutine_getExecuteTime, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #endif /** * Coroutine System API */ ZEND_FENTRY(gethostbyname, ZEND_FN(swoole_coroutine_gethostbyname), arginfo_class_Swoole_Coroutine_System_gethostbyname, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(dnsLookup, ZEND_FN(swoole_async_dns_lookup_coro), arginfo_class_Swoole_Coroutine_System_dnsLookup, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, exec, arginfo_class_Swoole_Coroutine_System_exec, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, sleep, arginfo_class_Swoole_Coroutine_System_sleep, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, getaddrinfo, arginfo_class_Swoole_Coroutine_System_getaddrinfo, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, statvfs, arginfo_class_Swoole_Coroutine_System_statvfs, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, readFile, arginfo_class_Swoole_Coroutine_System_readFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, writeFile, arginfo_class_Swoole_Coroutine_System_writeFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, wait, arginfo_class_Swoole_Coroutine_System_wait, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitPid, arginfo_class_Swoole_Coroutine_System_waitPid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitSignal, arginfo_class_Swoole_Coroutine_System_waitSignal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitEvent, arginfo_class_Swoole_Coroutine_System_waitEvent, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on /** * Exit Exception */ static PHP_METHOD(swoole_exit_exception, getFlags); static PHP_METHOD(swoole_exit_exception, getStatus); // clang-format off static constexpr zend_function_entry swoole_exit_exception_methods[] = { PHP_ME(swoole_exit_exception, getFlags, arginfo_class_Swoole_ExitException_getFlags, ZEND_ACC_PUBLIC) PHP_ME(swoole_exit_exception, getStatus, arginfo_class_Swoole_ExitException_getStatus, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on #if PHP_VERSION_ID < 80400 static int coro_exit_handler(zend_execute_data *execute_data) { zval ex; zend_long flags = 0; if (Coroutine::get_current()) { flags |= SW_EXIT_IN_COROUTINE; } if (sw_server() && sw_server()->is_started()) { flags |= SW_EXIT_IN_SERVER; } if (flags) { const zend_op *opline = EX(opline); zval _exit_status{}; zval *exit_status = nullptr; if (opline->op1_type != IS_UNUSED) { if (opline->op1_type == IS_CONST) { // see: https://github.com/php/php-src/commit/e70618aff6f447a298605d07648f2ce9e5a284f5 #ifdef EX_CONSTANT exit_status = EX_CONSTANT(opline->op1); #else exit_status = RT_CONSTANT(opline, opline->op1); #endif } else { exit_status = EX_VAR(opline->op1.var); } if (Z_ISREF_P(exit_status)) { exit_status = Z_REFVAL_P(exit_status); } ZVAL_DUP(&_exit_status, exit_status); exit_status = &_exit_status; } else { exit_status = &_exit_status; ZVAL_NULL(exit_status); } zend_object *obj = zend_throw_exception(swoole_exit_exception_ce, "swoole exit", 0); ZVAL_OBJ(&ex, obj); zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("flags"), flags); Z_TRY_ADDREF_P(exit_status); zend_update_property(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), exit_status); } return ZEND_USER_OPCODE_DISPATCH; } #else SW_EXTERN_C_BEGIN PHP_FUNCTION(swoole_exit) { zend_long flags = 0; if (Coroutine::get_current()) { flags |= SW_EXIT_IN_COROUTINE; } if (sw_server() && sw_server()->is_started()) { flags |= SW_EXIT_IN_SERVER; } zend_string *message = NULL; zend_long status = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_STR_OR_LONG(message, status) ZEND_PARSE_PARAMETERS_END(); if (flags) { zval ex = {}; zend_object *obj = zend_throw_exception(swoole_exit_exception_ce, (message ? ZSTR_VAL(message) : "swoole exit"), 0); ZVAL_OBJ(&ex, obj); zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("flags"), flags); if (message) { zend_update_property_str(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), message); } else { zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), status); } } else { if (!php_swoole_call_original_handler(ZEND_STRL("exit"), INTERNAL_FUNCTION_PARAM_PASSTHRU)) { if (message) { php_write(ZSTR_VAL(message), ZSTR_LEN(message)); } sw_php_exit(status); } } } SW_EXTERN_C_END #endif static int coro_begin_silence_handler(zend_execute_data *execute_data) { PHPContext *task = PHPCoroutine::get_context(); task->in_silence = true; task->ori_error_reporting = EG(error_reporting); return ZEND_USER_OPCODE_DISPATCH; } static int coro_end_silence_handler(zend_execute_data *execute_data) { PHPContext *task = PHPCoroutine::get_context(); task->in_silence = false; return ZEND_USER_OPCODE_DISPATCH; } static void coro_interrupt_resume(void *data) { auto *co = static_cast(data); if (co && !co->is_end()) { swoole_trace_log(SW_TRACE_COROUTINE, "interrupt_callback cid=%ld ", co->get_cid()); co->resume(); } } static void coro_interrupt_function(zend_execute_data *execute_data) { PHPContext *task = PHPCoroutine::get_context(); if (task && task->co && PHPCoroutine::is_schedulable(task)) { swoole_event_defer(coro_interrupt_resume, (void *) task->co); task->co->yield(); } if (orig_interrupt_function) { orig_interrupt_function(execute_data); } } PHPContext *PHPCoroutine::create_context(const Args *args) { auto *ctx = static_cast(emalloc(sizeof(PHPContext))); ctx->output_ptr = nullptr; ctx->serialize_lock = 0; ctx->serialize = {}; ctx->unserialize = {}; ctx->in_silence = false; ctx->co = Coroutine::get_current(); ctx->co->set_task((void *) ctx); ctx->defer_tasks = nullptr; ctx->pcid = ctx->co->get_origin_cid(); ctx->context = nullptr; ctx->on_yield = nullptr; ctx->on_resume = nullptr; ctx->on_close = nullptr; ctx->enable_scheduler = true; if (UNEXPECTED(SWOOLE_G(enable_fiber_mock))) { fiber_context_init(ctx); } else { ctx->fiber_context = nullptr; } ctx->fiber_init_notified = false; EG(vm_stack) = zend_vm_stack_new_page(SW_DEFAULT_PHP_STACK_PAGE_SIZE, nullptr); EG(vm_stack_top) = EG(vm_stack)->top + ZEND_CALL_FRAME_SLOT; EG(vm_stack_end) = EG(vm_stack)->end; EG(vm_stack_page_size) = SW_DEFAULT_PHP_STACK_PAGE_SIZE; zend_function *func = EG(current_execute_data)->func; auto *call = reinterpret_cast((EG(vm_stack_top))); EG(current_execute_data) = call; memset(EG(current_execute_data), 0, sizeof(zend_execute_data)); EG(error_handling) = EH_NORMAL; EG(exception_class) = nullptr; EG(exception) = nullptr; EG(jit_trace_num) = 0; call->func = func; EG(vm_stack_top) += ZEND_CALL_FRAME_SLOT; #ifdef ZEND_CHECK_STACK_LIMIT EG(stack_base) = stack_base(ctx); EG(stack_limit) = stack_limit(ctx); #endif save_vm_stack(ctx); record_last_msec(ctx); ctx->fci_cache = *args->fci_cache; ctx->fci.size = sizeof(ctx->fci); ctx->fci.object = nullptr; ctx->fci.param_count = args->argc; ctx->fci.params = args->argv; ctx->fci.named_params = nullptr; ctx->return_value = {}; ctx->fci.retval = &ctx->return_value; if (args->callable) { ctx->fci.function_name = *args->callable; Z_TRY_ADDREF(ctx->fci.function_name); } else { ZVAL_UNDEF(&ctx->fci.function_name); } sw_zend_fci_cache_persist(&ctx->fci_cache); return ctx; } void PHPCoroutine::bailout() { Coroutine::bailout([]() { if (sw_reactor()) { sw_reactor()->running = false; sw_reactor()->bailout = true; } zend_bailout(); }); } bool PHPCoroutine::catch_exception() { if (UNEXPECTED(EG(exception))) { #if SW_RECOVER_CANCELED_EXCEPTION if (EG(exception)->ce == swoole_coroutine_canceled_exception_ce) { OBJ_RELEASE(EG(exception)); EG(exception) = nullptr; } else { #endif // the exception error messages MUST be output on the current coroutine stack zend_exception_error(EG(exception), E_ERROR); return true; #if SW_RECOVER_CANCELED_EXCEPTION } #endif } return false; } void PHPCoroutine::activate() { if (sw_unlikely(activated)) { return; } zval *enable_library = zend_get_constant_str(ZEND_STRL("SWOOLE_LIBRARY")); if (enable_library == nullptr || !zval_is_true(enable_library)) { php_swoole_load_library(); } /* init reactor and register event wait */ php_swoole_check_reactor(); /* replace interrupt function */ orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = coro_interrupt_function; if (SWOOLE_G(enable_preemptive_scheduler) || config.enable_preemptive_scheduler) { /* create a thread to interrupt the coroutine that takes up too much time */ interrupt_thread_start(); } if (sw_is_main_thread()) { if (config.hook_flags) { enable_hook(config.hook_flags); } disable_unsafe_function(); } /* deactivate when reactor free */ sw_reactor()->add_destroy_callback(deactivate, nullptr); Coroutine::activate(); Coroutine::set_on_yield(on_yield); Coroutine::set_on_resume(on_resume); Coroutine::set_on_close(on_close); activated = true; } void PHPCoroutine::deactivate(void *ptr) { if (sw_unlikely(!activated)) { return; } activated = false; interrupt_thread_stop(); if (sw_is_main_thread()) { disable_hook(); enable_unsafe_function(); } Coroutine::set_on_yield(nullptr); Coroutine::set_on_resume(nullptr); Coroutine::set_on_close(nullptr); zend_interrupt_function = orig_interrupt_function; if (config.enable_deadlock_check) { deadlock_check(); } Coroutine::deactivate(); } void PHPCoroutine::shutdown() { if (activated) { deactivate(nullptr); } if (options) { zend_array_destroy(options); options = nullptr; } free_main_context(); } // !!! [Danger Warning] // The deadlock_check function can only print information and cannot execute any async functions // This function can only execute internal code and cannot provide any callbacks, allowing the execution of user code void PHPCoroutine::deadlock_check() { if (Coroutine::count() == 0) { return; } if (php_swoole_is_fatal_error() || (sw_reactor() && sw_reactor()->bailout)) { return; } if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Coroutine\deadlock_check)", 0, nullptr); } else { printf("\n ===================================================================" "\n [FATAL ERROR]: all coroutines (count: %lu) are asleep - deadlock! " "\n ===================================================================" "\n", Coroutine::count()); } } void PHPCoroutine::interrupt_thread_stop() { if (!interrupt_thread_running) { return; } interrupt_thread_running = false; interrupt_thread.join(); } void PHPCoroutine::interrupt_thread_start() { if (interrupt_thread_running) { return; } zend_vm_interrupt = &EG(vm_interrupt); interrupt_thread_running = true; interrupt_thread = std::thread([]() { swoole_signal_block_all(); while (interrupt_thread_running) { zend_atomic_bool_store(zend_vm_interrupt, 1); std::this_thread::sleep_for(std::chrono::milliseconds(MAX_EXEC_MSEC / 2)); } }); } /** * The meaning of the task argument in coro switch functions * * create: origin_task * yield: current_task * resume: target_task * close: current_task * */ inline void PHPCoroutine::save_vm_stack(PHPContext *ctx) { ctx->bailout = EG(bailout); ctx->vm_stack_top = EG(vm_stack_top); ctx->vm_stack_end = EG(vm_stack_end); ctx->vm_stack = EG(vm_stack); ctx->vm_stack_page_size = EG(vm_stack_page_size); ctx->execute_data = EG(current_execute_data); ctx->jit_trace_num = EG(jit_trace_num); ctx->error_handling = EG(error_handling); ctx->exception_class = EG(exception_class); ctx->exception = EG(exception); if (UNEXPECTED(ctx->in_silence)) { ctx->tmp_error_reporting = EG(error_reporting); EG(error_reporting) = ctx->ori_error_reporting; } #ifdef ZEND_CHECK_STACK_LIMIT ctx->stack_base = EG(stack_base); ctx->stack_limit = EG(stack_limit); #endif } inline void PHPCoroutine::restore_vm_stack(PHPContext *ctx) { EG(bailout) = ctx->bailout; EG(vm_stack_top) = ctx->vm_stack_top; EG(vm_stack_end) = ctx->vm_stack_end; EG(vm_stack) = ctx->vm_stack; EG(vm_stack_page_size) = ctx->vm_stack_page_size; EG(current_execute_data) = ctx->execute_data; EG(jit_trace_num) = ctx->jit_trace_num; EG(error_handling) = ctx->error_handling; EG(exception_class) = ctx->exception_class; EG(exception) = ctx->exception; if (UNEXPECTED(ctx->in_silence)) { EG(error_reporting) = ctx->tmp_error_reporting; } #ifdef ZEND_CHECK_STACK_LIMIT EG(stack_base) = ctx->stack_base; EG(stack_limit) = ctx->stack_limit; #endif } inline void PHPCoroutine::save_og(PHPContext *ctx) { if (OG(handlers).elements) { ctx->output_ptr = (zend_output_globals *) emalloc(sizeof(zend_output_globals)); memcpy(ctx->output_ptr, SWOG, sizeof(zend_output_globals)); php_output_activate(); } else { ctx->output_ptr = nullptr; } } inline void PHPCoroutine::restore_og(PHPContext *ctx) { if (ctx->output_ptr) { memcpy(SWOG, ctx->output_ptr, sizeof(zend_output_globals)); efree(ctx->output_ptr); ctx->output_ptr = nullptr; } } void PHPCoroutine::save_bg(PHPContext *ctx) { if (BG(serialize_lock)) { ctx->serialize_lock = BG(serialize_lock); } if (BG(serialize).data) { memcpy(&ctx->serialize, &BG(serialize), sizeof(BG(serialize))); } if (BG(unserialize).data) { memcpy(&ctx->unserialize, &BG(unserialize), sizeof(BG(unserialize))); } } void PHPCoroutine::restore_bg(PHPContext *ctx) { if (ctx->serialize_lock) { BG(serialize_lock) = ctx->serialize_lock; ctx->serialize_lock = 0; } if (ctx->serialize.data) { memcpy(&BG(serialize), &ctx->serialize, sizeof(BG(serialize))); ctx->serialize = {}; } if (ctx->unserialize.data) { memcpy(&BG(unserialize), &ctx->unserialize, sizeof(BG(unserialize))); ctx->unserialize = {}; } } void PHPCoroutine::set_hook_flags(uint32_t flags) { zval zoptions; array_init(&zoptions); add_assoc_long(&zoptions, "hook_flags", flags); if (options) { zend_hash_merge(options, Z_ARRVAL(zoptions), nullptr, true); zval_ptr_dtor(&zoptions); } else { options = Z_ARRVAL(zoptions); } config.hook_flags = flags; } void PHPCoroutine::save_context(PHPContext *ctx) { save_vm_stack(ctx); save_og(ctx); save_bg(ctx); } void PHPCoroutine::restore_context(PHPContext *ctx) { restore_vm_stack(ctx); restore_og(ctx); restore_bg(ctx); } void PHPCoroutine::on_yield(void *arg) { auto *ctx = static_cast(arg); auto *origin_ctx = get_origin_context(ctx); fiber_context_switch_try_notify(ctx, origin_ctx); save_context(ctx); restore_context(origin_ctx); if (ctx->on_yield) { (*ctx->on_yield)(ctx); } swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", ctx->co->get_cid(), ctx->co->get_origin_cid()); } void PHPCoroutine::on_resume(void *arg) { auto *ctx = static_cast(arg); auto *current_ctx = get_context(); fiber_context_switch_try_notify(current_ctx, ctx); save_context(current_ctx); restore_context(ctx); record_last_msec(ctx); if (ctx->on_resume) { (*ctx->on_resume)(ctx); } swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", Coroutine::get_current_cid(), ctx->co->get_cid()); } void PHPCoroutine::on_close(void *arg) { auto *ctx = static_cast(arg); if (ctx->on_close) { (*ctx->on_close)(ctx); } efree(ctx); } void PHPCoroutine::destroy_context(PHPContext *ctx) { PHPContext *origin_ctx = get_origin_context(ctx); #ifdef SW_LOG_TRACE_OPEN // MUST be assigned here, the task memory may have been released long cid = ctx->co->get_cid(); long origin_cid = ctx->co->get_origin_cid(); #endif if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_CORO_STOP)) { swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_STOP, ctx); } if (OG(handlers).elements) { zend_bool no_headers = SG(request_info).no_headers; /* Do not send headers by SAPI */ SG(request_info).no_headers = true; if (OG(active)) { php_output_end_all(); } php_output_deactivate(); php_output_activate(); SG(request_info).no_headers = no_headers; } if (ctx->defer_tasks) { while (!ctx->defer_tasks->empty()) { zend::Function *defer_fci = ctx->defer_tasks->top(); ctx->defer_tasks->pop(); sw_zend_fci_cache_discard(&defer_fci->fci_cache); efree(defer_fci); } delete ctx->defer_tasks; ctx->defer_tasks = nullptr; } // Release resources if (ctx->context) { zend_object *context = ctx->context; ctx->context = reinterpret_cast(INVALID_PTR); OBJ_RELEASE(context); } Z_TRY_DELREF(ctx->fci.function_name); ZVAL_UNDEF(&ctx->fci.function_name); sw_zend_fci_cache_discard(&ctx->fci_cache); Z_TRY_DELREF(ctx->return_value); fiber_context_try_destroy(ctx, origin_ctx); swoole_trace_log(SW_TRACE_COROUTINE, "coro close cid=%ld and resume to %ld, %zu remained. usage size: %zu. malloc size: %zu", cid, origin_cid, (uintmax_t) Coroutine::count() - 1, (uintmax_t) zend_memory_usage(0), (uintmax_t) zend_memory_usage(1)); zend_vm_stack_destroy(); restore_context(origin_ctx); } void PHPCoroutine::main_func(void *_args) { bool exception_caught = false; const auto args = static_cast(_args); PHPContext *ctx = create_context(args); zend_first_try { swoole_trace_log(SW_TRACE_COROUTINE, "Create coro id: %ld, origin cid: %ld, coro total count: %zu, heap size: %zu", ctx->co->get_cid(), ctx->co->get_origin_cid(), (uintmax_t) Coroutine::count(), (uintmax_t) zend_memory_usage(0)); if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_CORO_START)) { swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_START, ctx); } if (UNEXPECTED(ctx->fiber_context && ctx->fci_cache.function_handler->type == ZEND_USER_FUNCTION)) { zend_execute_data *tmp = EG(current_execute_data); zend_execute_data call = {}; EG(current_execute_data) = &call; EG(current_execute_data)->opline = ctx->fci_cache.function_handler->op_array.opcodes; call.func = ctx->fci_cache.function_handler; fiber_context_switch_try_notify(get_origin_context(ctx), ctx); EG(current_execute_data) = tmp; } zend_call_function(&ctx->fci, &ctx->fci_cache); // Catch exception in main function of the coroutine exception_caught = catch_exception(); // The defer tasks still need to be executed after an exception occurs if (ctx->defer_tasks) { std::stack *tasks = ctx->defer_tasks; while (!tasks->empty()) { zend::Function *defer_fci = tasks->top(); tasks->pop(); if (Z_TYPE_P(&ctx->return_value) != IS_UNDEF) { defer_fci->fci.param_count = 1; defer_fci->fci.params = &ctx->return_value; } if (UNEXPECTED(sw_zend_call_function_anyway(&defer_fci->fci, &defer_fci->fci_cache) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "defer callback handler error"); } if (EG(exception)) { zend_bailout(); } sw_zend_fci_cache_discard(&defer_fci->fci_cache); efree(defer_fci); } delete ctx->defer_tasks; ctx->defer_tasks = nullptr; } } zend_catch { // zend_bailout is executed in the c function catch_exception(); exception_caught = true; } zend_end_try(); destroy_context(ctx); if (exception_caught) { bailout(); } } long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *callable) { if (sw_unlikely(Coroutine::count() >= config.max_num)) { php_swoole_fatal_error(E_WARNING, "exceed max number of coroutine %zu", (uintmax_t) Coroutine::count()); return Coroutine::ERR_LIMIT; } if (sw_unlikely(!fci_cache || !fci_cache->function_handler)) { php_swoole_fatal_error(E_ERROR, "invalid function call info cache"); return Coroutine::ERR_INVALID; } zend_uchar type = fci_cache->function_handler->type; if (sw_unlikely(type != ZEND_USER_FUNCTION && type != ZEND_INTERNAL_FUNCTION)) { php_swoole_fatal_error(E_ERROR, "invalid function type %u", fci_cache->function_handler->type); return Coroutine::ERR_INVALID; } if (sw_unlikely(!activated)) { activate(); } Args _args; _args.fci_cache = fci_cache; _args.argv = argv; _args.argc = argc; _args.callable = callable; save_context(get_context()); return Coroutine::create(main_func, (void *) &_args); } void PHPCoroutine::defer(zend::Function *fci) { PHPContext *ctx = get_context(); if (ctx->defer_tasks == nullptr) { ctx->defer_tasks = new std::stack; } ctx->defer_tasks->push(fci); } void PHPCoroutine::fiber_context_init(PHPContext *ctx) { auto *fiber_context = static_cast(emalloc(sizeof(zend_fiber_context))); fiber_context->handle = reinterpret_cast(INVALID_PTR); fiber_context->kind = reinterpret_cast(INVALID_PTR); fiber_context->function = reinterpret_cast(INVALID_PTR); fiber_context->stack = reinterpret_cast(INVALID_PTR); ctx->fiber_context = fiber_context; zend_observer_fiber_init_notify(fiber_context); } void PHPCoroutine::fiber_context_try_destroy(const PHPContext *ctx, PHPContext *origin_ctx) { if (UNEXPECTED(ctx->fiber_context)) { ctx->fiber_context->status = ZEND_FIBER_STATUS_DEAD; fiber_context_switch_try_notify(ctx, origin_ctx); zend_observer_fiber_destroy_notify(ctx->fiber_context); if (ctx->fiber_context != nullptr) { efree(ctx->fiber_context); } } } zend_fiber_status PHPCoroutine::fiber_get_status(const PHPContext *ctx) { // main_context if (ctx->fiber_context == EG(main_fiber_context)) { return ZEND_FIBER_STATUS_RUNNING; } switch (ctx->co->get_state()) { case Coroutine::STATE_INIT: return ZEND_FIBER_STATUS_INIT; case Coroutine::STATE_WAITING: return ZEND_FIBER_STATUS_SUSPENDED; case Coroutine::STATE_RUNNING: return ZEND_FIBER_STATUS_RUNNING; case Coroutine::STATE_END: return ZEND_FIBER_STATUS_DEAD; default: php_swoole_fatal_error(E_ERROR, "Unexpected state when get fiber status"); return ZEND_FIBER_STATUS_DEAD; } } void PHPCoroutine::fiber_context_switch_notify(const PHPContext *from, PHPContext *to) { zend_fiber_context *from_context = from->fiber_context; zend_fiber_context *to_context = to->fiber_context; from_context->status = fiber_get_status(from); to_context->status = fiber_get_status(to); if (!to->fiber_init_notified) { to_context->status = ZEND_FIBER_STATUS_INIT; zend_observer_fiber_switch_notify(from_context, to_context); to_context->status = fiber_get_status(to); to->fiber_init_notified = true; } else { zend_observer_fiber_switch_notify(from_context, to_context); } } void PHPCoroutine::fiber_context_switch_try_notify(const PHPContext *from, PHPContext *to) { if (UNEXPECTED(from->fiber_context && to->fiber_context)) { fiber_context_switch_notify(from, to); } } #ifdef ZEND_CHECK_STACK_LIMIT void *PHPCoroutine::stack_limit(PHPContext *ctx) { #ifdef SW_USE_THREAD_CONTEXT return nullptr; #else zend_ulong reserve = EG(reserved_stack_size); #ifdef __APPLE__ /* On Apple Clang, the stack probing function ___chkstk_darwin incorrectly * probes a location that is twice the entered function's stack usage away * from the stack pointer, when using an alternative stack. * https://openradar.appspot.com/radar?id=5497722702397440 */ reserve = reserve * 2; #endif if (!ctx->co) { return nullptr; } /* stack->pointer is the end of the stack */ return (int8_t *) ctx->co->get_ctx().get_stack() + reserve; #endif } void *PHPCoroutine::stack_base(PHPContext *ctx) { #ifdef SW_USE_THREAD_CONTEXT return nullptr; #else if (!ctx->co) { return nullptr; } return (void *) ((uintptr_t) ctx->co->get_ctx().get_stack() + ctx->co->get_ctx().get_stack_size()); #endif } #endif /* ZEND_CHECK_STACK_LIMIT */ /* hook autoload */ static zend_class_entry *(*original_zend_autoload)(zend_string *name, zend_string *lc_name); struct AutoloadContext { Coroutine *coroutine; zend_class_entry *ce; }; struct AutoloadQueue { Coroutine *coroutine; std::queue *queue; }; static zend_class_entry *swoole_coroutine_autoload(zend_string *name, zend_string *lc_name) { auto current = Coroutine::get_current(); if (!current) { return original_zend_autoload(name, lc_name); } ZEND_ASSERT(EG(in_autoload) != nullptr); zend_hash_del(EG(in_autoload), lc_name); if (UNEXPECTED(SWOOLE_G(in_autoload) == nullptr)) { ALLOC_HASHTABLE(SWOOLE_G(in_autoload)); zend_hash_init(SWOOLE_G(in_autoload), 8, nullptr, nullptr, 0); } zval *z_queue = zend_hash_find(SWOOLE_G(in_autoload), lc_name); if (z_queue != nullptr) { auto queue = (AutoloadQueue *) Z_PTR_P(z_queue); if (queue->coroutine == current) { return nullptr; } AutoloadContext context; context.coroutine = current; context.ce = nullptr; queue->queue->push(&context); current->yield(); return context.ce; } AutoloadQueue queue; queue.coroutine = current; std::queue queue_object; queue.queue = &queue_object; zend_hash_add_ptr(SWOOLE_G(in_autoload), lc_name, &queue); zend_class_entry *ce = original_zend_autoload(name, lc_name); zend_hash_del(SWOOLE_G(in_autoload), lc_name); AutoloadContext *pending_context = nullptr; while (!queue_object.empty()) { pending_context = queue_object.front(); queue_object.pop(); pending_context->ce = ce; pending_context->coroutine->resume(); } return ce; } void php_swoole_coroutine_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE(swoole_coroutine_util, "Swoole\\Coroutine", "Co", swoole_coroutine_methods, nullptr); SW_SET_CLASS_CREATE(swoole_coroutine_util, sw_zend_create_object_deny); SW_INIT_CLASS_ENTRY_BASE( swoole_coroutine_iterator, "Swoole\\Coroutine\\Iterator", "Co\\Iterator", nullptr, spl_ce_ArrayIterator); SW_INIT_CLASS_ENTRY_BASE( swoole_coroutine_context, "Swoole\\Coroutine\\Context", "Co\\Context", nullptr, spl_ce_ArrayObject); SW_REGISTER_LONG_CONSTANT("SWOOLE_DEFAULT_MAX_CORO_NUM", SW_DEFAULT_MAX_CORO_NUM); SW_REGISTER_LONG_CONSTANT("SWOOLE_CORO_MAX_NUM_LIMIT", Coroutine::MAX_NUM_LIMIT); SW_REGISTER_LONG_CONSTANT("SWOOLE_CORO_INIT", Coroutine::STATE_INIT); SW_REGISTER_LONG_CONSTANT("SWOOLE_CORO_WAITING", Coroutine::STATE_WAITING); SW_REGISTER_LONG_CONSTANT("SWOOLE_CORO_RUNNING", Coroutine::STATE_RUNNING); SW_REGISTER_LONG_CONSTANT("SWOOLE_CORO_END", Coroutine::STATE_END); // prohibit exit in coroutine SW_INIT_CLASS_ENTRY_EX( swoole_exit_exception, "Swoole\\ExitException", nullptr, swoole_exit_exception_methods, swoole_exception); zend_declare_property_long(swoole_exit_exception_ce, ZEND_STRL("flags"), 0, ZEND_ACC_PRIVATE); zend_declare_property_long(swoole_exit_exception_ce, ZEND_STRL("status"), 0, ZEND_ACC_PRIVATE); SW_INIT_CLASS_ENTRY_EX2(swoole_coroutine_canceled_exception, "Swoole\\Coroutine\\CanceledException", nullptr, nullptr, zend_ce_exception, zend_get_std_object_handlers()); swoole_coroutine_canceled_exception_ce->ce_flags |= ZEND_ACC_FINAL; SW_INIT_CLASS_ENTRY_EX2(swoole_coroutine_timeout_exception, "Swoole\\Coroutine\\TimeoutException", nullptr, nullptr, zend_ce_exception, zend_get_std_object_handlers()); swoole_coroutine_timeout_exception_ce->ce_flags |= ZEND_ACC_FINAL; SW_REGISTER_LONG_CONSTANT("SWOOLE_EXIT_IN_COROUTINE", SW_EXIT_IN_COROUTINE); SW_REGISTER_LONG_CONSTANT("SWOOLE_EXIT_IN_SERVER", SW_EXIT_IN_SERVER); if (SWOOLE_G(cli)) { #if PHP_VERSION_ID < 80400 ori_exit_handler = zend_get_user_opcode_handler(ZEND_EXIT); zend_set_user_opcode_handler(ZEND_EXIT, coro_exit_handler); #endif ori_begin_silence_handler = zend_get_user_opcode_handler(ZEND_BEGIN_SILENCE); zend_set_user_opcode_handler(ZEND_BEGIN_SILENCE, coro_begin_silence_handler); ori_end_silence_handler = zend_get_user_opcode_handler(ZEND_END_SILENCE); zend_set_user_opcode_handler(ZEND_END_SILENCE, coro_end_silence_handler); } /* hook autoload */ original_zend_autoload = zend_autoload; zend_autoload = swoole_coroutine_autoload; SWOOLE_G(in_autoload) = nullptr; } void php_swoole_coroutine_rinit() { PHPCoroutine::init_main_context(); } void php_swoole_coroutine_rshutdown() { if (SWOOLE_G(in_autoload)) { zend_hash_destroy(SWOOLE_G(in_autoload)); FREE_HASHTABLE(SWOOLE_G(in_autoload)); SWOOLE_G(in_autoload) = nullptr; } PHPCoroutine::shutdown(); } static PHP_METHOD(swoole_exit_exception, getFlags) { SW_RETURN_PROPERTY("flags"); } static PHP_METHOD(swoole_exit_exception, getStatus) { SW_RETURN_PROPERTY("status"); } PHP_FUNCTION(swoole_coroutine_create) { zend_fcall_info fci; zend_fcall_info_cache fci_cache; ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_FUNC(fci, fci_cache) Z_PARAM_VARIADIC('*', fci.params, fci.param_count) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (sw_unlikely(SWOOLE_G(req_status) == PHP_SWOOLE_CALL_USER_SHUTDOWNFUNC_BEGIN)) { auto *func = EG(current_execute_data)->prev_execute_data->func; if (func->common.function_name && sw_unlikely(memcmp(ZSTR_VAL(func->common.function_name), ZEND_STRS("__destruct")) == 0)) { php_swoole_fatal_error(E_ERROR, "can not use coroutine in __destruct after php_request_shutdown"); RETURN_FALSE; } } long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params, &fci.function_name); if (sw_likely(cid > 0)) { RETURN_LONG(cid); } else { RETURN_FALSE; } } PHP_FUNCTION(swoole_coroutine_defer) { zend_fcall_info fci; zend_fcall_info_cache fci_cache; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_FUNC(fci, fci_cache) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Coroutine::get_current_safe(); auto *defer_fci = static_cast(emalloc(sizeof(zend::Function))); defer_fci->fci = fci; defer_fci->fci_cache = fci_cache; sw_zend_fci_cache_persist(&defer_fci->fci_cache); PHPCoroutine::defer(defer_fci); } static PHP_METHOD(swoole_coroutine, stats) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("event_num"), sw_reactor() ? sw_reactor()->get_event_num() : 0); add_assoc_long_ex(return_value, ZEND_STRL("signal_listener_num"), swoole_signal_get_listener_num()); if (sw_async_threads()) { add_assoc_long_ex(return_value, ZEND_STRL("aio_task_num"), sw_async_threads()->get_task_num()); add_assoc_long_ex(return_value, ZEND_STRL("aio_worker_num"), sw_async_threads()->get_worker_num()); add_assoc_long_ex(return_value, ZEND_STRL("aio_queue_size"), sw_async_threads()->get_queue_size()); } else { add_assoc_long_ex(return_value, ZEND_STRL("aio_task_num"), 0); add_assoc_long_ex(return_value, ZEND_STRL("aio_worker_num"), 0); add_assoc_long_ex(return_value, ZEND_STRL("aio_queue_size"), 0); } add_assoc_long_ex(return_value, ZEND_STRL("c_stack_size"), Coroutine::get_stack_size()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_num"), Coroutine::count()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_peak_num"), Coroutine::get_peak_num()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_last_cid"), Coroutine::get_last_cid()); #ifdef SW_USE_IOURING auto iouring = SwooleTG.iouring; if (iouring) { add_assoc_long_ex(return_value, ZEND_STRL("iouring_task_num"), iouring->get_task_num()); add_assoc_long_ex(return_value, ZEND_STRL("iouring_sq_usage_percent"), iouring->get_sq_usage_percent()); add_assoc_long_ex(return_value, ZEND_STRL("iouring_waiting_task_num"), iouring->get_waiting_task_num()); } #endif } PHP_METHOD(swoole_coroutine, getCid) { RETURN_LONG(PHPCoroutine::get_cid()); } PHP_METHOD(swoole_coroutine, getPcid) { zend_long cid = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_long ret = PHPCoroutine::get_pcid(cid); if (ret == 0) { RETURN_FALSE; } RETURN_LONG(ret); } static PHP_METHOD(swoole_coroutine, getContext) { zend_long cid = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *ctx = static_cast( (EXPECTED(cid == 0) ? Coroutine::get_current_task() : Coroutine::get_task_by_cid(cid))); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); RETURN_NULL(); } if (UNEXPECTED(ctx->context == reinterpret_cast(INVALID_PTR))) { /* bad context (has been destroyed), see: https://github.com/swoole/swoole-src/issues/2991 */ php_swoole_fatal_error(E_WARNING, "The context of coroutine has been destroyed"); RETURN_NULL(); } if (UNEXPECTED(!ctx->context)) { object_init_ex(return_value, swoole_coroutine_context_ce); ctx->context = Z_OBJ_P(return_value); } GC_ADDREF(ctx->context); RETURN_OBJ(ctx->context); } static PHP_METHOD(swoole_coroutine, getElapsed) { zend_long cid = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(PHPCoroutine::get_elapsed(cid)); } static PHP_METHOD(swoole_coroutine, getStackUsage) { zend_long cid = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ssize_t usage = PHPCoroutine::get_stack_usage(cid); if (usage < 0) { RETURN_FALSE; } RETURN_LONG(usage); } static PHP_METHOD(swoole_coroutine, exists) { zend_long cid; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(Coroutine::get_by_cid(cid) != nullptr); } static PHP_METHOD(swoole_coroutine, resume) { zend_long cid; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto coroutine_iterator = user_yield_coros.find(cid); if (coroutine_iterator == user_yield_coros.end()) { php_swoole_fatal_error(E_WARNING, "can not resume the coroutine which is in IO operation or non-existent"); RETURN_FALSE; } Coroutine *co = coroutine_iterator->second; user_yield_coros.erase(cid); co->resume(); RETURN_TRUE; } static PHP_METHOD(swoole_coroutine, yield) { Coroutine *co = Coroutine::get_current_safe(); user_yield_coros[co->get_cid()] = co; Coroutine::CancelFunc cancel_fn = [](Coroutine *co) { user_yield_coros.erase(co->get_cid()); co->resume(); return true; }; co->yield(&cancel_fn); if (co->is_canceled()) { swoole_set_last_error(SW_ERROR_CO_CANCELED); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_coroutine, join) { Coroutine *co = Coroutine::get_current_safe(); zval *cid_array; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(cid_array) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_array_length(cid_array) == 0) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } std::set co_set; std::shared_ptr canceled = std::make_shared(false); PHPContext::SwapCallback join_fn = [&co_set, canceled, co](PHPContext *task) { co_set.erase(task); if (!co_set.empty()) { return; } swoole_event_defer( [co, canceled](void *) { if (*canceled == false) { co->resume(); } }, nullptr); }; zval *zcid; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cid_array), zcid) { long cid = zval_get_long(zcid); if (co->get_cid() == cid) { php_swoole_error_ex(E_WARNING, SW_ERROR_WRONG_OPERATION, "can not join self"); RETURN_FALSE; } auto ctx = PHPCoroutine::get_context_by_cid(cid); if (ctx == nullptr) { continue; } if (ctx->on_close) { swoole_set_last_error(SW_ERROR_WRONG_OPERATION); RETURN_FALSE; } ctx->on_close = &join_fn; co_set.insert(ctx); } ZEND_HASH_FOREACH_END(); if (co_set.empty()) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } if (!co->yield_ex(timeout)) { if (!co_set.empty()) { for (auto ctx : co_set) { ctx->on_close = nullptr; } } *canceled = true; RETURN_FALSE; } RETURN_TRUE; } static bool php_swoole_coroutine_throw_exception(long cid, bool throw_exception, zend_class_entry *ce) { if (throw_exception) { auto *task = PHPCoroutine::get_context_by_cid(cid); // The coroutine does not exist or is not a PHP coroutine, and cannot be canceled or throw an exception if (!task) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); return false; } if (task->exception && task->exception_class == ce) { swoole_set_last_error(SW_ERROR_CO_CANCELED); return false; } zend_object *previous = task->exception; task->exception_class = ce; task->exception = zend_objects_new(task->exception_class); object_properties_init(task->exception, task->exception_class); if (previous) { zend_exception_set_previous(task->exception, previous); } zend_execute_data *ex_backup = EG(current_execute_data); EG(current_execute_data) = task->execute_data; zend::object_set(task->exception, ZSTR_KNOWN(ZEND_STR_FILE), zend_get_executed_filename_ex()); zend::object_set(task->exception, ZSTR_KNOWN(ZEND_STR_LINE), zend_get_executed_lineno()); EG(current_execute_data) = ex_backup; // Ignore the result of Co::cancel(). Even if the coroutine is in an irrevocable state, // it will directly throw an exception and terminate the coroutine task->co->cancel(); return true; } else { Coroutine *co = swoole_coroutine_get(cid); if (!co) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); return false; } return co->cancel(); } } static PHP_METHOD(swoole_coroutine, cancel) { zend_long cid; bool throw_exception = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(cid) Z_PARAM_OPTIONAL Z_PARAM_BOOL(throw_exception) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(php_swoole_coroutine_throw_exception(cid, throw_exception, swoole_coroutine_canceled_exception_ce)); } static PHP_METHOD(swoole_coroutine, setTimeLimit) { double timeout = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Coroutine *co = Coroutine::get_current_safe(); if (timeout == 0 || co == nullptr) { RETURN_FALSE; } long cid = co->get_cid(); swoole_timer_add((long) timeout * 1000, false, [cid](swoole::Timer *, swoole::TimerNode *tnode) { swoole_timer_del(tnode); if (PHPCoroutine::get_context_by_cid(cid) != nullptr) { php_swoole_coroutine_throw_exception(cid, true, swoole_coroutine_timeout_exception_ce); } }); RETURN_TRUE; } static PHP_METHOD(swoole_coroutine, isCanceled) { Coroutine *co = Coroutine::get_current_safe(); RETURN_BOOL(co->is_canceled()); } PHP_FUNCTION(swoole_test_kernel_coroutine) { if (!PHPCoroutine::is_activated()) { RETURN_FALSE; } zend_long count = 100; double sleep_time = 1.0; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(count) Z_PARAM_DOUBLE(sleep_time) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Coroutine::create([=](void *ptr) { SW_LOOP_N(count) { System::sleep(sleep_time); } }); } static PHP_METHOD(swoole_coroutine, getBackTrace) { zend_long cid = 0; zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT; zend_long limit = 0; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) Z_PARAM_LONG(options) Z_PARAM_LONG(limit) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (!cid || cid == PHPCoroutine::get_cid()) { zend_fetch_debug_backtrace(return_value, 0, options, limit); } else { auto *task = PHPCoroutine::get_context_by_cid(cid); if (UNEXPECTED(!task)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); RETURN_FALSE; } zend_execute_data *ex_backup = EG(current_execute_data); EG(current_execute_data) = task->execute_data; zend_fetch_debug_backtrace(return_value, 0, options, limit); EG(current_execute_data) = ex_backup; } } void sw_php_print_backtrace_impl(int skip_last, int options, int limit, bool include_main) { zval backtrace; zend_fetch_debug_backtrace(&backtrace, skip_last, options, limit); ZEND_ASSERT(Z_TYPE(backtrace) == IS_ARRAY); zend_string *str = zend_trace_to_string(Z_ARRVAL(backtrace), include_main); ZEND_WRITE(ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); zval_ptr_dtor(&backtrace); } void sw_php_print_backtrace(zend_long cid, zend_long options, zend_long limit, zval *return_value) { if (!cid || cid == PHPCoroutine::get_cid()) { sw_php_print_backtrace_impl(1, options, limit); } else { PHPContext *ctx = PHPCoroutine::get_context_by_cid(cid); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); if (return_value) { RETVAL_FALSE; } return; } zend_execute_data *ex_backup = EG(current_execute_data); EG(current_execute_data) = ctx->execute_data; sw_php_print_backtrace_impl(0, options, limit); EG(current_execute_data) = ex_backup; } } static PHP_METHOD(swoole_coroutine, printBackTrace) { zend_long cid = 0; zend_long options = 0; zend_long limit = 0; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) Z_PARAM_LONG(options) Z_PARAM_LONG(limit) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); sw_php_print_backtrace(cid, options, limit, return_value); } static PHP_METHOD(swoole_coroutine, list) { zval zlist; array_init(&zlist); for (auto &co : Coroutine::coroutines) { add_next_index_long(&zlist, co.second->get_cid()); } object_init_ex(return_value, swoole_coroutine_iterator_ce); sw_zend_call_method_with_1_params(return_value, swoole_coroutine_iterator_ce, &swoole_coroutine_iterator_ce->constructor, "__construct", nullptr, &zlist); zval_ptr_dtor(&zlist); } #ifdef SW_CORO_TIME static PHP_METHOD(swoole_coroutine, getExecuteTime) { RETURN_LONG(PHPCoroutine::get_execute_time()); } #endif PHP_METHOD(swoole_coroutine, enableScheduler) { RETURN_BOOL(PHPCoroutine::enable_scheduler()); } PHP_METHOD(swoole_coroutine, disableScheduler) { RETURN_BOOL(PHPCoroutine::disable_scheduler()); } /** * for gdb */ zend_executor_globals *php_swoole_get_executor_globals() { return (zend_executor_globals *) &EG(uninitialized_zval); } ================================================ FILE: ext-src/swoole_coroutine_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "swoole_memory.h" #include "swoole_lock.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_coroutine_lock_arginfo.h" END_EXTERN_C() using swoole::CoroutineLock; static zend_class_entry *swoole_coroutine_lock_ce; static zend_object_handlers swoole_coroutine_lock_handlers; struct CoLockObject { CoroutineLock *lock; bool shared; zend_object std; }; static CoLockObject *co_lock_fetch_object(zend_object *obj) { return (CoLockObject *) ((char *) obj - swoole_coroutine_lock_handlers.offset); } static CoroutineLock *co_lock_get_ptr(zval *zobject) { return co_lock_fetch_object(Z_OBJ_P(zobject))->lock; } static CoroutineLock *co_lock_get_and_check_ptr(zval *zobject) { CoroutineLock *lock = co_lock_get_ptr(zobject); if (UNEXPECTED(!lock)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return lock; } static void co_lock_set_ptr(zval *zobject, CoroutineLock *ptr) { co_lock_fetch_object(Z_OBJ_P(zobject))->lock = ptr; } static void co_lock_free_object(zend_object *object) { CoLockObject *o = co_lock_fetch_object(object); if (o->lock && !o->shared) { delete o->lock; } zend_object_std_dtor(object); } static zend_object *co_lock_create_object(zend_class_entry *ce) { auto *lock = static_cast(zend_object_alloc(sizeof(CoLockObject), ce)); zend_object_std_init(&lock->std, ce); object_properties_init(&lock->std, ce); lock->std.handlers = &swoole_coroutine_lock_handlers; return &lock->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_coroutine_lock, __construct); static PHP_METHOD(swoole_coroutine_lock, lock); static PHP_METHOD(swoole_coroutine_lock, unlock); SW_EXTERN_C_END // clang-format off static constexpr zend_function_entry swoole_coroutine_lock_methods[] = { PHP_ME(swoole_coroutine_lock, __construct, arginfo_class_Swoole_Coroutine_Lock___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_lock, lock, arginfo_class_Swoole_Coroutine_Lock_lock, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_lock, unlock, arginfo_class_Swoole_Coroutine_Lock_unlock, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_coroutine_lock_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_coroutine_lock, "Swoole\\Coroutine\\Lock", nullptr, swoole_coroutine_lock_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_coroutine_lock); SW_SET_CLASS_CLONEABLE(swoole_coroutine_lock, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_coroutine_lock, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_coroutine_lock, co_lock_create_object, co_lock_free_object, CoLockObject, std); zend_declare_property_long(swoole_coroutine_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); } static PHP_METHOD(swoole_coroutine_lock, __construct) { CoroutineLock *lock = co_lock_get_ptr(ZEND_THIS); if (lock != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend_bool shared = false; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(shared) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); lock = new CoroutineLock(shared); co_lock_set_ptr(ZEND_THIS, lock); RETURN_TRUE; } static PHP_METHOD(swoole_coroutine_lock, lock) { zend_long operation = LOCK_EX; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(operation) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); CoroutineLock *lock = co_lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->lock(operation)); } static PHP_METHOD(swoole_coroutine_lock, unlock) { CoroutineLock *lock = co_lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->unlock()); } ================================================ FILE: ext-src/swoole_coroutine_scheduler.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | shiguangqi | | Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include BEGIN_EXTERN_C() #include "stubs/php_swoole_coroutine_scheduler_arginfo.h" END_EXTERN_C() using swoole::Coroutine; using swoole::NameResolver; using swoole::PHPCoroutine; using swoole::Reactor; using swoole::coroutine::System; struct SchedulerTask { zend_long count; zend_fcall_info fci; zend_fcall_info_cache fci_cache; }; struct SchedulerObject { std::queue *list; bool started; zend_object std; }; static zend_class_entry *swoole_coroutine_scheduler_ce; static zend_object_handlers swoole_coroutine_scheduler_handlers; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_coroutine_scheduler, add); static PHP_METHOD(swoole_coroutine_scheduler, parallel); static PHP_METHOD(swoole_coroutine_scheduler, start); SW_EXTERN_C_END static sw_inline SchedulerObject *scheduler_get_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_coroutine_scheduler_handlers.offset); } static zend_object *scheduler_create_object(zend_class_entry *ce) { auto *s = static_cast(zend_object_alloc(sizeof(SchedulerObject), ce)); zend_object_std_init(&s->std, ce); object_properties_init(&s->std, ce); s->std.handlers = &swoole_coroutine_scheduler_handlers; return &s->std; } static void scheduler_free_object(zend_object *object) { SchedulerObject *s = scheduler_get_object(object); if (s->list) { while (!s->list->empty()) { SchedulerTask *task = s->list->front(); s->list->pop(); sw_zend_fci_cache_discard(&task->fci_cache); sw_zend_fci_params_discard(&task->fci); efree(task); } delete s->list; s->list = nullptr; } zend_object_std_dtor(&s->std); } // clang-format off static const zend_function_entry swoole_coroutine_scheduler_methods[] = { PHP_ME(swoole_coroutine_scheduler, add, arginfo_class_Swoole_Coroutine_Scheduler_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_scheduler, parallel, arginfo_class_Swoole_Coroutine_Scheduler_parallel, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_scheduler, set, arginfo_class_Swoole_Coroutine_Scheduler_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_scheduler, getOptions, arginfo_class_Swoole_Coroutine_Scheduler_getOptions, ZEND_ACC_PUBLIC) PHP_ME(swoole_coroutine_scheduler, start, arginfo_class_Swoole_Coroutine_Scheduler_start, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_coroutine_scheduler_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_coroutine_scheduler, "Swoole\\Coroutine\\Scheduler", "Co\\Scheduler", swoole_coroutine_scheduler_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_coroutine_scheduler); SW_SET_CLASS_CLONEABLE(swoole_coroutine_scheduler, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_coroutine_scheduler, sw_zend_class_unset_property_deny); SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_coroutine_scheduler); SW_SET_CLASS_CUSTOM_OBJECT( swoole_coroutine_scheduler, scheduler_create_object, scheduler_free_object, SchedulerObject, std); swoole_coroutine_scheduler_ce->ce_flags |= ZEND_ACC_FINAL; } static zend::Callable *exit_condition_fci_cache = nullptr; static bool php_swoole_coroutine_reactor_can_exit(Reactor *reactor, size_t &event_num) { zval retval; SW_ASSERT(exit_condition_fci_cache); ZVAL_NULL(&retval); int success = sw_zend_call_function_ex(nullptr, exit_condition_fci_cache->ptr(), 0, nullptr, &retval); if (UNEXPECTED(success != SUCCESS)) { php_swoole_fatal_error(E_ERROR, "Coroutine can_exit callback handler error"); } if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return Z_TYPE_P(&retval) != IS_FALSE; } void php_swoole_coroutine_scheduler_rshutdown() { swoole_name_resolver_each([](const std::list::iterator &iter) -> swTraverseOperation { if (iter->type == NameResolver::TYPE_PHP) { zval_dtor((zval *) iter->private_data); efree(iter->private_data); return SW_TRAVERSE_REMOVE; } else { return SW_TRAVERSE_KEEP; } }); if (exit_condition_fci_cache) { sw_callable_free(exit_condition_fci_cache); exit_condition_fci_cache = nullptr; } } void php_swoole_set_coroutine_option(zend_array *vht) { zval *ztmp; if (php_swoole_array_get_value(vht, "max_coro_num", ztmp) || php_swoole_array_get_value(vht, "max_coroutine", ztmp)) { zend_long max_num = zval_get_long(ztmp); PHPCoroutine::set_max_num(max_num <= 0 ? SW_DEFAULT_MAX_CORO_NUM : max_num); } if (php_swoole_array_get_value(vht, "enable_deadlock_check", ztmp)) { PHPCoroutine::set_deadlock_check(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "hook_flags", ztmp)) { PHPCoroutine::set_hook_flags(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "enable_preemptive_scheduler", ztmp)) { PHPCoroutine::enable_preemptive_scheduler(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "c_stack_size", ztmp) || php_swoole_array_get_value(vht, "stack_size", ztmp)) { Coroutine::set_stack_size(php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "name_resolver", ztmp)) { if (!ZVAL_IS_ARRAY(ztmp)) { php_swoole_fatal_error(E_WARNING, "name_resolver must be an array"); } else { zend_hash_apply(Z_ARR_P(ztmp), [](zval *zresolver) -> int { php_swoole_name_resolver_add(zresolver); return ZEND_HASH_APPLY_KEEP; }); } } if (PHPCoroutine::options) { zend_hash_merge(PHPCoroutine::options, vht, zval_add_ref, true); } else { PHPCoroutine::options = zend_array_dup(vht); } } PHP_METHOD(swoole_coroutine_scheduler, set) { zval *zset = nullptr; HashTable *vht = nullptr; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); php_swoole_set_global_option(vht); php_swoole_set_coroutine_option(vht); if (php_swoole_array_get_value(vht, "dns_cache_expire", ztmp)) { System::set_dns_cache_expire((time_t) zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "dns_cache_capacity", ztmp)) { System::set_dns_cache_capacity((size_t) zval_get_long(ztmp)); } /* Reactor can exit */ if ((ztmp = zend_hash_str_find(vht, ZEND_STRL("exit_condition")))) { if (exit_condition_fci_cache) { sw_callable_free(exit_condition_fci_cache); } exit_condition_fci_cache = sw_callable_create(ztmp); if (exit_condition_fci_cache) { SwooleG.user_exit_condition = php_swoole_coroutine_reactor_can_exit; if (sw_reactor()) { sw_reactor()->set_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT, SwooleG.user_exit_condition); } } else { if (sw_reactor()) { sw_reactor()->remove_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT); SwooleG.user_exit_condition = nullptr; } } } } PHP_METHOD(swoole_coroutine_scheduler, getOptions) { if (!PHPCoroutine::options) { return; } RETURN_ARR(zend_array_dup(PHPCoroutine::options)); } static void scheduler_add_task(SchedulerObject *s, SchedulerTask *task) { if (!s->list) { s->list = new std::queue; } sw_zend_fci_cache_persist(&task->fci_cache); sw_zend_fci_params_persist(&task->fci); s->list->push(task); } static PHP_METHOD(swoole_coroutine_scheduler, add) { SchedulerObject *s = scheduler_get_object(Z_OBJ_P(ZEND_THIS)); if (s->started) { php_swoole_fatal_error( E_WARNING, "scheduler is running, unable to execute %s->add", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } auto *task = static_cast(ecalloc(1, sizeof(SchedulerTask))); ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_FUNC(task->fci, task->fci_cache) Z_PARAM_VARIADIC('*', task->fci.params, task->fci.param_count) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); task->count = 1; scheduler_add_task(s, task); } static PHP_METHOD(swoole_coroutine_scheduler, parallel) { SchedulerObject *s = scheduler_get_object(Z_OBJ_P(ZEND_THIS)); if (s->started) { php_swoole_fatal_error( E_WARNING, "scheduler is running, unable to execute %s->parallel", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } auto *task = static_cast(ecalloc(1, sizeof(SchedulerTask))); zend_long count; ZEND_PARSE_PARAMETERS_START(2, -1) Z_PARAM_LONG(count) Z_PARAM_FUNC(task->fci, task->fci_cache) Z_PARAM_VARIADIC('*', task->fci.params, task->fci.param_count) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); task->count = count; scheduler_add_task(s, task); } static PHP_METHOD(swoole_coroutine_scheduler, start) { SchedulerObject *s = scheduler_get_object(Z_OBJ_P(ZEND_THIS)); if (s->started) { php_swoole_fatal_error( E_WARNING, "scheduler is started, unable to execute %s->start", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (php_swoole_reactor_init() < 0) { RETURN_FALSE; } s->started = true; if (!s->list) { php_swoole_fatal_error(E_WARNING, "no coroutine task"); RETURN_FALSE; } while (!s->list->empty()) { SchedulerTask *task = s->list->front(); s->list->pop(); for (zend_long i = 0; i < task->count; i++) { PHPCoroutine::create(&task->fci_cache, task->fci.param_count, task->fci.params, &task->fci.function_name); } sw_zend_fci_cache_discard(&task->fci_cache); sw_zend_fci_params_discard(&task->fci); efree(task); } php_swoole_event_wait(); delete s->list; s->list = nullptr; s->started = false; RETURN_TRUE; } ================================================ FILE: ext-src/swoole_coroutine_system.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_coroutine_system.h" #include "ext/standard/file.h" #include #include using swoole::Coroutine; using swoole::Event; using swoole::PHPCoroutine; using swoole::Reactor; using swoole::String; using swoole::TimerNode; using swoole::coroutine::System; enum FileOperateFlag { SW_FILE_LOCK = LOCK_EX, SW_FILE_APPEND = PHP_FILE_APPEND, }; static zend_class_entry *swoole_coroutine_system_ce; // clang-format off static const zend_function_entry swoole_coroutine_system_methods[] = { ZEND_FENTRY(gethostbyname, ZEND_FN(swoole_coroutine_gethostbyname), arginfo_class_Swoole_Coroutine_System_gethostbyname, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(dnsLookup, ZEND_FN(swoole_async_dns_lookup_coro), arginfo_class_Swoole_Coroutine_System_dnsLookup, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, exec, arginfo_class_Swoole_Coroutine_System_exec, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, sleep, arginfo_class_Swoole_Coroutine_System_sleep, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, getaddrinfo, arginfo_class_Swoole_Coroutine_System_getaddrinfo, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, statvfs, arginfo_class_Swoole_Coroutine_System_statvfs, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, readFile, arginfo_class_Swoole_Coroutine_System_readFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, writeFile, arginfo_class_Swoole_Coroutine_System_writeFile, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, wait, arginfo_class_Swoole_Coroutine_System_wait, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitPid, arginfo_class_Swoole_Coroutine_System_waitPid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitSignal, arginfo_class_Swoole_Coroutine_System_waitSignal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitEvent, arginfo_class_Swoole_Coroutine_System_waitEvent, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void php_swoole_coroutine_system_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE( swoole_coroutine_system, "Swoole\\Coroutine\\System", "Co\\System", swoole_coroutine_system_methods, nullptr); SW_SET_CLASS_CREATE(swoole_coroutine_system, sw_zend_create_object_deny); zend::add_constant("FILE_LOCK", SW_FILE_LOCK); } PHP_METHOD(swoole_coroutine_system, sleep) { double seconds; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(seconds) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(seconds < SW_TIMER_MIN_SEC)) { php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_SEC)); RETURN_FALSE; } RETURN_BOOL(System::sleep(seconds) == 0); } PHP_METHOD(swoole_coroutine_system, readFile) { char *filename; size_t l_filename; zend_long flags = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(filename, l_filename) Z_PARAM_OPTIONAL Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto result = System::read_file(filename, flags & SW_FILE_LOCK); if (result == nullptr) { RETURN_FALSE; } else { RETVAL_STRINGL(result->str, result->length); } } PHP_METHOD(swoole_coroutine_system, writeFile) { char *filename; size_t l_filename; char *data; size_t l_data; zend_long flags = 0; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STRING(filename, l_filename) Z_PARAM_STRING(data, l_data) Z_PARAM_OPTIONAL Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int _flags = 0; if (flags & SW_FILE_APPEND) { _flags |= O_APPEND; } else { _flags |= O_TRUNC; } ssize_t retval = System::write_file(filename, data, l_data, flags & SW_FILE_LOCK, _flags); if (retval < 0) { RETURN_FALSE; } else { RETURN_LONG(retval); } } PHP_FUNCTION(swoole_coroutine_gethostbyname) { Coroutine::get_current_safe(); char *domain_name; size_t l_domain_name; zend_long family = AF_INET; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STRING(domain_name, l_domain_name) Z_PARAM_OPTIONAL Z_PARAM_LONG(family) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (l_domain_name == 0) { php_swoole_fatal_error(E_WARNING, "domain name is empty"); RETURN_FALSE; } if (family != AF_INET && family != AF_INET6) { php_swoole_fatal_error(E_WARNING, "unknown protocol family, must be AF_INET or AF_INET6"); RETURN_FALSE; } std::string address = System::gethostbyname(std::string(domain_name, l_domain_name), family, timeout); if (address.empty()) { RETURN_FALSE; } else { RETURN_STRINGL(address.c_str(), address.length()); } } PHP_FUNCTION(swoole_clear_dns_cache) { System::clear_dns_cache(); } PHP_METHOD(swoole_coroutine_system, getaddrinfo) { char *hostname; size_t l_hostname; zend_long family = AF_INET; zend_long socktype = SOCK_STREAM; zend_long protocol = IPPROTO_TCP; char *service = nullptr; size_t l_service = 0; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 6) Z_PARAM_STRING(hostname, l_hostname) Z_PARAM_OPTIONAL Z_PARAM_LONG(family) Z_PARAM_LONG(socktype) Z_PARAM_LONG(protocol) Z_PARAM_STRING(service, l_service) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (l_hostname == 0) { php_swoole_fatal_error(E_WARNING, "hostname is empty"); RETURN_FALSE; } if (family != AF_INET && family != AF_INET6) { php_swoole_fatal_error(E_WARNING, "unknown protocol family, must be AF_INET or AF_INET6"); RETURN_FALSE; } std::string str_service(service ? service : ""); std::vector result = System::getaddrinfo(hostname, family, socktype, protocol, str_service, timeout); if (result.empty()) { RETURN_FALSE; } array_init(return_value); for (auto &i : result) { add_next_index_stringl(return_value, i.c_str(), i.length()); } } PHP_METHOD(swoole_coroutine_system, statvfs) { char *path; size_t l_path; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(path, l_path) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); struct statvfs _stat; swoole_coroutine_statvfs(path, &_stat); array_init(return_value); add_assoc_long(return_value, "bsize", _stat.f_bsize); add_assoc_long(return_value, "frsize", _stat.f_frsize); add_assoc_long(return_value, "blocks", _stat.f_blocks); add_assoc_long(return_value, "bfree", _stat.f_bfree); add_assoc_long(return_value, "bavail", _stat.f_bavail); add_assoc_long(return_value, "files", _stat.f_files); add_assoc_long(return_value, "ffree", _stat.f_ffree); add_assoc_long(return_value, "favail", _stat.f_favail); add_assoc_long(return_value, "fsid", _stat.f_fsid); add_assoc_long(return_value, "flag", _stat.f_flag); add_assoc_long(return_value, "namemax", _stat.f_namemax); } PHP_METHOD(swoole_coroutine_system, exec) { char *command; size_t command_len; zend_bool get_error_stream = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(command, command_len) Z_PARAM_OPTIONAL Z_PARAM_BOOL(get_error_stream) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int status; auto buffer = std::shared_ptr(swoole::make_string(1024, sw_zend_string_allocator())); if (!System::exec(command, get_error_stream, buffer, &status)) { RETURN_FALSE; } auto str = zend::fetch_zend_string_by_val(buffer->str); buffer->set_null_terminated(); str->len = buffer->length; buffer->release(); zval zdata; ZVAL_STR(&zdata, str); array_init(return_value); add_assoc_long(return_value, "code", WEXITSTATUS(status)); add_assoc_long(return_value, "signal", WTERMSIG(status)); add_assoc_zval(return_value, "output", &zdata); } static void swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAMETERS, pid_t pid, double timeout) { int status; Coroutine::get_current_safe(); if (pid < 0) { pid = System::wait(&status, timeout); } else { pid = System::waitpid(pid, &status, 0, timeout); } if (pid > 0) { array_init(return_value); add_assoc_long(return_value, "pid", pid); add_assoc_long(return_value, "code", WEXITSTATUS(status)); add_assoc_long(return_value, "signal", WTERMSIG(status)); } else { RETURN_FALSE; } } PHP_METHOD(swoole_coroutine_system, wait) { SW_MUST_BE_MAIN_THREAD(); double timeout = -1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1, timeout); } PHP_METHOD(swoole_coroutine_system, waitPid) { SW_MUST_BE_MAIN_THREAD(); zend_long pid; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(pid) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAM_PASSTHRU, pid, timeout); } PHP_METHOD(swoole_coroutine_system, waitSignal) { SW_MUST_BE_MAIN_THREAD(); zval *zsignals; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(zsignals) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); std::vector signals; if (ZVAL_IS_ARRAY(zsignals)) { zval *item; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zsignals), item) { signals.push_back(zval_get_long(item)); } ZEND_HASH_FOREACH_END(); } else { signals.push_back(zval_get_long(zsignals)); } int signo = System::wait_signal(signals, timeout); if (signo == -1) { if (swoole_get_last_error() == EBUSY) { php_swoole_fatal_error(E_WARNING, "Unable to wait signal, async signal listener has been registered"); } else if (swoole_get_last_error() == EINVAL) { php_swoole_fatal_error(E_WARNING, "Invalid signal in the given list"); } errno = swoole_get_last_error(); RETURN_FALSE; } RETURN_LONG(signo); } PHP_METHOD(swoole_coroutine_system, waitEvent) { zval *zfd; zend_long events = SW_EVENT_READ; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zfd) Z_PARAM_OPTIONAL Z_PARAM_LONG(events) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int fd = php_swoole_convert_to_fd(zfd); if (fd < 0) { php_swoole_fatal_error(E_WARNING, "unknown fd type"); RETURN_FALSE; } events = System::wait_event(fd, events, timeout); if (events < 0) { RETURN_FALSE; } RETURN_LONG(events); } ================================================ FILE: ext-src/swoole_curl.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_curl.h" #include "swoole_socket.h" #ifdef SW_USE_CURL namespace swoole { namespace curl { Handle *get_handle(CURL *cp) { Handle *handle; if (curl_easy_getinfo(cp, CURLINFO_PRIVATE, (void *) &handle) == CURLE_OK) { return handle; } else { return nullptr; } } Handle *create_handle(CURL *cp) { auto *handle = new Handle(cp); curl_easy_setopt(cp, CURLOPT_PRIVATE, handle); swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_MAGENTA " handle=%p, curl=%p", "[CREATE]", handle, cp); return handle; } void destroy_handle(CURL *cp) { auto handle = get_handle(cp); if (!handle) { return; } delete handle->easy_multi; curl_easy_setopt(cp, CURLOPT_PRIVATE, nullptr); swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " handle=%p, curl=%p", "[DESTROY]", handle, cp); delete handle; } static int execute_callback(Event *event, int bitmask) { auto curl_socket = static_cast(event->socket->object); curl_socket->bitmask |= bitmask; curl_socket->multi->callback(curl_socket, bitmask, event->fd); return 0; } Multi::Multi() { multi_handle_ = curl_multi_init(); co = nullptr; curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETFUNCTION, handle_socket); curl_multi_setopt(multi_handle_, CURLMOPT_TIMERFUNCTION, handle_timeout); curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETDATA, this); curl_multi_setopt(multi_handle_, CURLMOPT_TIMERDATA, this); } Multi::~Multi() { del_timer(); curl_multi_cleanup(multi_handle_); } int Multi::cb_readable(Reactor *reactor, Event *event) { return execute_callback(event, CURL_CSELECT_IN); } int Multi::cb_writable(Reactor *reactor, Event *event) { return execute_callback(event, CURL_CSELECT_OUT); } int Multi::cb_error(Reactor *reactor, Event *event) { return execute_callback(event, CURL_CSELECT_ERR); } int Multi::handle_socket(CURL *cp, curl_socket_t sockfd, int action, void *userp, void *socketp) { auto *multi = static_cast(userp); swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_CYAN "curl=%p, sockfd=%d, action=%d, userp=%p, socketp=%p", "[HANDLE_SOCKET]", cp, sockfd, action, userp, socketp); switch (action) { case CURL_POLL_IN: case CURL_POLL_OUT: case CURL_POLL_INOUT: return multi->set_event(socketp, sockfd, action); case CURL_POLL_REMOVE: return multi->del_event(socketp, sockfd); default: abort(); } return 0; } int Multi::del_event(void *socket_ptr, curl_socket_t sockfd) { sockets.erase(sockfd); curl_multi_assign(multi_handle_, sockfd, nullptr); if (sw_unlikely(!socket_ptr)) { return SW_ERR; } auto curl_socket = static_cast(socket_ptr); if (curl_socket->socket->events && sw_likely(swoole_event_is_available())) { curl_socket->socket->silent_remove = 1; swoole_event_del(curl_socket->socket); } swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " socket_ptr=%p, fd=%d", "[DEL_EVENT]", socket_ptr, sockfd); curl_socket->socket->fd = -1; curl_socket->socket->free(); if (selector.executing) { curl_socket->deleted = true; selector.release_sockets.insert(curl_socket); } else { delete curl_socket; } return SW_OK; } int Multi::set_event(void *socket_ptr, curl_socket_t sockfd, int action) { if (sw_unlikely(!swoole_event_is_available())) { return -1; } if (sw_unlikely(!swoole_event_isset_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_READ))) { swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_READ, cb_readable); swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_WRITE, cb_writable); swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_ERROR, cb_error); } Socket *curl_socket; if (socket_ptr) { curl_socket = (Socket *) socket_ptr; } else { curl_socket = new Socket(); if (sw_unlikely(curl_multi_assign(multi_handle_, sockfd, curl_socket) != CURLM_OK)) { delete curl_socket; return -1; } curl_socket->socket = new network::Socket(); curl_socket->socket->fd = sockfd; curl_socket->socket->removed = 1; curl_socket->socket->fd_type = static_cast(PHP_SWOOLE_FD_CO_CURL); curl_socket->socket->object = curl_socket; curl_socket->multi = this; sockets[sockfd] = curl_socket; } curl_socket->sockfd = sockfd; curl_socket->action = action; int events = 0; if (action != CURL_POLL_IN) { events |= SW_EVENT_WRITE; } if (action != CURL_POLL_OUT) { events |= SW_EVENT_READ; } swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_GREEN " curl_socket=%p, fd=%d, events=%d", "[ADD_EVENT]", curl_socket, sockfd, events); if (curl_socket->socket->events) { return swoole_event_set(curl_socket->socket, events); } else { return swoole_event_add(curl_socket->socket, events); } } CURLMcode Multi::add_handle(Handle *handle) { auto retval = curl_multi_add_handle(multi_handle_, handle->cp); if (retval == CURLM_OK) { handle->multi = this; swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_GREEN " handle=%p, curl=%p, multi=%p, running_handles=%d", "[ADD_HANDLE]", handle, handle->cp, this, running_handles_); } return retval; } CURLMcode Multi::remove_handle(Handle *handle) const { swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " handle=%p, curl=%p, multi=%p, running_handles=%d", "[REMOVE_HANDLE]", handle, handle->cp, handle->multi, handle->multi->running_handles_); const auto rc = curl_multi_remove_handle(multi_handle_, handle->cp); handle->multi = nullptr; return rc; } void Multi::selector_prepare() { for (auto it : sockets) { Socket *curl_socket = it.second; if (curl_socket->socket->removed) { swoole_event_add(curl_socket->socket, get_event(curl_socket->action)); swoole_trace_log(SW_TRACE_CO_CURL, "resume, curl_socket=%p, fd=%d, action=%d", curl_socket, curl_socket->socket->get_fd(), curl_socket->action); } } } CURLcode Multi::exec(Handle *handle) { if (add_handle(handle) != CURLM_OK) { return CURLE_FAILED_INIT; } bool is_canceled = false; SW_LOOP { selector_prepare(); if (wait_event()) { co = check_bound_co(); co->yield_ex(-1); is_canceled = co->is_canceled(); co = nullptr; if (is_canceled) { swoole_set_last_error(SW_ERROR_CO_CANCELED); break; } } selector_finish(); if (running_handles_ == 0) { break; } set_timer(); } del_timer(); CURLcode retval = read_info(); remove_handle(handle); return is_canceled ? CURLE_ABORTED_BY_CALLBACK : retval; } CURLcode Multi::read_info() const { CURLMsg *message; int pending; while ((message = curl_multi_info_read(multi_handle_, &pending))) { switch (message->msg) { case CURLMSG_DONE: /* Do not use message data after calling curl_multi_remove_handle() and curl_easy_cleanup(). As per curl_multi_info_read() docs: "WARNING: The data the returned pointer points to will not survive calling curl_multi_cleanup, curl_multi_remove_handle or curl_easy_cleanup." */ return message->data.result; default: swoole_warning("CURLMSG default"); break; } } return CURLE_OK; } int Multi::handle_timeout(CURLM *mh, long timeout_ms, void *userp) { auto *multi = static_cast(userp); swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_BLUE " timeout_ms=%ld", "[HANDLE_TIMEOUT]", timeout_ms); if (sw_unlikely(!swoole_event_is_available())) { return -1; } if (timeout_ms < 0) { if (multi->timer) { multi->del_timer(); } else { multi->add_timer(1000); } } else { if (timeout_ms == 0) { timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in a bit */ } multi->add_timer(timeout_ms); } return 0; } void Multi::selector_finish() { del_timer(); selector.executing = true; if (selector.timer_callback) { selector.timer_callback = false; curl_multi_socket_action(multi_handle_, CURL_SOCKET_TIMEOUT, 0, &running_handles_); swoole_trace_log(SW_TRACE_CO_CURL, "socket_action[timer], running_handles=%d", running_handles_); } while (!selector.active_sockets.empty()) { auto active_sockets = selector.active_sockets; selector.active_sockets.clear(); for (auto curl_socket : active_sockets) { /** * In `curl_multi_socket_action`, `Handle::destroy_socket()` may be invoked, * which will remove entries from the `unordered_map`. * In C++, removing elements during iteration can render the iterator invalid; hence, * it's necessary to copy `handle->sockets` into a new `unordered_map`. */ swoole_trace_log(SW_TRACE_CO_CURL, "curl_multi_socket_action(): sockfd=%d, bitmask=%d, running_handles_=%d", curl_socket->sockfd, curl_socket->bitmask, running_handles_); if (!curl_socket->deleted) { int bitmask = curl_socket->bitmask; curl_socket->bitmask = 0; curl_multi_socket_action(multi_handle_, curl_socket->sockfd, bitmask, &running_handles_); } } } selector.executing = false; for (auto curl_socket : selector.release_sockets) { delete curl_socket; } selector.release_sockets.clear(); } long Multi::select(php_curlm *mh, double timeout) { if (zend_llist_count(&mh->easyh) == 0) { return 0; } if (curl_multi_socket_all(multi_handle_, &running_handles_) != CURLM_OK) { return CURLE_FAILED_INIT; } selector_prepare(); set_timer(); // no events and timers, should not be suspended if (!wait_event()) { return 0; } co = check_bound_co(); co->yield_ex(timeout); co = nullptr; swoole_trace_log(SW_TRACE_CO_CURL, "yield timeout, count=%lu", zend_llist_count(&mh->easyh)); const auto count = selector.active_sockets.size(); selector_finish(); return static_cast(count); } void Multi::callback(Socket *curl_socket, int bitmask, int sockfd) { swoole_trace_log( SW_TRACE_CO_CURL, "curl_socket=%p, bitmask=%d, co=%p, sockfd=%d", curl_socket, bitmask, co, sockfd); if (!curl_socket) { selector.timer_callback = true; } if (!co) { if (curl_socket) { swoole_event_del(curl_socket->socket); } else { del_timer(); } return; } if (curl_socket) { selector.active_sockets.insert(curl_socket); } if (defer_callback) { return; } defer_callback = true; swoole_event_defer( [this](void *data) { defer_callback = false; if (co) { co->resume(); } }, nullptr); } } // namespace curl } // namespace swoole CURLcode swoole_curl_easy_perform(CURL *cp) { auto handle = swoole::curl::get_handle(cp); if (!handle->easy_multi) { handle->easy_multi = new Multi(); } return handle->easy_multi->exec(handle); } void swoole_curl_easy_reset(CURL *cp) { auto handle = swoole::curl::get_handle(cp); curl_easy_reset(cp); curl_easy_setopt(cp, CURLOPT_PRIVATE, handle); } php_curl *swoole_curl_get_handle(zval *zid, bool exclusive, bool required) { php_curl *ch = Z_CURL_P(zid); if (SWOOLE_G(req_status) == PHP_SWOOLE_RSHUTDOWN_END) { exclusive = false; } if (exclusive && swoole_coroutine_is_in()) { auto handle = swoole::curl::get_handle(ch->cp); if (required && !handle) { php_swoole_fatal_error(E_WARNING, "The given handle is not initialized in coroutine"); return nullptr; } if (handle && handle->multi && handle->multi->check_bound_co() == nullptr) { return nullptr; } } return ch; } #endif ================================================ FILE: ext-src/swoole_curl_interface.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #pragma once #include "php_swoole_cxx.h" #ifdef SW_USE_CURL SW_EXTERN_C_BEGIN #include #include #define fread swoole_coroutine_fread #define fwrite swoole_coroutine_fwrite #define curl_easy_reset swoole_curl_easy_reset void swoole_native_curl_minit(int module_number); void swoole_native_curl_mshutdown(); PHP_FUNCTION(swoole_native_curl_close); PHP_FUNCTION(swoole_native_curl_copy_handle); PHP_FUNCTION(swoole_native_curl_errno); PHP_FUNCTION(swoole_native_curl_error); PHP_FUNCTION(swoole_native_curl_exec); PHP_FUNCTION(swoole_native_curl_getinfo); PHP_FUNCTION(swoole_native_curl_init); PHP_FUNCTION(swoole_native_curl_setopt); PHP_FUNCTION(swoole_native_curl_setopt_array); PHP_FUNCTION(swoole_native_curl_reset); PHP_FUNCTION(swoole_native_curl_escape); PHP_FUNCTION(swoole_native_curl_unescape); PHP_FUNCTION(swoole_native_curl_pause); PHP_FUNCTION(swoole_native_curl_multi_add_handle); PHP_FUNCTION(swoole_native_curl_multi_close); PHP_FUNCTION(swoole_native_curl_multi_errno); PHP_FUNCTION(swoole_native_curl_multi_exec); PHP_FUNCTION(swoole_native_curl_multi_select); PHP_FUNCTION(swoole_native_curl_multi_remove_handle); PHP_FUNCTION(swoole_native_curl_multi_setopt); PHP_FUNCTION(swoole_native_curl_multi_getcontent); PHP_FUNCTION(swoole_native_curl_multi_info_read); PHP_FUNCTION(swoole_native_curl_multi_init); #if LIBCURL_VERSION_NUM >= 0x073E00 PHP_FUNCTION(swoole_native_curl_upkeep); #endif SW_EXTERN_C_END #endif ================================================ FILE: ext-src/swoole_event.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "swoole_server.h" #include "swoole_signal.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_event_arginfo.h" END_EXTERN_C() using namespace swoole; using swoole::network::Socket; static std::unordered_map event_socket_map; zend_class_entry *swoole_event_ce; static zend_object_handlers swoole_event_handlers; struct EventObject { zval zsocket; zend::Callable *readable_callback; zend::Callable *writable_callback; }; static int event_readable_callback(Reactor *reactor, Event *event); static int event_writable_callback(Reactor *reactor, Event *event); static int event_error_callback(Reactor *reactor, Event *event); static void event_defer_callback(void *data); static void event_cycle_callback(void *data); SW_EXTERN_C_BEGIN static PHP_FUNCTION(swoole_event_add); static PHP_FUNCTION(swoole_event_set); static PHP_FUNCTION(swoole_event_del); static PHP_FUNCTION(swoole_event_write); static PHP_FUNCTION(swoole_event_wait); static PHP_FUNCTION(swoole_event_rshutdown); static PHP_FUNCTION(swoole_event_exit); static PHP_FUNCTION(swoole_event_defer); static PHP_FUNCTION(swoole_event_cycle); static PHP_FUNCTION(swoole_event_dispatch); static PHP_FUNCTION(swoole_event_isset); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_event_methods[] = { ZEND_FENTRY(add, ZEND_FN(swoole_event_add), arginfo_swoole_event_add, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(del, ZEND_FN(swoole_event_del), arginfo_swoole_event_del, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(set, ZEND_FN(swoole_event_set), arginfo_swoole_event_set, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(isset, ZEND_FN(swoole_event_isset), arginfo_swoole_event_isset, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(dispatch, ZEND_FN(swoole_event_dispatch), arginfo_swoole_event_dispatch, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(defer, ZEND_FN(swoole_event_defer), arginfo_swoole_event_defer, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(cycle, ZEND_FN(swoole_event_cycle), arginfo_swoole_event_cycle, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(write, ZEND_FN(swoole_event_write), arginfo_swoole_event_write, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(wait, ZEND_FN(swoole_event_wait), arginfo_swoole_event_wait, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(rshutdown, ZEND_FN(swoole_event_rshutdown), arginfo_swoole_event_rshutdown, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(exit, ZEND_FN(swoole_event_exit), arginfo_swoole_event_exit, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void php_swoole_event_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_event, "Swoole\\Event", nullptr, swoole_event_methods); SW_SET_CLASS_CREATE(swoole_event, sw_zend_create_object_deny); SW_FUNCTION_ALIAS( &swoole_event_ce->function_table, "add", CG(function_table), "swoole_event_add", arginfo_swoole_event_add); SW_FUNCTION_ALIAS( &swoole_event_ce->function_table, "del", CG(function_table), "swoole_event_del", arginfo_swoole_event_del); SW_FUNCTION_ALIAS( &swoole_event_ce->function_table, "set", CG(function_table), "swoole_event_set", arginfo_swoole_event_set); SW_FUNCTION_ALIAS( &swoole_event_ce->function_table, "wait", CG(function_table), "swoole_event_wait", arginfo_swoole_event_wait); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "isset", CG(function_table), "swoole_event_isset", arginfo_swoole_event_isset); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "dispatch", CG(function_table), "swoole_event_dispatch", arginfo_swoole_event_dispatch); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "defer", CG(function_table), "swoole_event_defer", arginfo_swoole_event_defer); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "cycle", CG(function_table), "swoole_event_cycle", arginfo_swoole_event_cycle); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "write", CG(function_table), "swoole_event_write", arginfo_swoole_event_write); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "exit", CG(function_table), "swoole_event_exit", arginfo_swoole_event_exit); SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "rshutdown", CG(function_table), "swoole_event_rshutdown", arginfo_swoole_event_rshutdown); } static void event_object_free(void *data) { auto *peo = static_cast(data); delete peo->readable_callback; delete peo->writable_callback; zval_ptr_dtor((&peo->zsocket)); efree(peo); } static int event_readable_callback(Reactor *reactor, Event *event) { auto *peo = static_cast(event->socket->object); zval argv[1]; argv[0] = peo->zsocket; auto fcc = peo->readable_callback->ptr(); if (UNEXPECTED(!zend::function::call(fcc, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error(E_WARNING, "%s: readable callback handler error, fd [%d] will be removed from reactor", ZSTR_VAL(swoole_event_ce->name), php_swoole_convert_to_fd(&peo->zsocket)); event->socket->object = nullptr; swoole_event_defer(event_object_free, peo); swoole_event_del(event->socket); return SW_ERR; } return SW_OK; } static int event_writable_callback(Reactor *reactor, Event *event) { auto *peo = static_cast(event->socket->object); zval argv[1]; argv[0] = peo->zsocket; auto fcc = peo->writable_callback->ptr(); if (UNEXPECTED(!zend::function::call(fcc, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error(E_WARNING, "%s: writable callback handler error, fd [%d] will be removed from reactor", ZSTR_VAL(swoole_event_ce->name), php_swoole_convert_to_fd(&peo->zsocket)); event->socket->object = nullptr; swoole_event_defer(event_object_free, peo); swoole_event_del(event->socket); return SW_ERR; } return SW_OK; } static int event_error_callback(Reactor *reactor, Event *event) { if (!(event->socket->events & SW_EVENT_ERROR)) { if (event->socket->events & SW_EVENT_READ) { return reactor->get_handler(event->socket->fd_type, SW_EVENT_READ)(reactor, event); } else { return reactor->get_handler(event->socket->fd_type, SW_EVENT_WRITE)(reactor, event); } } int error; if (event->socket->get_option(SOL_SOCKET, SO_ERROR, &error) < 0) { php_swoole_sys_error(E_WARNING, "swoole_event->onError[1]: getsockopt[sock=%d] failed", event->fd); } if (error != 0) { php_swoole_fatal_error( E_WARNING, "swoole_event->onError[1]: socket error. Error: %s [%d]", strerror(error), error); } event_object_free(event->socket->object); swoole_event_del(event->socket); return SW_OK; } static void event_defer_callback(void *data) { auto *cb = static_cast(data); if (UNEXPECTED(!zend::function::call(cb, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s::defer callback handler error", ZSTR_VAL(swoole_event_ce->name)); } delete cb; } static void event_cycle_callback(void *data) { auto *cb = static_cast(data); if (UNEXPECTED(!zend::function::call(cb, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s::end callback handler error", ZSTR_VAL(swoole_event_ce->name)); } } int php_swoole_reactor_init() { if (!SWOOLE_G(cli)) { php_swoole_fatal_error(E_ERROR, "async-io must be used in PHP CLI mode"); return SW_ERR; } if (sw_server()) { if (sw_server()->is_task_worker() && !sw_server()->task_enable_coroutine) { php_swoole_fatal_error( E_ERROR, "Unable to use async-io in task processes, please set `task_enable_coroutine` to true"); return SW_ERR; } if (sw_server()->is_manager()) { php_swoole_fatal_error(E_ERROR, "Unable to use async-io in manager process"); return SW_ERR; } } if (!sw_reactor()) { swoole_trace_log(SW_TRACE_PHP, "init reactor"); if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { php_swoole_fatal_error(E_ERROR, "Unable to create event-loop reactor"); return SW_ERR; } php_swoole_register_shutdown_function("swoole_event_rshutdown"); } if (sw_reactor() && SwooleG.user_exit_condition && !sw_reactor()->isset_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT)) { sw_reactor()->set_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT, SwooleG.user_exit_condition); } return SW_OK; } void php_swoole_event_wait() { if (php_swoole_is_fatal_error() || !sw_reactor()) { return; } if (swoole_coroutine_is_in()) { php_swoole_fatal_error(E_ERROR, "Unable to call Event::wait() in coroutine"); return; } if (!sw_reactor()->if_exit() && !sw_reactor()->bailout) { // Don't disable object slot reuse while running shutdown functions: // https://github.com/php/php-src/commit/bd6eabd6591ae5a7c9ad75dfbe7cc575fa907eac #if defined(EG_FLAGS_IN_SHUTDOWN) && !defined(EG_FLAGS_OBJECT_STORE_NO_REUSE) zend_bool in_shutdown = EG(flags) & EG_FLAGS_IN_SHUTDOWN; EG(flags) &= ~EG_FLAGS_IN_SHUTDOWN; #endif if (sw_reactor()->wait() < 0) { php_swoole_sys_error(E_ERROR, "reactor wait failed"); } #if defined(EG_FLAGS_IN_SHUTDOWN) && !defined(EG_FLAGS_OBJECT_STORE_NO_REUSE) if (in_shutdown) { EG(flags) |= EG_FLAGS_IN_SHUTDOWN; } #endif } swoole_event_free(); } void php_swoole_event_exit() { if (sw_reactor()) { php_swoole_timer_clear_all(); sw_reactor()->running = false; } } int php_swoole_convert_to_fd(zval *zsocket) { int fd = -1; switch (Z_TYPE_P(zsocket)) { case IS_RESOURCE: { php_stream *stream; if ((php_stream_from_zval_no_verify(stream, zsocket))) { if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void **) &fd, 1) == SUCCESS && fd >= 0) { return fd; } } php_swoole_fatal_error(E_WARNING, "fd argument must be either valid PHP stream or valid PHP socket resource"); return SW_ERR; } case IS_LONG: { fd = Z_LVAL_P(zsocket); if (fd < 0) { php_swoole_fatal_error(E_WARNING, "invalid file descriptor#%d passed", fd); return SW_ERR; } return fd; } case IS_OBJECT: { zval *zfd = nullptr; if (sw_zval_is_co_socket(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_FD), 0); } else if (sw_zval_is_client(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_SOCK), 0); } else if (sw_zval_is_process(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_PIPE), 0); #ifdef SWOOLE_SOCKETS_SUPPORT } else if (sw_zval_is_php_socket(zsocket)) { php_socket *php_sock = SW_Z_SOCKET_P(zsocket); if (IS_INVALID_SOCKET(php_sock)) { php_swoole_fatal_error(E_WARNING, "contains a closed socket"); return SW_ERR; } return php_sock->bsd_socket; #endif } if (zfd == nullptr || Z_TYPE_P(zfd) != IS_LONG) { return SW_ERR; } return Z_LVAL_P(zfd); } default: php_swoole_fatal_error(E_WARNING, "invalid file descriptor passed"); return SW_ERR; } } int php_swoole_convert_to_fd_ex(zval *zsocket, int *async) { int fd; *async = 0; if (Z_TYPE_P(zsocket) == IS_RESOURCE) { php_stream *stream; if ((php_stream_from_zval_no_verify(stream, zsocket))) { if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void **) &fd, 1) == SUCCESS && fd >= 0) { *async = (stream->wrapper && (stream->wrapper->wops == php_plain_files_wrapper.wops)) ? 0 : 1; return fd; } } #ifdef SWOOLE_SOCKETS_SUPPORT else { php_socket *php_sock; if ((php_sock = SW_Z_SOCKET_P(zsocket))) { fd = php_sock->bsd_socket; *async = 1; return fd; } } #endif } php_swoole_fatal_error(E_WARNING, "fd argument must be either valid PHP stream or valid PHP socket resource"); return SW_ERR; } #ifdef SWOOLE_SOCKETS_SUPPORT php_socket *php_swoole_convert_to_socket(int sock) { php_socket *socket_object; zval zsocket; object_init_ex(&zsocket, socket_ce); socket_object = Z_SOCKET_P(&zsocket); auto new_sock = dup(sock); if (new_sock < 0) { return nullptr; } socket_import_file_descriptor(new_sock, socket_object); return socket_object; } #endif static void event_check_reactor() { php_swoole_check_reactor(); if (!swoole_event_isset_handler(SW_FD_USER, SW_EVENT_READ)) { swoole_event_set_handler(SW_FD_USER, SW_EVENT_READ, event_readable_callback); swoole_event_set_handler(SW_FD_USER, SW_EVENT_WRITE, event_writable_callback); swoole_event_set_handler(SW_FD_USER, SW_EVENT_ERROR, event_error_callback); } } static Socket *event_get_socket(int socket_fd) { auto i = event_socket_map.find(socket_fd); if (i == event_socket_map.end()) { return nullptr; } return i->second; } static PHP_FUNCTION(swoole_event_add) { zval *zfd; zend_long events = SW_EVENT_READ; zval *zreadable_callback = nullptr; zval *zwritable_callback = nullptr; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_ZVAL(zfd) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(zreadable_callback) Z_PARAM_ZVAL(zwritable_callback) Z_PARAM_LONG(events) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); event_check_reactor(); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { php_swoole_fatal_error(E_WARNING, "unknown fd type"); RETURN_FALSE; } if (socket_fd == 0 && (events & SW_EVENT_WRITE)) { php_swoole_fatal_error(E_WARNING, "invalid socket fd [%d]", socket_fd); RETURN_FALSE; } if (event_socket_map.find(socket_fd) != event_socket_map.end()) { php_swoole_fatal_error(E_WARNING, "already exist"); RETURN_FALSE; } if (!(events & (SW_EVENT_WRITE | SW_EVENT_READ))) { php_swoole_fatal_error(E_WARNING, "invalid events"); RETURN_FALSE; } auto readable_callback = sw_callable_create_ex(zreadable_callback, "readable_callback", true); if ((events & SW_EVENT_READ) && readable_callback == nullptr) { php_swoole_fatal_error( E_WARNING, "%s: unable to find readable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); RETURN_FALSE; } auto writable_callback = sw_callable_create_ex(zwritable_callback, "writable_callback", true); if ((events & SW_EVENT_WRITE) && writable_callback == nullptr) { php_swoole_fatal_error( E_WARNING, "%s: unable to find writable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); delete readable_callback; RETURN_FALSE; } auto *peo = static_cast(ecalloc(1, sizeof(EventObject))); Z_TRY_ADDREF_P(zfd); peo->zsocket = *zfd; peo->readable_callback = readable_callback; peo->writable_callback = writable_callback; Socket *socket = make_socket(socket_fd, SW_FD_USER); socket->set_nonblock(); socket->object = peo; if (swoole_event_add(socket, events) < 0) { php_swoole_fatal_error(E_WARNING, "swoole_event_add failed"); socket->free(); event_object_free(peo); RETURN_FALSE; } event_socket_map[socket_fd] = socket; RETURN_LONG(socket_fd); } static PHP_FUNCTION(swoole_event_write) { zval *zfd; char *data; size_t len; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zfd) Z_PARAM_STRING(data, len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (len == 0) { php_swoole_fatal_error(E_WARNING, "data empty"); RETURN_FALSE; } int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { php_swoole_fatal_error(E_WARNING, "unknown type"); RETURN_FALSE; } Socket *socket = event_get_socket(socket_fd); if (socket == nullptr) { php_swoole_fatal_error(E_WARNING, "socket[%d] is not found in the reactor", socket_fd); RETURN_FALSE; } event_check_reactor(); if (swoole_event_write(socket, data, len) < 0) { RETURN_FALSE; } else { RETURN_TRUE; } } static PHP_FUNCTION(swoole_event_set) { if (!sw_reactor()) { php_swoole_fatal_error(E_WARNING, "reactor is not ready, cannot call swoole_event_set"); RETURN_FALSE; } zval *zfd; zend_long events = 0; zval *zreadable_callback = nullptr; zval *zwritable_callback = nullptr; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_ZVAL(zfd) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(zreadable_callback) Z_PARAM_ZVAL(zwritable_callback) Z_PARAM_LONG(events) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { RETURN_FALSE; } Socket *socket = event_get_socket(socket_fd); if (socket == nullptr) { php_swoole_fatal_error(E_WARNING, "socket[%d] is not found in the reactor", socket_fd); RETURN_FALSE; } auto *peo = static_cast(socket->object); auto readable_callback = sw_callable_create_ex(zreadable_callback, "readable_callback"); auto writable_callback = sw_callable_create_ex(zwritable_callback, "writable_callback"); if (readable_callback) { if (peo->readable_callback) { swoole_event_defer(sw_callable_free, peo->readable_callback); } peo->readable_callback = readable_callback; } if (writable_callback) { if (peo->writable_callback) { swoole_event_defer(sw_callable_free, peo->writable_callback); } peo->writable_callback = writable_callback; } if ((events & SW_EVENT_READ) && peo->readable_callback == nullptr) { php_swoole_fatal_error( E_WARNING, "%s: unable to find readable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); RETURN_FALSE; } if ((events & SW_EVENT_WRITE) && peo->writable_callback == nullptr) { php_swoole_fatal_error( E_WARNING, "%s: unable to find writable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); RETURN_FALSE; } if (swoole_event_set(socket, events) < 0) { php_swoole_fatal_error(E_WARNING, "%s::set failed", ZSTR_VAL(swoole_event_ce->name)); RETURN_FALSE; } RETURN_TRUE; } static PHP_FUNCTION(swoole_event_del) { zval *zfd; if (!sw_reactor()) { php_swoole_fatal_error(E_WARNING, "reactor is not ready, cannot call swoole_event_del"); RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zfd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { php_swoole_fatal_error(E_WARNING, "unknown type"); RETURN_FALSE; } Socket *socket = event_get_socket(socket_fd); if (!socket) { RETURN_FALSE; } swoole_event_defer(event_object_free, socket->object); int retval = swoole_event_del(socket); event_socket_map.erase(socket_fd); socket->fd = -1; socket->free(); RETURN_BOOL(retval == SW_OK); } static PHP_FUNCTION(swoole_event_defer) { zval *zfn; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); php_swoole_check_reactor(); auto fn = sw_callable_create(zfn); swoole_event_defer(event_defer_callback, fn); RETURN_TRUE; } static PHP_FUNCTION(swoole_event_cycle) { zval *zcallback; zend_bool before = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(zcallback) Z_PARAM_OPTIONAL Z_PARAM_BOOL(before) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); event_check_reactor(); auto reactor = sw_reactor(); auto defer_task = before ? &reactor->future_task : &reactor->idle_task; if (ZVAL_IS_NULL(zcallback)) { if (defer_task->callback == nullptr) { RETURN_FALSE; } else { swoole_event_defer(sw_callable_free, defer_task->data); defer_task->callback = nullptr; defer_task->data = nullptr; } } else { if (defer_task->data != nullptr) { swoole_event_defer(sw_callable_free, defer_task->data); } auto callback = sw_callable_create(zcallback); defer_task->callback = event_cycle_callback; defer_task->data = callback; } RETURN_TRUE; } static PHP_FUNCTION(swoole_event_exit) { php_swoole_event_exit(); } static PHP_FUNCTION(swoole_event_wait) { if (!sw_reactor()) { return; } php_swoole_event_wait(); } static PHP_FUNCTION(swoole_event_rshutdown) { /* prevent the program from jumping out of the rshutdown */ zend_try { // when throw Exception, do not show the info if (!php_swoole_is_fatal_error() && sw_reactor()) { if (!sw_reactor()->bailout) { php_swoole_fatal_error(E_DEPRECATED, "Event::wait() in shutdown function is deprecated"); } php_swoole_event_wait(); } } zend_end_try(); } static PHP_FUNCTION(swoole_event_dispatch) { if (!sw_reactor()) { RETURN_FALSE; } sw_reactor()->once = true; if (sw_reactor()->wait() < 0) { php_swoole_sys_error(E_ERROR, "reactor wait failed"); } sw_reactor()->once = false; RETURN_TRUE; } static PHP_FUNCTION(swoole_event_isset) { if (!sw_reactor()) { RETURN_FALSE; } zval *zfd; zend_long events = SW_EVENT_READ | SW_EVENT_WRITE; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|l", &zfd, &events) == FAILURE) { RETURN_FALSE; } int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { php_swoole_fatal_error(E_WARNING, "unknown type"); RETURN_FALSE; } Socket *_socket = event_get_socket(socket_fd); if (_socket == nullptr || _socket->removed) { RETURN_FALSE; } if (_socket->events & events) { RETURN_TRUE; } else { RETURN_FALSE; } } ================================================ FILE: ext-src/swoole_firebird.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "php_swoole_cxx.h" #include "swoole_coroutine.h" #ifdef SW_USE_FIREBIRD #include "php_swoole_firebird.h" using swoole::Coroutine; static SW_THREAD_LOCAL bool swoole_firebird_blocking = true; void swoole_firebird_set_blocking(bool blocking) { swoole_firebird_blocking = blocking; } ISC_STATUS swoole_isc_attach_database( ISC_STATUS *_0, short _1, const ISC_SCHAR *_2, isc_db_handle *_3, short _4, const ISC_SCHAR *_5) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_attach_database"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_attach_database(_0, _1, _2, _3, _4, _5); }); return result; } ISC_STATUS swoole_isc_detach_database(ISC_STATUS *_0, isc_db_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_detach_database"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_detach_database(_0, _1); }); return result; } ISC_STATUS swoole_isc_dsql_execute( ISC_STATUS *_0, isc_tr_handle *_1, isc_stmt_handle *_2, unsigned short _3, const XSQLDA *_4) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_execute"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_execute(_0, _1, _2, _3, _4); }); return result; } ISC_STATUS swoole_isc_dsql_execute2( ISC_STATUS *_0, isc_tr_handle *_1, isc_stmt_handle *_2, unsigned short _3, const XSQLDA *_4, const XSQLDA *_5) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_execute2"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_execute2(_0, _1, _2, _3, _4, _5); }); return result; } ISC_STATUS swoole_isc_dsql_sql_info( ISC_STATUS *_0, isc_stmt_handle *_1, short _2, const ISC_SCHAR *_3, short _4, ISC_SCHAR *_5) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_sql_info"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_sql_info(_0, _1, _2, _3, _4, _5); }); return result; } ISC_STATUS swoole_isc_dsql_free_statement(ISC_STATUS *_0, isc_stmt_handle *_1, unsigned short _2) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_free_statement"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_free_statement(_0, _1, _2); }); return result; } ISC_STATUS swoole_isc_start_transaction( ISC_STATUS *_0, isc_tr_handle *_1, short _2, isc_db_handle *_3, size_t _4, char *_5) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_start_transaction"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_start_transaction(_0, _1, _2, _3, _4, _5); }); return result; } ISC_STATUS swoole_isc_commit_retaining(ISC_STATUS *_0, isc_tr_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_commit_retaining"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_commit_retaining(_0, _1); }); return result; } ISC_STATUS swoole_isc_commit_transaction(ISC_STATUS *_0, isc_tr_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_commit_transaction"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_commit_transaction(_0, _1); }); return result; } ISC_STATUS swoole_isc_rollback_transaction(ISC_STATUS *_0, isc_tr_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_rollback_transaction"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_rollback_transaction(_0, _1); }); return result; } ISC_STATUS swoole_isc_dsql_allocate_statement(ISC_STATUS *_0, isc_db_handle *_1, isc_stmt_handle *_2) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_allocate_statement"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_allocate_statement(_0, _1, _2); }); return result; } ISC_STATUS swoole_isc_dsql_prepare(ISC_STATUS *_0, isc_tr_handle *_1, isc_stmt_handle *_2, unsigned short _3, const ISC_SCHAR *_4, unsigned short _5, XSQLDA *_6) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_prepare"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_prepare(_0, _1, _2, _3, _4, _5, _6); }); return result; } ISC_STATUS swoole_isc_dsql_fetch(ISC_STATUS *_0, isc_stmt_handle *_1, unsigned short _2, const XSQLDA *_3) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_fetch"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_fetch(_0, _1, _2, _3); }); return result; } ISC_STATUS swoole_isc_open_blob( ISC_STATUS *_0, isc_db_handle *_1, isc_tr_handle *_2, isc_blob_handle *_3, ISC_QUAD *_4) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_open_blob"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_open_blob(_0, _1, _2, _3, _4); }); return result; } ISC_STATUS swoole_isc_blob_info( ISC_STATUS *_0, isc_blob_handle *_1, short _2, const ISC_SCHAR *_3, short _4, ISC_SCHAR *_5) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_blob_info"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_blob_info(_0, _1, _2, _3, _4, _5); }); return result; } ISC_STATUS swoole_isc_get_segment( ISC_STATUS *_0, isc_blob_handle *_1, unsigned short *_2, unsigned short _3, ISC_SCHAR *_4) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_get_segment"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_get_segment(_0, _1, _2, _3, _4); }); return result; } ISC_STATUS swoole_isc_put_segment(ISC_STATUS *_0, isc_blob_handle *_1, unsigned short _2, const ISC_SCHAR *_3) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_put_segment"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_put_segment(_0, _1, _2, _3); }); return result; } ISC_STATUS swoole_isc_create_blob( ISC_STATUS *_0, isc_db_handle *_1, isc_tr_handle *_2, isc_blob_handle *_3, ISC_QUAD *_4) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_create_blob"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_create_blob(_0, _1, _2, _3, _4); }); return result; } ISC_STATUS swoole_isc_close_blob(ISC_STATUS *_0, isc_blob_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_close_blob"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_close_blob(_0, _1); }); return result; } ISC_STATUS swoole_isc_dsql_set_cursor_name(ISC_STATUS *_0, isc_stmt_handle *_1, const ISC_SCHAR *_2, unsigned short _3) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_set_cursor_name"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_set_cursor_name(_0, _1, _2, _3); }); return result; } ISC_STATUS swoole_fb_ping(ISC_STATUS *_0, isc_db_handle *_1) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "fb_ping"); ISC_STATUS result; php_swoole_async(swoole_firebird_blocking, [&]() { result = fb_ping(_0, _1); }); return result; } int swoole_isc_version(isc_db_handle *_0, ISC_VERSION_CALLBACK _1, void *_2) { swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_version"); int result; php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_version(_0, _1, _2); }); return result; } void php_swoole_firebird_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("FB_ATTR_DATE_FORMAT")) == nullptr) { #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); #else REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); #endif } php_pdo_unregister_driver(&swoole_pdo_firebird_driver); php_pdo_register_driver(&swoole_pdo_firebird_driver); } void php_swoole_firebird_mshutdown() { php_pdo_unregister_driver(&swoole_pdo_firebird_driver); } #endif ================================================ FILE: ext-src/swoole_http2_client_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_http.h" #include "swoole_string.h" #include "swoole_protocol.h" #include "swoole_socket.h" #include "swoole_util.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_http2_client_coro_arginfo.h" END_EXTERN_C() #include "swoole_http2.h" #define HTTP2_CLIENT_HOST_HEADER_INDEX 3 using namespace swoole; using swoole::http2::get_default_setting; namespace Http2 = swoole::http2; static zend_class_entry *swoole_http2_client_coro_ce; static zend_object_handlers swoole_http2_client_coro_handlers; static zend_class_entry *swoole_http2_client_coro_exception_ce; static zend_object_handlers swoole_http2_client_coro_exception_handlers; static zend_class_entry *swoole_http2_request_ce; static zend_object_handlers swoole_http2_request_handlers; static zend_class_entry *swoole_http2_response_ce; static zend_object_handlers swoole_http2_response_handlers; namespace swoole { namespace coroutine { namespace http2 { struct Stream { uint32_t stream_id; uint8_t gzip; uint8_t flags; String *buffer; #ifdef SW_HAVE_ZLIB z_stream gzip_stream; String *gzip_buffer; #endif zval zresponse; // flow control uint32_t remote_window_size; uint32_t local_window_size; }; class Client { public: std::string host; int port; bool open_ssl; double timeout = network::Socket::default_read_timeout; uint32_t stream_id = 0; // the next send stream id uint32_t last_stream_id = 0; // the last received stream id Http2::Settings local_settings = {}; Http2::Settings remote_settings = {}; // flow control uint32_t remote_window_size = 0; uint32_t local_window_size = 0; std::unordered_map streams; std::queue send_queue; /* safety zval */ zval _zobject; zval *zobject; SocketImpl *socket_ = nullptr; zval zsocket; Client(const char *_host, size_t _host_len, int _port, bool _ssl, const zval *zobj) { host = std::string(_host, _host_len); port = _port; open_ssl = _ssl; _zobject = *zobj; zobject = &_zobject; Http2::init_settings(&local_settings); local_window_size = local_settings.init_window_size; } Stream *get_stream(uint32_t _stream_id) { auto i = streams.find(_stream_id); if (i == streams.end()) { return nullptr; } else { return i->second; } } ssize_t build_header(const zval *zobj, zval *zrequest, char *buffer); void update_error_properties(int code, const char *msg) const { php_swoole_socket_set_error_properties(zobject, code, msg); } void io_error() const { update_error_properties(socket_->errCode, socket_->errMsg); } void nghttp2_error(int code, const char *msg) const { update_error_properties(code, std_string::format("%s with error: %s", msg, nghttp2_strerror(code)).c_str()); } bool is_available() const { if (sw_unlikely(!socket_ || !socket_->is_connected())) { php_swoole_socket_set_error_properties(zobject, SW_ERROR_CLIENT_NO_CONNECTION); return false; } return true; } void apply_setting(const zval *zset) const { if (socket_ && ZVAL_IS_ARRAY(zset)) { php_swoole_socket_set(socket_, zset); } } bool recv_packet(double _timeout) const { if (sw_unlikely(socket_->recv_packet(_timeout) <= 0)) { io_error(); return false; } return true; } bool connect(); Stream *create_stream(uint32_t _stream_id, uint8_t flags); void destroy_stream(Stream *stream); bool delete_stream(uint32_t _stream_id) { const auto i = streams.find(_stream_id); if (i == streams.end()) { return false; } destroy_stream(i->second); streams.erase(i); return true; } bool send_window_update(int _stream_id, uint32_t size); bool send_ping_frame(); bool send_data(uint32_t _stream_id, const char *p, size_t len, int flag); uint32_t send_request(zval *zrequest); bool write_data(uint32_t _stream_id, zval *zdata, bool end); bool send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len); ReturnCode parse_frame(zval *return_value, bool pipeline_read = false); bool close() const; void socket_dtor(); ~Client() { close(); } private: nghttp2_hd_inflater *inflater = nullptr; nghttp2_hd_deflater *deflater = nullptr; bool send_setting(); int parse_header(Stream *stream, int flags, char *in, size_t inlen) const; void clean_send_queue() { while (!send_queue.empty()) { zend_string *frame = send_queue.front(); send_queue.pop(); zend_string_release(frame); } } bool send(const char *buf, size_t len) { if (socket_->has_bound(SW_EVENT_WRITE)) { if (send_queue.size() > remote_settings.max_concurrent_streams) { socket_->errCode = SW_ERROR_QUEUE_FULL; socket_->errMsg = "the send queue is full, try again later"; io_error(); return false; } send_queue.push(zend_string_init(buf, len, false)); return true; } if (sw_unlikely(socket_->send_all(buf, len) != (ssize_t) len)) { io_error(); return false; } while (!send_queue.empty()) { zend_string *frame = send_queue.front(); if (sw_unlikely(socket_->send_all(frame->val, frame->len) != (ssize_t) frame->len)) { io_error(); zend_throw_exception(swoole_http2_client_coro_exception_ce, "failed to send control frame", SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED); return false; } send_queue.pop(); zend_string_release(frame); } return true; } }; } // namespace http2 } // namespace coroutine } // namespace swoole using swoole::coroutine::http2::Client; using swoole::coroutine::http2::Stream; using swoole::http2::HeaderSet; struct Http2ClientObject { Client *client; zend_object std; }; static sw_inline Http2ClientObject *http2_client_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http2_client_coro_handlers.offset); } static sw_inline Client *http2_client_coro_get_client(const zval *zobject) { return http2_client_coro_fetch_object(Z_OBJ_P(zobject))->client; } static void http2_client_coro_free_object(zend_object *object) { Http2ClientObject *h2o = http2_client_coro_fetch_object(object); if (h2o->client) { delete h2o->client; h2o->client = nullptr; } zend_object_std_dtor(&h2o->std); } static zend_object *http2_client_coro_create_object(zend_class_entry *ce) { auto *request = static_cast(zend_object_alloc(sizeof(Http2ClientObject), ce)); zend_object_std_init(&request->std, ce); object_properties_init(&request->std, ce); request->std.handlers = &swoole_http2_client_coro_handlers; return &request->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http2_client_coro, __construct); static PHP_METHOD(swoole_http2_client_coro, __destruct); static PHP_METHOD(swoole_http2_client_coro, set); static PHP_METHOD(swoole_http2_client_coro, connect); static PHP_METHOD(swoole_http2_client_coro, stats); static PHP_METHOD(swoole_http2_client_coro, isStreamExist); static PHP_METHOD(swoole_http2_client_coro, send); static PHP_METHOD(swoole_http2_client_coro, write); static PHP_METHOD(swoole_http2_client_coro, recv); static PHP_METHOD(swoole_http2_client_coro, read); static PHP_METHOD(swoole_http2_client_coro, ping); static PHP_METHOD(swoole_http2_client_coro, goaway); static PHP_METHOD(swoole_http2_client_coro, close); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_http2_client_methods[] = { PHP_ME(swoole_http2_client_coro, __construct, arginfo_class_Swoole_Coroutine_Http2_Client___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, __destruct, arginfo_class_Swoole_Coroutine_Http2_Client___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, set, arginfo_class_Swoole_Coroutine_Http2_Client_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, connect, arginfo_class_Swoole_Coroutine_Http2_Client_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, stats, arginfo_class_Swoole_Coroutine_Http2_Client_stats, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, isStreamExist, arginfo_class_Swoole_Coroutine_Http2_Client_isStreamExist, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, send, arginfo_class_Swoole_Coroutine_Http2_Client_send, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, write, arginfo_class_Swoole_Coroutine_Http2_Client_write, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, recv, arginfo_class_Swoole_Coroutine_Http2_Client_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, read, arginfo_class_Swoole_Coroutine_Http2_Client_read, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, goaway, arginfo_class_Swoole_Coroutine_Http2_Client_goaway, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, ping, arginfo_class_Swoole_Coroutine_Http2_Client_ping, ZEND_ACC_PUBLIC) PHP_ME(swoole_http2_client_coro, close, arginfo_class_Swoole_Coroutine_Http2_Client_close, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_http2_client_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY( swoole_http2_client_coro, "Swoole\\Coroutine\\Http2\\Client", "Co\\Http2\\Client", swoole_http2_client_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_client_coro); SW_SET_CLASS_CLONEABLE(swoole_http2_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_client_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_http2_client_coro, http2_client_coro_create_object, http2_client_coro_free_object, Http2ClientObject, std); SW_INIT_CLASS_ENTRY_EX(swoole_http2_client_coro_exception, "Swoole\\Coroutine\\Http2\\Client\\Exception", "Co\\Http2\\Client\\Exception", nullptr, swoole_exception); SW_INIT_CLASS_ENTRY(swoole_http2_request, "Swoole\\Http2\\Request", nullptr, nullptr); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_request); SW_SET_CLASS_CLONEABLE(swoole_http2_request, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_request, sw_zend_class_unset_property_deny); SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_request); SW_INIT_CLASS_ENTRY(swoole_http2_response, "Swoole\\Http2\\Response", nullptr, nullptr); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_response); SW_SET_CLASS_CLONEABLE(swoole_http2_response, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_response, sw_zend_class_unset_property_deny); SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_response); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_client_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("serverLastStreamId"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("path"), "/", ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("method"), "GET", ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("usePipelineRead"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_response_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_DATA", SW_HTTP2_TYPE_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_HEADERS", SW_HTTP2_TYPE_HEADERS); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PRIORITY", SW_HTTP2_TYPE_PRIORITY); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_RST_STREAM", SW_HTTP2_TYPE_RST_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_SETTINGS", SW_HTTP2_TYPE_SETTINGS); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PUSH_PROMISE", SW_HTTP2_TYPE_PUSH_PROMISE); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PING", SW_HTTP2_TYPE_PING); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_GOAWAY", SW_HTTP2_TYPE_GOAWAY); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_WINDOW_UPDATE", SW_HTTP2_TYPE_WINDOW_UPDATE); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_CONTINUATION", SW_HTTP2_TYPE_CONTINUATION); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_NO_ERROR", SW_HTTP2_ERROR_NO_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_PROTOCOL_ERROR", SW_HTTP2_ERROR_PROTOCOL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INTERNAL_ERROR", SW_HTTP2_ERROR_INTERNAL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FLOW_CONTROL_ERROR", SW_HTTP2_ERROR_FLOW_CONTROL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_SETTINGS_TIMEOUT", SW_HTTP2_ERROR_SETTINGS_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_STREAM_CLOSED", SW_HTTP2_ERROR_STREAM_CLOSED); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FRAME_SIZE_ERROR", SW_HTTP2_ERROR_FRAME_SIZE_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_REFUSED_STREAM", SW_HTTP2_ERROR_REFUSED_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CANCEL", SW_HTTP2_ERROR_CANCEL); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_COMPRESSION_ERROR", SW_HTTP2_ERROR_COMPRESSION_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CONNECT_ERROR", SW_HTTP2_ERROR_CONNECT_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_ENHANCE_YOUR_CALM", SW_HTTP2_ERROR_ENHANCE_YOUR_CALM); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INADEQUATE_SECURITY", SW_HTTP2_ERROR_INADEQUATE_SECURITY); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_HTTP_1_1_REQUIRED", SW_HTTP2_ERROR_HTTP_1_1_REQUIRED); } void Client::socket_dtor() { socket_ = nullptr; clean_send_queue(); auto i = streams.begin(); while (i != streams.end()) { destroy_stream(i->second); streams.erase(i++); } if (inflater) { nghttp2_hd_inflate_del(inflater); inflater = nullptr; } if (deflater) { nghttp2_hd_deflate_del(deflater); deflater = nullptr; } zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); zend_update_property_null(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("socket")); zval_ptr_dtor(&zsocket); ZVAL_NULL(&zsocket); } bool Client::connect() { if (sw_unlikely(socket_ != nullptr)) { update_error_properties(EISCONN, strerror(EISCONN)); return false; } auto object = php_swoole_create_socket(network::Socket::convert_to_type(host)); if (UNEXPECTED(!object)) { php_swoole_socket_set_error_properties(zobject, errno, strerror(errno)); return false; } ZVAL_OBJ(&zsocket, object); socket_ = php_swoole_get_socket(&zsocket); socket_->set_dtor([this](Socket *_socket) { socket_dtor(); }); socket_->set_zero_copy(true); if (open_ssl && !socket_->enable_ssl_encrypt()) { io_error(); close(); return false; } socket_->http2 = true; socket_->open_length_check = true; socket_->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; socket_->protocol.package_length_offset = 0; socket_->protocol.package_body_offset = 0; socket_->protocol.get_package_length = Http2::get_frame_length; apply_setting( sw_zend_read_property_ex(swoole_http2_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0)); if (!socket_->connect(host, port)) { io_error(); close(); return false; } stream_id = 1; // [init]: we must set default value, server is not always send all the settings Http2::init_settings(&remote_settings); remote_window_size = remote_settings.init_window_size; int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem()); if (ret != 0) { nghttp2_error(ret, "nghttp2_hd_inflate_new2() failed"); close(); return false; } ret = nghttp2_hd_deflate_new2(&deflater, local_settings.header_table_size, php_nghttp2_mem()); if (ret != 0) { nghttp2_error(ret, "nghttp2_hd_deflate_new2() failed"); close(); return false; } if (!send(ZEND_STRL(SW_HTTP2_PRI_STRING))) { close(); return false; } if (!send_setting()) { close(); return false; } zend_update_property(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &zsocket); zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1); return true; } bool Client::close() const { /* * The socket_ pointer MUST be staged, * when client close the member variable may be set to nullptr in socket dtor */ SocketImpl *_socket = socket_; if (_socket == nullptr) { update_error_properties(EBADF, strerror(EBADF)); return false; } zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); if (!_socket->close()) { update_error_properties(_socket->errCode, _socket->errMsg); return false; } return true; } ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { char *buf = socket_->get_read_buffer()->str; uint8_t type = buf[3]; uint8_t flags = buf[4]; uint32_t stream_error = 0; uint32_t _stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; ssize_t length = Http2::get_length(buf); buf += SW_HTTP2_FRAME_HEADER_SIZE; char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; if (_stream_id > last_stream_id) { last_stream_id = _stream_id; } switch (type) { case SW_HTTP2_TYPE_SETTINGS: { if (flags & SW_HTTP2_FLAG_ACK) { swoole_http2_frame_trace_log("ACK"); return SW_CONTINUE; } auto rc = Http2::unpack_setting_data(buf, length, [&](uint16_t id, uint32_t value) -> ReturnCode { swoole_http2_frame_trace_log("id=%d, value=%d", id, value); switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: if (value != remote_settings.header_table_size) { remote_settings.header_table_size = value; int ret = nghttp2_hd_deflate_change_table_size(deflater, value); if (ret != 0) { nghttp2_error(ret, "nghttp2_hd_deflate_change_table_size() failed"); return SW_ERROR; } } swoole_trace_log(SW_TRACE_HTTP2, "setting: header_compression_table_max=%u", value); break; case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: remote_settings.max_concurrent_streams = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value); break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: remote_window_size = remote_settings.init_window_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: init_send_window=%u", value); break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: remote_settings.max_frame_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: max_frame_size=%u", value); break; case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: if (value != remote_settings.max_header_list_size) { remote_settings.max_header_list_size = value; #if 0 int ret = nghttp2_hd_inflate_change_table_size(inflater, value); if (ret != 0) { nghttp2_error(ret, "nghttp2_hd_inflate_change_table_size() failed"); return SW_ERROR; } #endif } swoole_trace_log(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value); break; default: // disable warning and ignore it because some websites are not following http2 protocol totally // swoole_warning("unknown option[%d]: %d", id, value); break; } return SW_SUCCESS; }); if (rc != SW_SUCCESS) { return rc; } Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, _stream_id); if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE)) { return SW_ERROR; } return SW_CONTINUE; } case SW_HTTP2_TYPE_WINDOW_UPDATE: { uint32_t value = ntohl(*(uint32_t *) buf); swoole_trace_log( SW_TRACE_HTTP2, "[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, value); if (_stream_id == 0) { remote_window_size += value; } else { Stream *stream = get_stream(_stream_id); if (stream) { stream->remote_window_size += value; } } return SW_CONTINUE; } case SW_HTTP2_TYPE_PING: { swoole_http2_frame_trace_log("ping"); if (!(flags & SW_HTTP2_FLAG_ACK)) { Http2::set_frame_header( frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, _stream_id); memcpy( frame + SW_HTTP2_FRAME_HEADER_SIZE, buf + SW_HTTP2_FRAME_HEADER_SIZE, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE)) { return SW_ERROR; } } return SW_CONTINUE; } case SW_HTTP2_TYPE_GOAWAY: { uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf)); buf += 4; uint32_t value = ntohl(*(uint32_t *) (buf)); buf += 4; swoole_http2_frame_trace_log("last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", server_last_stream_id, value, (int) (length - SW_HTTP2_GOAWAY_SIZE), buf); // update goaway error code and error msg zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), value); zend_update_property_stringl( swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), buf, length - SW_HTTP2_GOAWAY_SIZE); zend_update_property_long( swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("serverLastStreamId"), server_last_stream_id); close(); return SW_CLOSE; } case SW_HTTP2_TYPE_RST_STREAM: { stream_error = ntohl(*(uint32_t *) (buf)); swoole_http2_frame_trace_log("error_code=%d", stream_error); // delete and free quietly delete_stream(_stream_id); return SW_CONTINUE; } /** * TODO not support push_promise */ case SW_HTTP2_TYPE_PUSH_PROMISE: { #ifdef SW_DEBUG uint32_t promise_stream_id = ntohl(*(uint32_t *) (buf)) & 0x7fffffff; swoole_http2_frame_trace_log("promise_stream_id=%d", promise_stream_id); #endif // auto promise_stream = create_stream(promise_stream_id, false); // RETVAL_ZVAL(promise_stream->response_object, 0, 0); // return SW_READY; return SW_CONTINUE; } default: { swoole_http2_frame_trace_log(""); } } Stream *stream = get_stream(_stream_id); // The stream is not found or has closed if (stream == nullptr) { swoole_notice("http2 stream#%d belongs to an unknown type or it never registered", _stream_id); return SW_CONTINUE; } if (type == SW_HTTP2_TYPE_HEADERS) { parse_header(stream, flags, buf, length); } else if (type == SW_HTTP2_TYPE_DATA) { if (!(flags & SW_HTTP2_FLAG_END_STREAM)) { stream->flags |= SW_HTTP2_STREAM_PIPELINE_RESPONSE; } if (length > 0) { if (!stream->buffer) { stream->buffer = make_string(SW_HTTP2_DATA_BUFFER_SIZE); } #ifdef SW_HAVE_ZLIB if (stream->gzip) { if (php_swoole_zlib_decompress(&stream->gzip_stream, stream->gzip_buffer, buf, length) == SW_ERR) { swoole_warning("decompress failed"); return SW_ERROR; } stream->buffer->append(stream->gzip_buffer->str, stream->gzip_buffer->length); } else #endif { stream->buffer->append(buf, length); } // now we control the connection flow only (not stream) // our window size is unlimited, so we don't worry about subtraction overflow local_window_size -= length; stream->local_window_size -= length; if (local_window_size < (local_settings.init_window_size / 4)) { if (!send_window_update(0, local_settings.init_window_size - local_window_size)) { return SW_ERROR; } local_window_size = local_settings.init_window_size; } if (stream->local_window_size < (local_settings.init_window_size / 4)) { if (!send_window_update(_stream_id, local_settings.init_window_size - stream->local_window_size)) { return SW_ERROR; } stream->local_window_size = local_settings.init_window_size; } } } bool end = (flags & SW_HTTP2_FLAG_END_STREAM) || type == SW_HTTP2_TYPE_RST_STREAM || type == SW_HTTP2_TYPE_GOAWAY; pipeline_read = ((pipeline_read || (stream->flags & SW_HTTP2_STREAM_USE_PIPELINE_READ)) && (stream->flags & SW_HTTP2_STREAM_PIPELINE_RESPONSE)); if (end || pipeline_read) { zval *zresponse = &stream->zresponse; if (type == SW_HTTP2_TYPE_RST_STREAM) { zend::object_set(zresponse, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); zend::object_set(zresponse, ZEND_STRL("errCode"), stream_error); } if (stream->buffer && stream->buffer->length > 0) { zend_update_property_stringl(swoole_http2_response_ce, SW_Z8_OBJ_P(zresponse), ZEND_STRL("data"), stream->buffer->str, stream->buffer->length); stream->buffer->clear(); } if (!end) { zend_update_property_bool( swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("pipeline"), 1); } RETVAL_ZVAL(zresponse, end, 0); if (!end) { // reinit response object for the following frames object_init_ex(zresponse, swoole_http2_response_ce); zend_update_property_long( swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), _stream_id); } else { delete_stream(_stream_id); } return SW_READY; } return SW_CONTINUE; } #ifdef SW_HAVE_ZLIB int php_swoole_zlib_decompress(z_stream *stream, String *buffer, char *body, int length) { int status = 0; stream->avail_in = length; stream->next_in = (Bytef *) body; stream->total_in = 0; stream->total_out = 0; swoole_trace_log(SW_TRACE_ZLIB, SW_START_LINE "\nstatus=%d\tavail_in=%u,\tavail_out=%u,\ttotal_in=%lu,\ttotal_out=%lu\n", status, stream->avail_in, stream->avail_out, stream->total_in, stream->total_out); buffer->clear(); while (true) { stream->avail_out = buffer->size - buffer->length; stream->next_out = (Bytef *) (buffer->str + buffer->length); status = inflate(stream, Z_SYNC_FLUSH); swoole_trace_log(SW_TRACE_ZLIB, "status=%d\tavail_in=%d,\tavail_out=%d,\ttotal_in=%lu,\ttotal_out=%lu,\tlength=%lu\n", status, stream->avail_in, stream->avail_out, stream->total_in, stream->total_out, buffer->length); if (status >= 0) { buffer->length = stream->total_out; } if (status == Z_STREAM_END) { return SW_OK; } else if (status == Z_OK) { if (buffer->length + 4096 >= buffer->size) { buffer->extend(); } if (stream->avail_in == 0) { return SW_OK; } } else { return SW_ERR; } } return SW_ERR; } #endif static PHP_METHOD(swoole_http2_client_coro, __construct) { char *host; size_t host_len; zend_long port = 80; zend_bool ssl = false; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 3) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_BOOL(ssl) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (host_len == 0) { zend_throw_exception(swoole_http2_client_coro_exception_ce, "host is empty", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } auto *client = new Client(host, host_len, port, ssl, ZEND_THIS); http2_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS))->client = client; zend_update_property_stringl( swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, host_len); zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), port); zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("ssl"), ssl); } static PHP_METHOD(swoole_http2_client_coro, set) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval *zsetting = sw_zend_read_and_convert_property_array(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); h2c->apply_setting(zset); RETURN_TRUE; } /** * called in read channel */ bool Client::send_window_update(int _stream_id, uint32_t size) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE]; swoole_http2_send_trace_log("[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, size); *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size); Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, _stream_id); return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE); } /** * called on connect */ bool Client::send_setting() { char frame[SW_HTTP2_SETTING_FRAME_SIZE]; size_t n = Http2::pack_setting_frame(frame, local_settings, false); swoole_http2_send_trace_log("[" SW_ECHO_MAGENTA "] ", Http2::get_type(SW_HTTP2_TYPE_SETTINGS), local_settings.header_table_size, local_settings.enable_push, local_settings.max_concurrent_streams, local_settings.init_window_size, local_settings.max_frame_size, local_settings.max_header_list_size); return send(frame, n); } void php_swoole_http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers); int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) const { zval *zresponse = &stream->zresponse; if (flags & SW_HTTP2_FLAG_PRIORITY) { // int stream_deps = ntohl(*(int *) (in)); // uint8_t weight = in[4]; in += 5; inlen -= 5; } zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("headers"), 0); zval *zcookies = sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("cookies"), 0); zval *zset_cookie_headers = sw_zend_read_and_convert_property_array( swoole_http2_response_ce, zresponse, ZEND_STRL("set_cookie_headers"), 0); int inflate_flags = 0; do { nghttp2_nv nv; ssize_t rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); if (rv < 0) { nghttp2_error(rv, "nghttp2_hd_inflate_hd failed"); return SW_ERR; } in += (size_t) rv; inlen -= (size_t) rv; swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "] %.*s[%lu]: %.*s[%lu]", "HEADER", (int) nv.namelen, nv.name, nv.namelen, (int) nv.valuelen, nv.value, nv.valuelen); if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { if (nv.name[0] == ':') { if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "status")) { zend::object_set(zresponse, ZEND_STRL("statusCode"), sw_atol((char *) nv.value)); } } else { #ifdef SW_HAVE_ZLIB if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-encoding") && SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "gzip")) { /** * init zlib stream */ stream->gzip = 1; memset(&stream->gzip_stream, 0, sizeof(stream->gzip_stream)); stream->gzip_buffer = make_string(8192); stream->gzip_stream.zalloc = php_zlib_alloc; stream->gzip_stream.zfree = php_zlib_free; /** * zlib decode */ if (Z_OK != inflateInit2(&stream->gzip_stream, MAX_WBITS + 16)) { swoole_warning("inflateInit2() failed"); return SW_ERR; } } else #endif if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "set-cookie")) { php_swoole_http_parse_set_cookies((char *) nv.value, nv.valuelen, zcookies, zset_cookie_headers); } add_assoc_stringl_ex(zheaders, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen); } } } while ([=] { if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { nghttp2_hd_inflate_end_headers(inflater); return false; } return inlen != 0; }()); return SW_OK; } ssize_t Client::build_header(const zval *zobj, zval *zrequest, char *buffer) { Client *h2c = http2_client_coro_get_client(zobj); zval *zmethod = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_METHOD), 0); zval *zpath = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PATH), 0); zval *zheaders = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADERS), 0); zval *zcookies = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIES), 0); HeaderSet headers(8 + php_swoole_array_length_safe(zheaders) + php_swoole_array_length_safe(zcookies)); bool find_host = false; if (Z_TYPE_P(zmethod) != IS_STRING || Z_STRLEN_P(zmethod) == 0) { headers.add(ZEND_STRL(":method"), ZEND_STRL("GET")); } else { headers.add(ZEND_STRL(":method"), Z_STRVAL_P(zmethod), Z_STRLEN_P(zmethod)); } if (Z_TYPE_P(zpath) != IS_STRING || Z_STRLEN_P(zpath) == 0) { headers.add(ZEND_STRL(":path"), "/", 1); } else { headers.add(ZEND_STRL(":path"), Z_STRVAL_P(zpath), Z_STRLEN_P(zpath)); } if (h2c->open_ssl) { headers.add(ZEND_STRL(":scheme"), ZEND_STRL("https")); } else { headers.add(ZEND_STRL(":scheme"), ZEND_STRL("http")); } // Host headers.reserve_one(); if (ZVAL_IS_ARRAY(zheaders)) { zend_string *key; zval *zvalue; ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), key, zvalue) { if (UNEXPECTED(!key || *ZSTR_VAL(key) == ':' || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); if (SW_STRCASEEQ(ZSTR_VAL(key), ZSTR_LEN(key), "host")) { headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), str_value.val(), str_value.len()); find_host = true; } else { headers.add(ZSTR_VAL(key), ZSTR_LEN(key), str_value.val(), str_value.len()); } } ZEND_HASH_FOREACH_END(); } if (!find_host) { const std::string *host_ptr; std::string _host; if (!h2c->open_ssl ? h2c->port != 80 : h2c->port != 443) { _host = std_string::format("%s:%d", h2c->host.c_str(), h2c->port); host_ptr = &_host; } else { host_ptr = &h2c->host; } headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), host_ptr->c_str(), host_ptr->length()); } // http cookies if (ZVAL_IS_ARRAY(zcookies)) { zend_string *key; zval *zvalue; size_t encoded_value_len; String *header_buffer = sw_tg_buffer(); ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zcookies), key, zvalue) { if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); header_buffer->clear(); header_buffer->append(ZSTR_VAL(key), ZSTR_LEN(key)); header_buffer->append("=", 1); char *encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len); if (encoded_value) { header_buffer->append(encoded_value, encoded_value_len); efree(encoded_value); headers.add(ZEND_STRL("cookie"), header_buffer->str, header_buffer->length); } } ZEND_HASH_FOREACH_END(); } size_t buflen = nghttp2_hd_deflate_bound(h2c->deflater, headers.get(), headers.len()); #if 0 if (buflen > h2c->remote_settings.max_header_list_size) { php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", client->remote_settings.max_header_list_size); return -1; } #endif ssize_t rv = nghttp2_hd_deflate_hd(h2c->deflater, (uchar *) buffer, buflen, headers.get(), headers.len()); if (rv < 0) { h2c->nghttp2_error(rv, "nghttp2_hd_deflate_hd() failed"); return -1; } return rv; } void Client::destroy_stream(Stream *stream) { delete (stream->buffer); #ifdef SW_HAVE_ZLIB if (stream->gzip) { inflateEnd(&stream->gzip_stream); } delete (stream->gzip_buffer); #endif zval_ptr_dtor(&stream->zresponse); efree(stream); } Stream *Client::create_stream(uint32_t _stream_id, uint8_t flags) { // malloc auto *stream = static_cast(ecalloc(1, sizeof(Stream))); // init stream->stream_id = _stream_id; stream->flags = flags; stream->remote_window_size = remote_settings.init_window_size; stream->local_window_size = local_settings.init_window_size; streams.emplace(_stream_id, stream); // create response object object_init_ex(&stream->zresponse, swoole_http2_response_ce); zend_update_property_long( swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), _stream_id); return stream; } /** * called in write channel */ bool Client::send_ping_frame() { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0); swoole_http2_send_trace_log("[" SW_ECHO_CYAN "]", "PING"); return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); } bool Client::send_data(uint32_t _stream_id, const char *p, size_t len, int flag) { uint8_t send_flag; uint32_t send_len; char header[SW_HTTP2_FRAME_HEADER_SIZE]; while (len > 0) { if (len > local_settings.max_frame_size) { send_len = local_settings.max_frame_size; send_flag = 0; } else { send_len = len; send_flag = flag; } Http2::set_frame_header(header, SW_HTTP2_TYPE_DATA, send_len, send_flag, _stream_id); if (!send(header, SW_HTTP2_FRAME_HEADER_SIZE)) { return false; } if (!send(p, send_len)) { return false; } len -= send_len; p += send_len; } return true; } /** * called in write channel */ uint32_t Client::send_request(zval *zrequest) { zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http2_request_ce, zrequest, ZEND_STRL("headers"), 0); zval *zdata = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 0); zval *zpipeline = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PIPELINE), 0); zval ztmp, *zuse_pipeline_read = zend_read_property_ex( Z_OBJCE_P(zrequest), SW_Z8_OBJ_P(zrequest), SW_ZSTR_KNOWN(SW_ZEND_STR_USE_PIPELINE_READ), true, &ztmp); bool is_data_empty = Z_TYPE_P(zdata) == IS_STRING ? Z_STRLEN_P(zdata) == 0 : !zval_is_true(zdata); if (ZVAL_IS_ARRAY(zdata)) { add_assoc_stringl_ex( zheaders, ZEND_STRL("content-type"), (char *) ZEND_STRL("application/x-www-form-urlencoded")); } /** * send headers */ char *buffer = sw_tg_buffer()->str; ssize_t bytes = build_header(zobject, zrequest, buffer + SW_HTTP2_FRAME_HEADER_SIZE); if (bytes <= 0) { return 0; } uint8_t flags = 0; if (zval_is_true(zpipeline)) { flags |= SW_HTTP2_STREAM_PIPELINE_REQUEST; } if (zval_is_true(zuse_pipeline_read)) { flags |= SW_HTTP2_STREAM_USE_PIPELINE_READ; } auto stream = create_stream(stream_id, flags); flags = SW_HTTP2_FLAG_END_HEADERS; if (is_data_empty && !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST)) { flags |= SW_HTTP2_FLAG_END_STREAM; } Http2::set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, flags, stream->stream_id); swoole_http2_send_trace_log( "[" SW_ECHO_GREEN ", STREAM#%d] length=%zd", Http2::get_type(SW_HTTP2_TYPE_HEADERS), stream->stream_id, bytes); if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE + bytes)) { return 0; } /** * send body */ if (!is_data_empty) { char *p; size_t len; smart_str formstr_s = {}; zend::String str_zpost_data; int flag = (stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) ? 0 : SW_HTTP2_FLAG_END_STREAM; if (ZVAL_IS_ARRAY(zdata)) { p = php_swoole_http_build_query(zdata, &len, &formstr_s); if (p == nullptr) { php_swoole_error(E_WARNING, "http_build_query failed"); return 0; } } else { str_zpost_data = zdata; p = str_zpost_data.val(); len = str_zpost_data.len(); } swoole_http2_send_trace_log("[" SW_ECHO_GREEN ", END, STREAM#%d] length=%zu", Http2::get_type(SW_HTTP2_TYPE_DATA), stream->stream_id, len); if (!send_data(stream->stream_id, p, len, flag)) { return 0; } if (formstr_s.s) { smart_str_free(&formstr_s); } } stream_id += 2; return stream->stream_id; } /** * called in write channel */ bool Client::write_data(uint32_t _stream_id, zval *zdata, bool end) { char buffer[SW_HTTP2_FRAME_HEADER_SIZE]; Stream *stream = get_stream(_stream_id); int flag = end ? SW_HTTP2_FLAG_END_STREAM : 0; if (stream == nullptr || !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) || (stream->flags & SW_HTTP2_STREAM_REQUEST_END)) { update_error_properties(EINVAL, std_string::format("unable to found active pipeline stream#%u", _stream_id).c_str()); return false; } if (ZVAL_IS_ARRAY(zdata)) { size_t len; smart_str formstr_s = {}; char *formstr = php_swoole_http_build_query(zdata, &len, &formstr_s); if (formstr == nullptr) { php_swoole_error(E_WARNING, "http_build_query failed"); return false; } Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, len, flag, _stream_id); swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", Http2::get_type(SW_HTTP2_TYPE_DATA), end ? " END," : "", stream_id, len); if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(formstr, len)) { smart_str_free(&formstr_s); return false; } smart_str_free(&formstr_s); } else { zend::String data(zdata); Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, data.len(), flag, _stream_id); swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", Http2::get_type(SW_HTTP2_TYPE_DATA), end ? " END," : "", stream_id, data.len()); if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(data.val(), data.len())) { return false; } } if (end) { stream->flags |= SW_HTTP2_STREAM_REQUEST_END; } return true; } /** * called in write channel */ bool Client::send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len) { size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len; auto frame = (char *) ecalloc(1, length); Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code); if (debug_data_len > 0) { memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len); } swoole_http2_send_trace_log("[" SW_ECHO_RED "] last-sid=%u, error-code=" ZEND_LONG_FMT, Http2::get_type(SW_HTTP2_TYPE_GOAWAY), last_stream_id, error_code); bool ret = send(frame, length); efree(frame); return ret; } static PHP_METHOD(swoole_http2_client_coro, send) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; } zval *zrequest; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT_OF_CLASS(zrequest, swoole_http2_request_ce) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); uint32_t stream_id = h2c->send_request(zrequest); if (stream_id == 0) { RETURN_FALSE; } else { RETURN_LONG(stream_id); } } static void http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool pipeline_read) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CLIENT_PRESERVE_SOCKET(ZEND_THIS); while (true) { if (!h2c->is_available()) { RETURN_FALSE; } if (!h2c->recv_packet(timeout)) { RETURN_FALSE; } ReturnCode ret = h2c->parse_frame(return_value, pipeline_read); if (ret == SW_CONTINUE) { continue; } else if (ret == SW_READY) { break; } else { RETURN_FALSE; } } } static PHP_METHOD(swoole_http2_client_coro, recv) { http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_http2_client_coro, __destruct) {} static PHP_METHOD(swoole_http2_client_coro, close) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); RETURN_BOOL(h2c->close()); } static PHP_METHOD(swoole_http2_client_coro, connect) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); RETURN_BOOL(h2c->connect()); } static sw_inline void http2_client_settings_to_array(const Http2::Settings *settings, zval *zarray) { array_init(zarray); add_assoc_long_ex(zarray, ZEND_STRL("header_table_size"), settings->header_table_size); add_assoc_long_ex(zarray, ZEND_STRL("init_window_size"), settings->init_window_size); add_assoc_long_ex(zarray, ZEND_STRL("max_concurrent_streams"), settings->max_concurrent_streams); add_assoc_long_ex(zarray, ZEND_STRL("max_frame_size"), settings->max_frame_size); add_assoc_long_ex(zarray, ZEND_STRL("max_header_list_size"), settings->max_header_list_size); } static PHP_METHOD(swoole_http2_client_coro, stats) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); zval _zarray, *zarray = &_zarray; String key = {}; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_STRING(key.str, key.length); ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (key.length > 0) { if (SW_STREQ(key.str, key.length, "current_stream_id")) { RETURN_LONG(h2c->stream_id); } else if (SW_STREQ(key.str, key.length, "last_stream_id")) { RETURN_LONG(h2c->last_stream_id); } else if (SW_STREQ(key.str, key.length, "local_settings")) { http2_client_settings_to_array(&h2c->local_settings, zarray); RETURN_ZVAL(zarray, 0, 0); } else if (SW_STREQ(key.str, key.length, "remote_settings")) { http2_client_settings_to_array(&h2c->remote_settings, zarray); RETURN_ZVAL(zarray, 0, 0); } else if (SW_STREQ(key.str, key.length, "active_stream_num")) { RETURN_LONG(h2c->streams.size()); } } else { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("current_stream_id"), h2c->stream_id); add_assoc_long_ex(return_value, ZEND_STRL("last_stream_id"), h2c->last_stream_id); http2_client_settings_to_array(&h2c->local_settings, zarray); add_assoc_zval_ex(return_value, ZEND_STRL("local_settings"), zarray); http2_client_settings_to_array(&h2c->remote_settings, zarray); add_assoc_zval_ex(return_value, ZEND_STRL("remote_settings"), zarray); add_assoc_long_ex(return_value, ZEND_STRL("active_stream_num"), h2c->streams.size()); } } static PHP_METHOD(swoole_http2_client_coro, isStreamExist) { zend_long stream_id = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(stream_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (stream_id < 0) { RETURN_FALSE; } Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->socket_) { RETURN_FALSE; } else if (stream_id == 0) { RETURN_TRUE; } Stream *stream = h2c->get_stream(stream_id); RETURN_BOOL(stream ? 1 : 0); } static PHP_METHOD(swoole_http2_client_coro, write) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; } zend_long stream_id; zval *data; zend_bool end = false; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_LONG(stream_id); Z_PARAM_ZVAL(data); Z_PARAM_OPTIONAL Z_PARAM_BOOL(end); ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); RETURN_BOOL(h2c->write_data(stream_id, data, end)); } static PHP_METHOD(swoole_http2_client_coro, read) { http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_http2_client_coro, ping) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; } RETURN_BOOL(h2c->send_ping_frame()); } /** * +-+-------------------------------------------------------------+ * |R| Last-Stream-ID (31) | * +-+-------------------------------------------------------------+ * | Error Code (32) | * +---------------------------------------------------------------+ * | Additional Debug Data (*) | * +---------------------------------------------------------------+ */ static PHP_METHOD(swoole_http2_client_coro, goaway) { Client *h2c = http2_client_coro_get_client(ZEND_THIS); zend_long error_code = SW_HTTP2_ERROR_NO_ERROR; char *debug_data = nullptr; size_t debug_data_len = 0; if (!h2c->is_available()) { RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(error_code); Z_PARAM_STRING(debug_data, debug_data_len); ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); RETURN_BOOL(h2c->send_goaway_frame(error_code, debug_data, debug_data_len)); } ================================================ FILE: ext-src/swoole_http2_server.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" #include #include #include "swoole_static_handler.h" #include "main/php_variables.h" using namespace swoole; using std::string; using swoole::coroutine::System; using swoole::http2::get_default_setting; using swoole::http_server::StaticHandler; namespace Http2 = swoole::http2; using HttpContext = swoole::http::Context; using Http2Stream = Http2::Stream; using Http2Session = Http2::Session; static SW_THREAD_LOCAL std::unordered_map> http2_sessions; static SW_THREAD_LOCAL std::unordered_map server_ips; static SW_THREAD_LOCAL std::unordered_map client_ips; static bool http2_server_respond(HttpContext *ctx, const String *body); static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler); static bool http2_server_send_status_code(HttpContext *ctx, int status_code); Http2Stream::Stream(const Http2Session *client, uint32_t _id) { ctx = swoole_http_context_new(client->fd); ctx->copy(client->default_ctx); ctx->http2 = true; ctx->stream_id = _id; ctx->keepalive = true; id = _id; local_window_size = client->local_settings.init_window_size; remote_window_size = client->remote_settings.init_window_size; } Http2Stream::~Stream() { ctx->stream_id = 0; ctx->end_ = true; ctx->free(); } void Http2Stream::reset(uint32_t error_code) const { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE]; swoole_trace_log( SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, error_code=%u", "RST_STREAM", id, error_code); *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code); set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, SW_HTTP2_RST_STREAM_SIZE, 0, id); ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE); } Http2Session::Session(SessionId _fd) { fd = _fd; init_settings(&local_settings); // [init]: we must set default value, peer is not always send all the settings init_settings(&remote_settings); local_window_size = local_settings.init_window_size; remote_window_size = remote_settings.init_window_size; last_stream_id = 0; shutting_down = false; is_coro = false; max_body_size = 0; } std::shared_ptr Http2Session::get_stream(uint32_t stream_id) { auto iter = streams.find(stream_id); if (iter == streams.end()) { return {}; } else { return iter->second; } } bool Http2Session::remove_stream(uint32_t stream_id) { auto iter = streams.find(stream_id); if (iter == streams.end()) { return false; } auto stream = iter->second; streams.erase(iter); return true; } std::shared_ptr Http2Session::create_stream(uint32_t stream_id) { auto stream = std::make_shared(this, stream_id); streams.emplace(stream_id, stream); if (sw_unlikely(!stream->ctx)) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 create stream#%d context error", stream_id); return {}; } auto ctx = stream->ctx; zend::object_set(ctx->request.zobject, ZEND_STRL("streamId"), stream_id); return stream; } Http2Session::~Session() { if (inflater) { nghttp2_hd_inflate_del(inflater); } if (deflater) { nghttp2_hd_deflate_del(deflater); } delete default_ctx; } static void http2_server_send_window_update(HttpContext *ctx, uint32_t stream_id, uint32_t size) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE]; swoole_trace_log( SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, size=%u", "WINDOW_UPDATE", stream_id, size); *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size); Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, stream_id); ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE); } static ssize_t http2_server_build_trailer(const HttpContext *ctx, uchar *buffer) { zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); uint32_t size = php_swoole_array_length_safe(ztrailer); if (size > 0) { Http2::HeaderSet trailer(size); zend_string *key; zval *zvalue; ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(ztrailer), key, zvalue) { if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); trailer.add(ZSTR_VAL(key), ZSTR_LEN(key), str_value.val(), str_value.len()); } ZEND_HASH_FOREACH_END(); ssize_t rv; auto client = http2_sessions[ctx->fd]; auto deflater = client->deflater; if (!deflater) { int ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem()); if (ret != 0) { swoole_warning("nghttp2_hd_deflate_new2() failed with error: %s", nghttp2_strerror(ret)); return -1; } client->deflater = deflater; } size_t buflen = nghttp2_hd_deflate_bound(deflater, trailer.get(), trailer.len()); #if 0 if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE) { php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); return -1; } #endif rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, trailer.get(), trailer.len()); if (rv < 0) { swoole_warning("nghttp2_hd_deflate_hd() failed with error: %s", nghttp2_strerror((int) rv)); return -1; } return rv; } return 0; } static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) { zval *zserver = ctx->request.zserver; zval *zrequest_uri = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_uri")); if (zrequest_uri && Z_TYPE_P(zrequest_uri) == IS_STRING) { StaticHandler handler(serv, Z_STRVAL_P(zrequest_uri), Z_STRLEN_P(zrequest_uri)); if (!handler.try_serve()) { return false; } if (handler.status_code == SW_HTTP_NOT_FOUND) { String body(SW_STRL(SW_HTTP_PAGE_404)); ctx->response.status = SW_HTTP_NOT_FOUND; http2_server_respond(ctx, &body); return true; } /** * if http_index_files is enabled, need to search the index file first. * if the index file is found, set filename to index filename. */ if (!handler.try_serve_index_file()) { return false; } /** * the index file was not found in the current directory, * if http_autoindex is enabled, should show the list of files in the current directory. */ if (!handler.has_index_file() && handler.is_enabled_auto_index() && handler.is_dir()) { String body(PATH_MAX); body.length = handler.make_index_page(&body); http2_server_respond(ctx, &body); return true; } auto date_str = handler.get_date(); auto date_str_last_modified = handler.get_date_last_modified(); zval *zheader = ctx->request.zheader; ctx->set_header(ZEND_STRL("Last-Modified"), date_str_last_modified, false); zval *zdate_if_modified_since = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-modified-since")); if (zdate_if_modified_since) { string date_if_modified_since(Z_STRVAL_P(zdate_if_modified_since), Z_STRLEN_P(zdate_if_modified_since)); if (!date_if_modified_since.empty() && handler.is_modified(date_if_modified_since)) { ctx->response.status = SW_HTTP_NOT_MODIFIED; return true; } } zval *zrange = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("range")); zval *zif_range = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-range")); handler.parse_range(zrange ? Z_STRVAL_P(zrange) : nullptr, zif_range ? Z_STRVAL_P(zif_range) : nullptr); ctx->response.status = handler.status_code; auto tasks = handler.get_tasks(); if (1 == tasks.size()) { if (SW_HTTP_PARTIAL_CONTENT == handler.status_code) { std::stringstream content_range; content_range << "bytes " << tasks[0].offset << "-" << (tasks[0].length + tasks[0].offset - 1) << "/" << handler.get_filesize() << "\r\n"; auto content_range_str = content_range.str(); ctx->set_header(ZEND_STRL("Content-Range"), content_range_str, false); } else { ctx->set_header(ZEND_STRL("Accept-Ranges"), SW_STRL("bytes"), false); } } ctx->onAfterResponse = nullptr; ctx->onBeforeRequest = nullptr; // request_method zval *zrequest_method = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_method")); if (zrequest_method && Z_TYPE_P(zrequest_method) == IS_STRING && SW_STRCASEEQ(Z_STRVAL_P(zrequest_method), Z_STRLEN_P(zrequest_method), "HEAD")) { String empty_body; http2_server_respond(ctx, &empty_body); return true; } else { return http2_server_send_range_file(ctx, &handler); } } return false; } static void http2_server_onRequest(const std::shared_ptr &client, const std::shared_ptr &stream) { HttpContext *ctx = stream->ctx; auto serv = ctx->get_async_server(); zval args[2]; zend::Callable *cb = nullptr; Connection *conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { goto _destroy; } ctx->request.version = SW_HTTP_VERSION_2; if (serv->enable_static_handler && http2_server_is_static_file(serv, ctx)) { goto _destroy; } do { zval *zserver = ctx->request.zserver; HashTable *ht = Z_ARR_P(zserver); swoole_http_server_populate_ip_and_port(serv, ht, conn, client->fd, ctx->keepalive); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_MASTER_TIME), (zend_long) conn->last_recv_time); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP2)); } while (false); cb = php_swoole_server_get_callback(serv, conn->server_fd, SW_SERVER_CB_onRequest); ctx->private_data_2 = cb; if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) { return; } args[0] = *ctx->request.zobject; args[1] = *ctx->response.zobject; if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { stream->reset(SW_HTTP2_ERROR_INTERNAL_ERROR); php_swoole_error(E_WARNING, "%s->onRequest[v2] handler error", ZSTR_VAL(swoole_http_server_ce->name)); } _destroy: zval_ptr_dtor(ctx->request.zobject); zval_ptr_dtor(ctx->response.zobject); } static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const String *body) { zval *zheader = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); zval *zcookie = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); Http2::HeaderSet headers(32 + php_swoole_array_length_safe(zheader) + php_swoole_array_length_safe(zcookie)); char intbuf[2][16]; assert(ctx->send_header_ == 0); // status code if (ctx->response.status == 0) { ctx->response.status = SW_HTTP_OK; } int ret = swoole_itoa(intbuf[0], ctx->response.status); headers.add(ZEND_STRL(":status"), intbuf[0], ret); uint32_t header_flags = 0x0; // headers if (ZVAL_IS_ARRAY(zheader)) { const char *key; uint32_t keylen; zval *zvalue; int type; zend_string *content_type = nullptr; auto add_header = [ctx, &content_type]( Http2::HeaderSet &headers, const char *key, size_t l_key, zval *value, uint32_t &header_flags) { if (ZVAL_IS_NULL(value)) { return; } zend::String str_value(value); str_value.rtrim(); if (swoole_http_has_crlf(str_value.val(), str_value.len())) { return; } if (SW_STRCASEEQ(key, l_key, "server")) { header_flags |= HTTP_HEADER_SERVER; } else if (SW_STRCASEEQ(key, l_key, "content-length")) { return; // ignore } else if (SW_STRCASEEQ(key, l_key, "date")) { header_flags |= HTTP_HEADER_DATE; } else if (SW_STRCASEEQ(key, l_key, "content-type")) { header_flags |= HTTP_HEADER_CONTENT_TYPE; #ifdef SW_HAVE_COMPRESSION if (ctx->accept_compression && ctx->compression_types) { content_type = zval_get_string(value); } #endif } headers.add(key, l_key, str_value.val(), str_value.len()); }; SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheader), key, keylen, type, zvalue) { if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { continue; } if (ZVAL_IS_ARRAY(zvalue)) { zval *zvalue_2; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) { add_header(headers, key, keylen, zvalue_2, header_flags); } SW_HASHTABLE_FOREACH_END(); } else { add_header(headers, key, keylen, zvalue, header_flags); } } SW_HASHTABLE_FOREACH_END(); (void) type; #ifdef SW_HAVE_COMPRESSION if (ctx->accept_compression && ctx->compression_types) { std::string str_content_type = content_type ? std::string(ZSTR_VAL(content_type), ZSTR_LEN(content_type)) : std::string(ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); ctx->accept_compression = ctx->compression_types->find(str_content_type) != ctx->compression_types->end(); if (content_type) { zend_string_release(content_type); } } #endif } if (!(header_flags & HTTP_HEADER_SERVER)) { headers.add(ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); } if (!(header_flags & HTTP_HEADER_DATE)) { auto date_str = php_swoole_http_get_date(); headers.add(ZEND_STRL("date"), ZSTR_VAL(date_str), ZSTR_LEN(date_str)); } if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) { headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html")); } // cookies if (ZVAL_IS_ARRAY(zcookie)) { zval *zvalue; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zcookie), zvalue) { if (Z_TYPE_P(zvalue) != IS_STRING) { continue; } headers.add(ZEND_STRL("set-cookie"), Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue)); } SW_HASHTABLE_FOREACH_END(); } if (body) { size_t content_length = body->length; // content length #ifdef SW_HAVE_COMPRESSION if (ctx->compress(body->str, body->length)) { content_length = ctx->zlib_buffer->length; // content encoding const char *content_encoding = ctx->get_content_encoding(); headers.add(ZEND_STRL("content-encoding"), (char *) content_encoding, strlen(content_encoding)); } #endif ret = swoole_itoa(intbuf[1], content_length); headers.add(ZEND_STRL("content-length"), intbuf[1], ret); } auto client = http2_sessions[ctx->fd]; auto deflater = client->deflater; if (!deflater) { ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem()); if (ret != 0) { swoole_warning("nghttp2_hd_deflate_new2() failed with error: %s", nghttp2_strerror(ret)); return -1; } client->deflater = deflater; } size_t buflen = nghttp2_hd_deflate_bound(deflater, headers.get(), headers.len()); /* if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE) { php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); return -1; } */ ssize_t rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, headers.get(), headers.len()); if (rv < 0) { swoole_warning("nghttp2_hd_deflate_hd() failed with error: %s", nghttp2_strerror((int) rv)); return -1; } ctx->send_header_ = 1; return rv; } bool swoole_http2_server_ping(HttpContext *ctx) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0); return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); } static bool http2_server_send_setting_ack(HttpContext *ctx) { char frame[SW_HTTP2_FRAME_HEADER_SIZE]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, 0); return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE); } static bool http2_server_send_rst_stream(HttpContext *ctx, int error_code) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + sizeof(uint32_t)]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, 0, SW_HTTP2_FLAG_ACK, 0); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code); return ctx->send(ctx, frame, sizeof(frame)); } bool swoole_http2_server_goaway(HttpContext *ctx, zend_long error_code, zend_string *sdata) { const char *debug_data = sdata ? ZSTR_VAL(sdata) : nullptr; size_t debug_data_len = sdata ? ZSTR_LEN(sdata) : 0; size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len; char *frame = (char *) ecalloc(1, length); auto client = http2_sessions[ctx->fd]; uint32_t last_stream_id = client->last_stream_id; Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code); if (debug_data_len > 0) { memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len); } bool rv = ctx->send(ctx, frame, length); efree(frame); client->shutting_down = true; return rv; } bool Http2Stream::send_header(const String *body, bool end_stream) const { char header_buffer[SW_BUFFER_SIZE_STD]; ssize_t bytes = http2_server_build_header(ctx, (uchar *) header_buffer, body); if (bytes < 0) { return false; } String *http_buffer = ctx->get_write_buffer(); http_buffer->clear(); /** +---------------+ |Pad Length? (8)| +-+-------------+-----------------------------------------------+ |E| Stream Dependency? (31) | +-+-------------+-----------------------------------------------+ | Weight? (8) | +-+-------------+-----------------------------------------------+ | Header Block Fragment (*) ... +---------------------------------------------------------------+ | Padding (*) ... +---------------------------------------------------------------+ */ char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; if (end_stream && (!body || body->length == 0)) { http2::set_frame_header( frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, id); ctx->end_ = 1; } else { http2::set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS, id); } http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE); http_buffer->append(header_buffer, bytes); if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) { ctx->send_header_ = 0; return false; } return true; } bool Http2Stream::send_end_stream_data_frame() const { char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, 0, SW_HTTP2_FLAG_END_STREAM, id); return ctx->send(ctx, frame_header, SW_HTTP2_FRAME_HEADER_SIZE); } bool Http2Stream::send_body( const String *body, bool end_stream, const std::shared_ptr &session, off_t offset, size_t length) { char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; char *p = body->str + offset; size_t l = length == 0 ? body->length : length; int flags = end_stream ? SW_HTTP2_FLAG_END_STREAM : SW_HTTP2_FLAG_NONE; String *http_buffer = ctx->get_write_buffer(); auto max_frame_size = session->local_settings.max_frame_size; while (l > 0) { size_t send_n; int _send_flags; if (l > max_frame_size) { send_n = max_frame_size; _send_flags = 0; } else { send_n = l; _send_flags = flags; } if (send_n > remote_window_size) { // The coroutine server receives HTTP2 frames serially and cannot be suspended, // therefore it does not support flow control // TODO: The coroutine http server also needs to support HTTP2 flow control if (ctx->is_co_socket() || !swoole_coroutine_is_in()) { swoole_warning("The data sent exceeded remote_window_size"); } else { if (remote_window_size == 0) { waiting_coroutine = Coroutine::get_current(); waiting_coroutine->yield(); waiting_coroutine = nullptr; continue; } else { send_n = remote_window_size; _send_flags = 0; } } } http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, send_n, _send_flags, id); // send twice to reduce memory copy if (send_n < swoole_pagesize()) { http_buffer->clear(); http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE); http_buffer->append(p, send_n); if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) { return false; } } else { if (!ctx->send(ctx, frame_header, SW_HTTP2_FRAME_HEADER_SIZE)) { return false; } if (!ctx->send(ctx, p, send_n)) { return false; } } swoole_trace_log( SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, flags=%d, send_n=%lu", "DATA", id, flags, send_n); l -= send_n; p += send_n; remote_window_size -= send_n; session->remote_window_size -= send_n; } return true; } bool Http2Stream::send_trailer() const { char header_buffer[SW_BUFFER_SIZE_STD] = {}; char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; String *http_buffer = ctx->get_write_buffer(); http_buffer->clear(); ssize_t bytes = http2_server_build_trailer(ctx, (uchar *) header_buffer); if (bytes > 0) { http2::set_frame_header( frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, id); http_buffer->append(frame_header, SW_HTTP2_FRAME_HEADER_SIZE); http_buffer->append(header_buffer, bytes); if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) { return false; } } return true; } bool swoole_http2_server_end(HttpContext *ctx, zend_string *sdata) { String http_body = {}; if (sdata) { http_body.length = ZSTR_LEN(sdata); http_body.str = ZSTR_VAL(sdata); } else { http_body.length = 0; http_body.str = nullptr; } return http2_server_respond(ctx, &http_body); } bool swoole_http2_server_write(HttpContext *ctx, zend_string *sdata) { String chunk = {}; chunk.length = ZSTR_LEN(sdata); chunk.str = ZSTR_VAL(sdata); if (chunk.length == 0) { php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); return false; } auto client = http2_sessions[ctx->fd]; auto stream = client->get_stream(ctx->stream_id); ctx->send_chunked = 1; if (!ctx->send_header_ && !stream->send_header(nullptr, false)) { return false; } if (!stream->send_body(&chunk, false, client)) { return false; } return true; } static bool http2_server_respond(HttpContext *ctx, const String *body) { auto client = http2_sessions[ctx->fd]; auto stream = client->get_stream(ctx->stream_id); zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); if (php_swoole_array_length_safe(ztrailer) == 0) { ztrailer = nullptr; } bool end_stream = (ztrailer == nullptr); if (!ctx->send_header_ && !stream->send_header(body, end_stream)) { return false; } // The headers have already been sent, retries are no longer allowed (even if send body failed) ctx->end_ = 1; bool error = true; #ifdef SW_HAVE_COMPRESSION if (ctx->content_compressed) { body = ctx->zlib_buffer.get(); } #endif SW_LOOP { if (ctx->send_chunked && body->length == 0 && !stream->send_end_stream_data_frame()) { break; } else if (!stream->send_body(body, end_stream, client)) { break; } else if (ztrailer && !stream->send_trailer()) { break; } error = false; break; } if (error) { ctx->close(ctx); } else { client->remove_stream(stream->id); } if (client->shutting_down && client->streams.empty()) { ctx->close(ctx); } return !error; } static bool http2_server_send_status_code(HttpContext *ctx, int status_code) { auto client = http2_sessions[ctx->fd]; auto stream = client->get_stream(ctx->stream_id); ctx->response.status = status_code; return stream->send_header(nullptr, true); } static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler) { auto client = http2_sessions[ctx->fd]; auto stream = client->get_stream(ctx->stream_id); #ifdef SW_HAVE_COMPRESSION ctx->accept_compression = 0; #endif bool error = false; zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); if (php_swoole_array_length_safe(ztrailer) == 0) { ztrailer = nullptr; } zval *zheader = sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) { ctx->set_header(ZEND_STRL("content-type"), handler->get_content_type(), false); } bool end_stream = (ztrailer == nullptr); auto body = std::make_shared(); body->length = handler->get_content_length(); if (!stream->send_header(body.get(), end_stream)) { return false; } /* headers has already been sent, retries are no longer allowed (even if send body failed) */ ctx->end_ = 1; auto tasks = handler->get_tasks(); if (!tasks.empty()) { File fp(handler->get_filename(), O_RDONLY); if (!fp.ready()) { return false; } char *buf; if (tasks.size() > 1) { for (auto i = tasks.begin(); i != tasks.end(); ++i) { body = std::make_shared(i->part_header, strlen(i->part_header)); if (!stream->send_body(body.get(), false, client)) { error = true; break; } fp.set_offset(i->offset); buf = (char *) emalloc(i->length); auto n_reads = fp.read(buf, i->length); if (n_reads < 0) { efree(buf); return false; } body = std::make_shared(buf, i->length); efree(buf); if (!stream->send_body(body.get(), false, client)) { error = true; break; } } if (!error) { body = std::make_shared(handler->get_end_part()); if (!stream->send_body(body.get(), end_stream, client)) { error = true; } } } else if (tasks[0].length > 0) { auto callback = [&]() -> bool { fp.set_offset(tasks[0].offset); buf = (char *) emalloc(tasks[0].length); auto n_reads = fp.read(buf, tasks[0].length); if (n_reads < 0) { efree(buf); return false; } body = std::make_shared(buf, n_reads); efree(buf); return true; }; if (swoole_coroutine_is_in()) { if (!swoole::coroutine::async(callback)) { return false; } } else { if (!callback()) { return false; } } if (!stream->send_body(body.get(), end_stream, client)) { error = true; } } } if (!error && ztrailer) { if (!stream->send_trailer()) { error = true; } } if (error) { ctx->close(ctx); } else { client->remove_stream(ctx->stream_id); } return true; } bool swoole_http2_server_send_file(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { auto client = http2_sessions[ctx->fd]; auto stream = client->get_stream(ctx->stream_id); std::shared_ptr body; #ifdef SW_HAVE_COMPRESSION ctx->accept_compression = 0; #endif if (swoole_coroutine_is_in()) { body = System::read_file(ZSTR_VAL(file), false); if (!body) { return false; } } else { File fp(ZSTR_VAL(file), O_RDONLY); if (!fp.ready()) { return false; } body = fp.read_content(); } body->length = SW_MIN(length, body->length); zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); if (php_swoole_array_length_safe(ztrailer) == 0) { ztrailer = nullptr; } zval *zheader = sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) { ctx->set_header(ZEND_STRL("content-type"), swoole::mime_type::get({ZSTR_VAL(file), ZSTR_LEN(file)}), false); } bool end_stream = (ztrailer == nullptr); if (!stream->send_header(body.get(), end_stream)) { return false; } /* headers has already been sent, retries are no longer allowed (even if send body failed) */ ctx->end_ = 1; bool error = false; if (body->length > 0) { if (!stream->send_body(body.get(), end_stream, client, offset, length)) { error = true; } } if (!error && ztrailer) { if (!stream->send_trailer()) { error = true; } } if (error) { ctx->close(ctx); } else { client->remove_stream(stream->id); } return true; } static bool http2_server_onBeforeRequest(HttpContext *ctx) { auto serv = ctx->get_async_server(); if (serv->is_unavailable()) { String null_body{}; ctx->response.status = SW_HTTP_SERVICE_UNAVAILABLE; http2_server_respond(ctx, &null_body); zval_ptr_dtor(ctx->request.zobject); zval_ptr_dtor(ctx->response.zobject); return false; } return swoole_http_server_onBeforeRequest(ctx); } static int http2_server_parse_header( const std::shared_ptr &client, HttpContext *ctx, int flags, const char *in, size_t inlen) { nghttp2_hd_inflater *inflater = client->inflater; if (!inflater) { int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem()); if (ret != 0) { swoole_warning("nghttp2_hd_inflate_new2() failed, Error: %s[%d]", nghttp2_strerror(ret), ret); return SW_ERR; } client->inflater = inflater; } if (flags & SW_HTTP2_FLAG_PRIORITY) { // int stream_deps = ntohl(*(int *) (in)); // uint8_t weight = in[4]; in += 5; inlen -= 5; } zval *zheader = ctx->request.zheader; zval *zserver = ctx->request.zserver; for (;;) { nghttp2_nv nv; int inflate_flags = 0; ssize_t rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); if (rv < 0) { swoole_warning("inflate failed, Error: %s[%zd]", nghttp2_strerror(rv), rv); return SW_ERR; } auto proclen = (size_t) rv; in += proclen; inlen -= proclen; if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { swoole_trace_log(SW_TRACE_HTTP2, "name=(%zu)[" SW_ECHO_BLUE "], value=(%zu)[" SW_ECHO_CYAN "]", nv.namelen, nv.name, nv.valuelen, nv.value); if (nv.name[0] == ':') { if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "method")) { add_assoc_stringl_ex(zserver, ZEND_STRL("request_method"), (char *) nv.value, nv.valuelen); } else if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "path")) { char *pathbuf = sw_tg_buffer()->str; char *v_str = strchr((char *) nv.value, '?'); zend_string *zstr_path; if (v_str) { v_str++; int k_len = v_str - (char *) nv.value - 1; int v_len = nv.valuelen - k_len - 1; memcpy(pathbuf, nv.value, k_len); pathbuf[k_len] = 0; add_assoc_stringl_ex(zserver, ZEND_STRL("query_string"), v_str, v_len); zstr_path = zend_string_init(pathbuf, k_len, false); // parse url params sapi_module.treat_data( PARSE_STRING, estrndup(v_str, v_len), // it will be freed by treat_data swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, ZEND_STRL("get"))); } else { zstr_path = zend_string_init((char *) nv.value, nv.valuelen, false); } ctx->request.path = estrndup((char *) nv.value, nv.valuelen); ctx->request.path_len = nv.valuelen; add_assoc_str_ex(zserver, ZEND_STRL("request_uri"), zstr_path); // path_info should be decoded zstr_path = zend_string_dup(zstr_path, false); ZSTR_LEN(zstr_path) = php_url_decode(ZSTR_VAL(zstr_path), ZSTR_LEN(zstr_path)); add_assoc_str_ex(zserver, ZEND_STRL("path_info"), zstr_path); } else if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "authority")) { add_assoc_stringl_ex(zheader, ZEND_STRL("host"), (char *) nv.value, nv.valuelen); } } else { if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-type")) { if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "application/x-www-form-urlencoded")) { ctx->request.post_form_urlencoded = 1; } else if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; if (!ctx->get_multipart_boundary( (char *) nv.value, nv.valuelen, offset, &boundary_str, &boundary_len)) { return SW_ERR; } ctx->init_multipart_parser(boundary_str, boundary_len); ctx->parser.data = ctx; } } else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "cookie")) { swoole_http_parse_cookie( swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zcookie, ZEND_STRL("cookie")), (const char *) nv.value, nv.valuelen); continue; } else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-length")) { char *end; zend_long content_length = std::strtol((char *) nv.value, &end, 10); if (end != (char *) nv.value + nv.valuelen || content_length > client->max_body_size) { http2_server_send_status_code(ctx, SW_HTTP_REQUEST_ENTITY_TOO_LARGE); } } #ifdef SW_HAVE_COMPRESSION else if (ctx->enable_compression && SW_STRCASEEQ((char *) nv.name, nv.namelen, "accept-encoding")) { ctx->set_compression_method((char *) nv.value, nv.valuelen); } #endif add_assoc_stringl_ex(zheader, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen); } } if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { nghttp2_hd_inflate_end_headers(inflater); break; } if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { break; } } return SW_OK; } int swoole_http2_server_parse(const std::shared_ptr &client, const char *buf) { int type = buf[3]; int flags = buf[4]; uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; if (stream_id > client->last_stream_id) { client->last_stream_id = stream_id; } if (client->shutting_down) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_IGNORE, "ignore http2 stream#%d after sending goaway", stream_id); return SW_ERR; } ssize_t length = Http2::get_length(buf); buf += SW_HTTP2_FRAME_HEADER_SIZE; uint32_t value = 0; switch (type) { case SW_HTTP2_TYPE_SETTINGS: { uint16_t id = 0; if (flags & SW_HTTP2_FLAG_ACK) { swoole_http2_frame_trace_log("ACK"); break; } while (length > 0) { id = ntohs(*(uint16_t *) (buf)); value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t))); swoole_http2_frame_trace_log("id=%d, value=%d", id, value); switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: if (value != client->remote_settings.header_table_size) { client->remote_settings.header_table_size = value; if (client->deflater) { int ret = nghttp2_hd_deflate_change_table_size(client->deflater, value); if (ret != 0) { swoole_warning("nghttp2_hd_deflate_change_table_size() failed, errno=%d, errmsg=%s", ret, nghttp2_strerror(ret)); return SW_ERR; } } } swoole_trace_log(SW_TRACE_HTTP2, "setting: header_table_size=%u", value); break; case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: client->remote_settings.max_concurrent_streams = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value); break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: client->remote_window_size = client->remote_settings.init_window_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: init_window_size=%u", value); break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: client->remote_settings.max_frame_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: max_frame_size=%u", value); break; case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: client->remote_settings.max_header_list_size = value; // useless now swoole_trace_log(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value); break; default: // disable warning and ignore it because some websites are not following http2 protocol totally // swoole_warning("unknown option[%d]: %d", id, value); break; } buf += sizeof(id) + sizeof(value); length -= sizeof(id) + sizeof(value); } // After receiving the setting frame sent by the client, it is necessary to reply with an ACK, // otherwise the client will assume that the server has not applied the setting http2_server_send_setting_ack(client->default_ctx); break; } case SW_HTTP2_TYPE_HEADERS: { auto stream = client->get_stream(stream_id); swoole_http2_frame_trace_log("%s", (stream ? "exist stream" : "new stream")); if (!stream) { stream = client->create_stream(stream_id); if (!stream) { return SW_ERR; } } HttpContext *ctx = stream->ctx; if (http2_server_parse_header(client, ctx, flags, buf, length) < 0) { return SW_ERR; } if (flags & SW_HTTP2_FLAG_END_STREAM) { client->handle(client, stream); } else { // need continue frame } break; } case SW_HTTP2_TYPE_DATA: { swoole_http2_frame_trace_log("data"); auto stream = client->get_stream(stream_id); if (!stream) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NOT_FOUND, "http2 stream#%d not found", stream_id); return SW_ERR; } HttpContext *ctx = stream->ctx; if (length > 0) { auto buffer = ctx->get_http2_data_buffer(); // Exceeded the max_body_size, or if the stream has ended, sends RST_STREAM frame, stops receiving data if (buffer->length + length > client->max_body_size || ctx->end_) { http2_server_send_rst_stream(ctx, 0); client->remove_stream(stream_id); break; } buffer->append(buf, length); // flow control client->local_window_size -= length; stream->local_window_size -= length; if (client->local_window_size < (client->local_settings.init_window_size / 4)) { http2_server_send_window_update( ctx, 0, client->local_settings.init_window_size - client->local_window_size); client->local_window_size = client->local_settings.init_window_size; } if (stream->local_window_size < (client->local_settings.init_window_size / 4)) { http2_server_send_window_update( ctx, stream_id, client->local_settings.init_window_size - stream->local_window_size); stream->local_window_size = client->local_settings.init_window_size; } } if (flags & SW_HTTP2_FLAG_END_STREAM) { if (ctx->get_http2_data_length() > 0) { auto buffer = ctx->get_http2_data_buffer(); if (ctx->parse_body && ctx->request.post_form_urlencoded) { auto post_prop = swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post")); // it will be freed by sapi_module.treat_data() auto post_str = estrndup(buffer->str, buffer->length); sapi_module.treat_data(PARSE_STRING, post_str, post_prop); } else if (ctx->mt_parser != nullptr) { if (!ctx->parse_multipart_data(buffer->str, buffer->length)) { return SW_ERR; } } } client->handle(client, stream); } break; } case SW_HTTP2_TYPE_PING: { swoole_http2_frame_trace_log("ping"); if (!(flags & SW_HTTP2_FLAG_ACK)) { char ping_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header( ping_frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id); memcpy(ping_frame + SW_HTTP2_FRAME_HEADER_SIZE, buf, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); client->default_ctx->send( client->default_ctx, ping_frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); } break; } case SW_HTTP2_TYPE_WINDOW_UPDATE: { value = ntohl(*(uint32_t *) buf); if (stream_id == 0) { client->remote_window_size += value; } else { auto stream = client->get_stream(stream_id); if (stream) { stream->remote_window_size += value; if (!client->is_coro && stream->waiting_coroutine) { stream->waiting_coroutine->resume(); } } } swoole_http2_frame_trace_log("window_size_increment=%d", value); break; } case SW_HTTP2_TYPE_RST_STREAM: { value = ntohl(*(int *) (buf)); swoole_http2_frame_trace_log("error_code=%d", value); client->remove_stream(stream_id); break; } case SW_HTTP2_TYPE_GOAWAY: { uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf)); buf += 4; value = ntohl(*(uint32_t *) (buf)); buf += 4; swoole_http2_frame_trace_log("last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", server_last_stream_id, value, (int) (length - SW_HTTP2_GOAWAY_SIZE), buf); // TODO: onRequest (void) server_last_stream_id; break; } default: { swoole_http2_frame_trace_log(""); } } return SW_OK; } int swoole_http2_server_onReceive(Server *serv, Connection *conn, RecvData *req) { SessionId session_id = req->info.fd; auto iter = http2_sessions.find(session_id); std::shared_ptr client; if (iter == http2_sessions.end()) { client = swoole_http2_server_session_new(session_id); client->default_ctx = new HttpContext(); client->default_ctx->init(serv); client->default_ctx->fd = session_id; client->default_ctx->http2 = true; client->default_ctx->keepalive = true; client->default_ctx->onBeforeRequest = http2_server_onBeforeRequest; client->max_body_size = serv->get_package_max_length(conn); client->handle = http2_server_onRequest; http2_sessions.emplace(session_id, client); } else { client = iter->second; } zval zdata; php_swoole_get_recv_data(serv, &zdata, req); int retval = swoole_http2_server_parse(client, Z_STRVAL(zdata)); zval_ptr_dtor(&zdata); return retval; } std::shared_ptr swoole_http2_server_session_new(SessionId fd) { auto session = std::make_shared(fd); http2_sessions.emplace(fd, session); return session; } void php_swoole_http2_server_onClose(Server *serv, SessionId session_id) { server_ips.erase(session_id); client_ips.erase(session_id); swoole_http2_server_session_free(session_id); } void swoole_http2_server_session_free(SessionId session_id) { auto iter = http2_sessions.find(session_id); if (iter == http2_sessions.end()) { return; } /* default_ctx does not blong to session object */ iter->second->default_ctx = nullptr; http2_sessions.erase(iter); } ================================================ FILE: ext-src/swoole_http_client_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Author: Twosee | | Author: Fang | | Author: Yuanyi Zhi | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_http.h" #include "php_swoole_websocket.h" #include "swoole_protocol.h" #include "swoole_file.h" #include "swoole_util.h" #include "swoole_mime_type.h" #include "swoole_base64.h" #include "swoole_socket.h" SW_EXTERN_C_BEGIN #include "stubs/php_swoole_http_client_coro_arginfo.h" #include "ext/standard/base64.h" SW_EXTERN_C_END using swoole::AsyncFile; using swoole::String; using swoole::network::Address; using swoole::websocket::FrameObject; namespace WebSocket = swoole::websocket; static int http_parser_on_header_field(llhttp_t *parser, const char *at, size_t length); static int http_parser_on_header_value(llhttp_t *parser, const char *at, size_t length); static int http_parser_on_headers_complete(llhttp_t *parser); static int http_parser_on_body(llhttp_t *parser, const char *at, size_t length); static int http_parser_on_message_complete(llhttp_t *parser); // clang-format off static constexpr llhttp_settings_t http_parser_settings = { nullptr, // on_message_begin nullptr, // on_protocol nullptr, // on_url nullptr, // on_status nullptr, // on_method nullptr, // on_version http_parser_on_header_field, // on_header_field http_parser_on_header_value, // on_header_value nullptr, // on_chunk_extension_name nullptr, // on_chunk_extension_value http_parser_on_headers_complete, // on_headers_complete http_parser_on_body, // on_body http_parser_on_message_complete, // on_message_complete nullptr, // on_protocol_complete nullptr, // on_url_complete nullptr, // on_status_complete nullptr, // on_method_complete nullptr, // on_version_complete nullptr, // on_header_field_complete nullptr, // on_header_value_complete nullptr, // on_chunk_extension_name_complete nullptr, // on_chunk_extension_value_complete nullptr, // on_chunk_header nullptr, // on_chunk_complete nullptr, // on_reset }; // clang-format on namespace swoole { namespace coroutine { namespace http { class Client { public: /* request info */ std::string host; uint16_t port; uint8_t ssl; double connect_timeout = 0; double response_timeout = 0; bool defer = false; bool lowercase_header = true; bool use_default_port; int8_t method = SW_HTTP_GET; std::string path; std::string basic_auth; /* for response parser */ const char *tmp_header_field_name = nullptr; int tmp_header_field_name_len = 0; String *body = nullptr; #ifdef SW_HAVE_COMPRESSION enum swHttpCompressMethod compress_method = HTTP_COMPRESS_NONE; bool compression_error = false; #endif /* options */ uint8_t max_retries = 0; bool keep_alive = true; // enable by default bool websocket = false; // if upgrade successfully bool chunked = false; // Transfer-Encoding: chunked bool body_decompression = true; bool http_compression = true; bool accept_websocket_compression = false; // websocket server accepts compression WebSocketSettings websocket_settings; bool in_callback = false; bool has_upload_files = false; std::shared_ptr download_file; // save http response to file zend::String download_file_name; // unlink the file on error zend_long download_offset = 0; /* safety zval */ zval _zobject; zval *zobject = &_zobject; zval zsocket; zend::Callable *write_func = nullptr; /** * Retain the send buffer object of the Socket after the Socket object is destroyed, * allowing access to the sent Request data even after the connection has been closed. */ String *tmp_write_buffer = nullptr; std::shared_ptr continue_frame_buffer; bool connection_close = false; bool completed = false; bool event_stream = false; Client(const zval *zobject, const std::string &host, zend_long port = 80, zend_bool ssl = false); bool is_available() const { if (sw_unlikely(!socket || !socket->is_connected())) { php_swoole_socket_set_error_properties(zobject, SW_ERROR_CLIENT_NO_CONNECTION); return false; } return true; } private: #ifdef SW_HAVE_ZLIB bool gzip_stream_active = false; z_stream gzip_stream = {}; #endif #ifdef SW_HAVE_BROTLI BrotliDecoderState *brotli_decoder_state = nullptr; #endif #ifdef SW_HAVE_ZSTD ZSTD_DStream *zstd_stream = nullptr; #endif bool connect(); void set_error(int error, const char *msg, int status) const; bool keep_liveness(); bool send_request(); void reset(); static void add_headers(String *buf, const char *key, size_t key_len, const char *data, size_t data_len) { buf->append(key, key_len); buf->append(ZEND_STRL(": ")); buf->append(data, data_len); buf->append(ZEND_STRL("\r\n")); } static void add_content_length(String *buf, size_t length) { char content_length_str[64]; size_t n = sw_snprintf(SW_STRS(content_length_str), "Content-Length: %zu\r\n\r\n", length); buf->append(content_length_str, n); } static void create_token(int length, char *buf) { char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§$%&/()=[]{}"; assert(length < 1024); for (int i = 0; i < length; i++) { buf[i] = characters[swoole_random_int() % (sizeof(characters) - 1)]; } buf[length] = '\0'; } public: #ifdef SW_HAVE_COMPRESSION bool decompress_response(const char *in, size_t in_len); #endif void apply_setting(zval *zset, bool check_all = true); void set_basic_auth(const std::string &username, const std::string &password); bool exec(const std::string &_path); bool recv_response(double timeout = 0); void recv_websocket_frame(zval *return_value, double timeout = 0); void add_header(const char *key, size_t key_len, const char *str, size_t length) const; bool upgrade(const std::string &_path); bool push(zval *zdata, zend_long opcode = websocket::OPCODE_TEXT, uint8_t flags = websocket::FLAG_FIN, zend_long code = websocket::CLOSE_NORMAL); bool close(bool should_be_reset = true); void socket_dtor(); void get_header_out(zval *return_value) const { String *buffer = nullptr; if (socket == nullptr) { buffer = tmp_write_buffer; } else { buffer = socket->get_write_buffer(); } if (buffer == nullptr) { RETURN_FALSE; } off_t offset = swoole_strnpos(buffer->str, buffer->length, ZEND_STRL("\r\n\r\n")); if (offset <= 0) { RETURN_FALSE; } RETURN_STRINGL(buffer->str, offset); } void getsockname(zval *return_value) const { if (!is_available()) { RETURN_FALSE; } if (!socket->getsockname()) { php_swoole_socket_set_error_properties(zobject, socket); RETURN_FALSE; } array_init(return_value); add_assoc_string(return_value, "address", socket->get_addr()); add_assoc_long(return_value, "port", socket->get_port()); } void getpeername(zval *return_value) const { Address sa; if (!is_available()) { RETURN_FALSE; } if (!socket->getpeername(&sa)) { php_swoole_socket_set_error_properties(zobject, socket); RETURN_FALSE; } array_init(return_value); add_assoc_string(return_value, "address", sa.get_addr()); add_assoc_long(return_value, "port", sa.get_port()); } void getpeercert(zval *return_value) const { if (!is_available()) { RETURN_FALSE; } auto cert = socket->ssl_get_peer_cert(); if (cert.empty()) { php_swoole_socket_set_error_properties(zobject, socket); RETURN_FALSE; } else { RETURN_STRINGL(cert.c_str(), cert.length()); } } ~Client(); private: SocketImpl *socket = nullptr; NameResolver::Context resolve_context_ = {}; SocketType socket_type = SW_SOCK_TCP; llhttp_t parser = {}; bool wait_response = false; }; } // namespace http } // namespace coroutine } // namespace swoole static zend_class_entry *swoole_http_client_coro_ce; static zend_object_handlers swoole_http_client_coro_handlers; static zend_class_entry *swoole_http_client_coro_exception_ce; static zend_object_handlers swoole_http_client_coro_exception_handlers; using swoole::coroutine::http::Client; struct HttpClientObject { Client *client; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http_client_coro, __construct); static PHP_METHOD(swoole_http_client_coro, __destruct); static PHP_METHOD(swoole_http_client_coro, set); static PHP_METHOD(swoole_http_client_coro, getDefer); static PHP_METHOD(swoole_http_client_coro, setDefer); static PHP_METHOD(swoole_http_client_coro, setMethod); static PHP_METHOD(swoole_http_client_coro, setHeaders); static PHP_METHOD(swoole_http_client_coro, setBasicAuth); static PHP_METHOD(swoole_http_client_coro, setCookies); static PHP_METHOD(swoole_http_client_coro, setData); static PHP_METHOD(swoole_http_client_coro, addFile); static PHP_METHOD(swoole_http_client_coro, addData); static PHP_METHOD(swoole_http_client_coro, execute); static PHP_METHOD(swoole_http_client_coro, getsockname); static PHP_METHOD(swoole_http_client_coro, getpeername); static PHP_METHOD(swoole_http_client_coro, get); static PHP_METHOD(swoole_http_client_coro, post); static PHP_METHOD(swoole_http_client_coro, download); static PHP_METHOD(swoole_http_client_coro, getBody); static PHP_METHOD(swoole_http_client_coro, getHeaders); static PHP_METHOD(swoole_http_client_coro, getCookies); static PHP_METHOD(swoole_http_client_coro, getStatusCode); static PHP_METHOD(swoole_http_client_coro, getHeaderOut); static PHP_METHOD(swoole_http_client_coro, getPeerCert); static PHP_METHOD(swoole_http_client_coro, upgrade); static PHP_METHOD(swoole_http_client_coro, push); static PHP_METHOD(swoole_http_client_coro, recv); static PHP_METHOD(swoole_http_client_coro, close); static PHP_METHOD(swoole_http_client_coro, ping); static PHP_METHOD(swoole_http_client_coro, disconnect); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_http_client_coro_methods[] = { PHP_ME(swoole_http_client_coro, __construct, arginfo_class_Swoole_Coroutine_Http_Client___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, __destruct, arginfo_class_Swoole_Coroutine_Http_Client___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, set, arginfo_class_Swoole_Coroutine_Http_Client_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getDefer, arginfo_class_Swoole_Coroutine_Http_Client_getDefer, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setDefer, arginfo_class_Swoole_Coroutine_Http_Client_setDefer, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setMethod, arginfo_class_Swoole_Coroutine_Http_Client_setMethod, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setHeaders, arginfo_class_Swoole_Coroutine_Http_Client_setHeaders, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setBasicAuth, arginfo_class_Swoole_Coroutine_Http_Client_setBasicAuth, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setCookies, arginfo_class_Swoole_Coroutine_Http_Client_setCookies, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, setData, arginfo_class_Swoole_Coroutine_Http_Client_setData, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, addFile, arginfo_class_Swoole_Coroutine_Http_Client_addFile, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, addData, arginfo_class_Swoole_Coroutine_Http_Client_addData, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, execute, arginfo_class_Swoole_Coroutine_Http_Client_execute, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getpeername, arginfo_class_Swoole_Coroutine_Http_Client_getpeername, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getsockname, arginfo_class_Swoole_Coroutine_Http_Client_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, get, arginfo_class_Swoole_Coroutine_Http_Client_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, post, arginfo_class_Swoole_Coroutine_Http_Client_post, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, download, arginfo_class_Swoole_Coroutine_Http_Client_download, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getBody, arginfo_class_Swoole_Coroutine_Http_Client_getBody, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getHeaders, arginfo_class_Swoole_Coroutine_Http_Client_getHeaders, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getCookies, arginfo_class_Swoole_Coroutine_Http_Client_getCookies, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getStatusCode, arginfo_class_Swoole_Coroutine_Http_Client_getStatusCode, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getHeaderOut, arginfo_class_Swoole_Coroutine_Http_Client_getHeaderOut, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getPeerCert, arginfo_class_Swoole_Coroutine_Http_Client_getPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, upgrade, arginfo_class_Swoole_Coroutine_Http_Client_upgrade, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, push, arginfo_class_Swoole_Coroutine_Http_Client_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, recv, arginfo_class_Swoole_Coroutine_Http_Client_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, close, arginfo_class_Swoole_Coroutine_Http_Client_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, ping, arginfo_class_Swoole_Coroutine_Http_Client_ping, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, disconnect, arginfo_class_Swoole_Coroutine_Http_Client_disconnect, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers) { const char *eof = at + length; size_t key_len = 0, value_len = 0; zval zvalue; // key const char *p = (char *) memchr(at, '=', length); if (p) { key_len = p - at; p++; // point to value } else { p = at; // key is empty } // value eof = (char *) memchr(p, ';', at + length - p); if (!eof) { eof = at + length; } value_len = eof - p; if (value_len != 0) { ZVAL_STRINGL(&zvalue, p, value_len); Z_STRLEN(zvalue) = php_url_decode(Z_STRVAL(zvalue), value_len); } else { ZVAL_EMPTY_STRING(&zvalue); } if (key_len == 0) { add_next_index_zval(zcookies, &zvalue); } else { add_assoc_zval_ex(zcookies, at, key_len, &zvalue); } // set_cookie_headers add_next_index_stringl(zset_cookie_headers, (char *) at, length); } static int http_parser_on_header_field(llhttp_t *parser, const char *at, size_t length) { auto *http = static_cast(parser->data); http->tmp_header_field_name = at; http->tmp_header_field_name_len = length; return 0; } static int http_parser_on_header_value(llhttp_t *parser, const char *at, size_t length) { auto *http = static_cast(parser->data); zval *zobject = static_cast(http->zobject); const char *header_name = http->tmp_header_field_name; size_t header_len = http->tmp_header_field_name_len; zend::CharPtr _header_name; if (http->lowercase_header) { _header_name.assign_tolower(header_name, header_len); header_name = _header_name.get(); } http->add_header(header_name, header_len, (char *) at, length); if (parser->status_code == SW_HTTP_SWITCHING_PROTOCOLS && SW_STREQ(header_name, header_len, "upgrade")) { if (swoole_http_token_list_contains_value(at, length, "websocket")) { http->websocket = true; } /* TODO: protocol error? */ } #ifdef SW_HAVE_ZLIB else if (http->websocket && http->websocket_settings.compression && SW_STREQ(header_name, header_len, "sec-websocket-extensions")) { if (swoole_strncasestr(at, length, SW_STRL("permessage-deflate"))) { http->accept_websocket_compression = true; } } #endif else if (SW_STREQ(header_name, header_len, "set-cookie")) { zval *zcookies = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("cookies"), 0); zval *zset_cookie_headers = sw_zend_read_and_convert_property_array( swoole_http_client_coro_ce, zobject, ZEND_STRL("set_cookie_headers"), 0); php_swoole_http_parse_set_cookies(at, length, zcookies, zset_cookie_headers); } #ifdef SW_HAVE_COMPRESSION else if (SW_STREQ(header_name, header_len, "content-encoding")) { if (false) { } #ifdef SW_HAVE_BROTLI else if (SW_STR_ISTARTS_WITH(at, length, "br")) { http->compress_method = HTTP_COMPRESS_BR; } #endif #ifdef SW_HAVE_ZLIB else if (SW_STR_ISTARTS_WITH(at, length, "gzip")) { http->compress_method = HTTP_COMPRESS_GZIP; } else if (SW_STR_ISTARTS_WITH(at, length, "deflate")) { http->compress_method = HTTP_COMPRESS_DEFLATE; } #endif #ifdef SW_HAVE_ZSTD else if (SW_STR_ISTARTS_WITH(at, length, "zstd")) { http->compress_method = HTTP_COMPRESS_ZSTD; } #endif } #endif else if (SW_STREQ(header_name, header_len, "transfer-encoding") && SW_STR_ISTARTS_WITH(at, length, "chunked")) { http->chunked = true; } else if (SW_STREQ(header_name, header_len, "connection")) { http->connection_close = SW_STR_ISTARTS_WITH(at, length, "close"); } else if (SW_STREQ(header_name, header_len, "content-type")) { http->event_stream = SW_STR_ISTARTS_WITH(at, length, "text/event-stream"); } return 0; } static int http_parser_on_headers_complete(llhttp_t *parser) { auto *http = static_cast(parser->data); if (http->method == SW_HTTP_HEAD || parser->status_code == SW_HTTP_NO_CONTENT) { return 1; } return 0; } static int http_parser_on_body(llhttp_t *parser, const char *at, size_t length) { auto *http = static_cast(parser->data); if (http->write_func) { zval zargv[2]; zargv[0] = *http->zobject; ZVAL_STRINGL(&zargv[1], at, length); http->in_callback = true; bool success = http->write_func->call(2, zargv, nullptr); http->in_callback = false; zval_ptr_dtor(&zargv[1]); return success ? 0 : -1; } #ifdef SW_HAVE_COMPRESSION else if (http->body_decompression && !http->compression_error && http->compress_method != HTTP_COMPRESS_NONE) { if (!http->decompress_response(at, length)) { http->compression_error = true; goto _append_raw; } } #endif else { #ifdef SW_HAVE_COMPRESSION _append_raw: #endif http->body->append(at, length); } if (http->download_file_name.get() && http->body->length > 0) { if (http->download_file == nullptr) { char *download_file_name = http->download_file_name.val(); auto fp = std::make_shared(download_file_name, O_CREAT | O_WRONLY, 0664); if (!fp->ready()) { swoole_sys_warning("open(%s, O_CREAT | O_WRONLY) failed", download_file_name); return -1; } if (http->download_offset == 0) { if (!fp->truncate(0)) { swoole_sys_warning("ftruncate(%s) failed", download_file_name); return -1; } } else { if (!fp->set_offset(http->download_offset)) { swoole_sys_warning("fseek(%s, %jd) failed", download_file_name, (intmax_t) http->download_offset); return -1; } } http->download_file = fp; } if (http->download_file->write(http->body) != (ssize_t) http->body->length) { return -1; } http->body->clear(); } return 0; } static int http_parser_on_message_complete(llhttp_t *parser) { auto *http = static_cast(parser->data); zval *zobject = static_cast(http->zobject); http->completed = true; if (parser->upgrade && !http->websocket) { // not support, continue. parser->upgrade = 0; return HPE_PAUSED; } zend_update_property_long( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("statusCode"), parser->status_code); if (http->download_file == nullptr) { zend_update_property_stringl( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("body"), SW_STRINGL(http->body)); } else { http->download_file_name.release(); } return HPE_PAUSED; } Client::Client(const zval *zobject, const std::string &host, zend_long port, zend_bool ssl) { this->host = host; this->socket_type = network::Socket::convert_to_type(this->host); this->use_default_port = port == 0; if (this->use_default_port) { port = ssl ? 443 : 80; } this->port = port; this->ssl = ssl; _zobject = *zobject; } #ifdef SW_HAVE_COMPRESSION bool Client::decompress_response(const char *in, size_t in_len) { if (in_len == 0) { return false; } size_t reserved_body_length = body->length; switch (compress_method) { #ifdef SW_HAVE_ZLIB case HTTP_COMPRESS_GZIP: case HTTP_COMPRESS_DEFLATE: { int status; int encoding = compress_method == HTTP_COMPRESS_GZIP ? SW_ZLIB_ENCODING_GZIP : SW_ZLIB_ENCODING_DEFLATE; bool first_decompress = !gzip_stream_active; if (!gzip_stream_active) { _retry: memset(&gzip_stream, 0, sizeof(gzip_stream)); gzip_stream.zalloc = php_zlib_alloc; gzip_stream.zfree = php_zlib_free; // gzip_stream.total_out = 0; status = inflateInit2(&gzip_stream, encoding); if (status != Z_OK) { swoole_warning("inflateInit2() failed by %s", zError(status)); return false; } gzip_stream_active = true; } gzip_stream.next_in = (Bytef *) in; gzip_stream.avail_in = in_len; gzip_stream.total_in = 0; while (true) { const size_t total_out = gzip_stream.total_out; gzip_stream.avail_out = body->size - body->length; gzip_stream.next_out = (Bytef *) (body->str + body->length); SW_ASSERT(body->length <= body->size); status = inflate(&gzip_stream, Z_SYNC_FLUSH); if (status >= 0) { body->length += (gzip_stream.total_out - total_out); if (body->length + (SW_BUFFER_SIZE_STD / 2) >= body->size) { body->extend(); } } if (status == Z_STREAM_END || (status == Z_OK && gzip_stream.avail_in == 0)) { return true; } if (status != Z_OK) { break; } } if (status == Z_DATA_ERROR && first_decompress) { first_decompress = false; inflateEnd(&gzip_stream); encoding = SW_ZLIB_ENCODING_RAW; body->length = reserved_body_length; goto _retry; } swoole_warning("HttpClient::decompress_response failed by %s", zError(status)); body->length = reserved_body_length; return false; } #endif #ifdef SW_HAVE_BROTLI case HTTP_COMPRESS_BR: { if (!brotli_decoder_state) { brotli_decoder_state = BrotliDecoderCreateInstance(php_brotli_alloc, php_brotli_free, nullptr); if (!brotli_decoder_state) { swoole_warning("BrotliDecoderCreateInstance() failed"); return false; } } const char *next_in = in; size_t available_in = in_len; while (true) { size_t available_out = body->size - body->length, reserved_available_out = available_out; char *next_out = body->str + body->length; size_t total_out; SW_ASSERT(body->length <= body->size); BrotliDecoderResult result = BrotliDecoderDecompressStream(brotli_decoder_state, &available_in, (const uint8_t **) &next_in, &available_out, (uint8_t **) &next_out, &total_out); body->length += reserved_available_out - available_out; if (result == BROTLI_DECODER_RESULT_SUCCESS || result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { return true; } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { body->extend(); } else { swoole_warning("BrotliDecoderDecompressStream() failed, %s", BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotli_decoder_state))); break; } } body->length = reserved_body_length; return false; } #endif #ifdef SW_HAVE_ZSTD case HTTP_COMPRESS_ZSTD: { size_t zstd_result = 0; if (zstd_stream == nullptr) { zstd_stream = ZSTD_createDStream(); if (!zstd_stream) { swoole_warning("ZSTD_createDStream() failed, can not create ZSTD stream"); return false; } zstd_result = ZSTD_initDStream(zstd_stream); if (ZSTD_isError(zstd_result)) { swoole_warning("ZSTD_initDStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); return false; } } size_t recommended_size = ZSTD_DStreamOutSize(); ZSTD_inBuffer in_buffer = {in, in_len, 0}; ZSTD_outBuffer out_buffer = {body->str + body->length, body->size - body->length, 0}; while (in_buffer.pos < in_buffer.size) { if (sw_unlikely(out_buffer.pos == out_buffer.size)) { body->extend(recommended_size + body->size); body->length += out_buffer.pos; out_buffer = {body->str + body->length, body->size - body->length, 0}; } zstd_result = ZSTD_decompressStream(zstd_stream, &out_buffer, &in_buffer); if (ZSTD_isError(zstd_result)) { swoole_warning("ZSTD_decompressStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); return false; } } body->length += out_buffer.pos; return true; } #endif default: break; } swoole_warning("HttpClient::decompress_response unknown compress method [%d]", compress_method); return false; } #endif void Client::apply_setting(zval *zset, const bool check_all) { if (!ZVAL_IS_ARRAY(zset) || php_swoole_array_length(zset) == 0) { return; } if (check_all) { zval *ztmp; HashTable *vht = Z_ARRVAL_P(zset); if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { connect_timeout = zval_get_double(ztmp); } if (php_swoole_array_get_value(vht, "timeout", ztmp)) { response_timeout = zval_get_double(ztmp); } if (php_swoole_array_get_value(vht, "max_retries", ztmp)) { max_retries = (uint8_t) SW_MIN(zval_get_long(ztmp), UINT8_MAX); } if (php_swoole_array_get_value(vht, "defer", ztmp)) { defer = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "lowercase_header", ztmp)) { lowercase_header = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "keep_alive", ztmp)) { keep_alive = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "http_compression", ztmp)) { http_compression = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "body_decompression", ztmp)) { body_decompression = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "write_func", ztmp)) { delete write_func; write_func = sw_callable_create(ztmp); } WebSocket::apply_setting(websocket_settings, vht, false); } if (socket) { php_swoole_socket_set(socket, zset); if (socket->http_proxy && !socket->ssl_is_enable()) { socket->http_proxy->dont_handshake = 1; } } } void Client::set_basic_auth(const std::string &username, const std::string &password) { std::string input = username + ":" + password; size_t output_size = sizeof("Basic ") + BASE64_ENCODE_OUT_SIZE(input.size()); char *output = (char *) emalloc(output_size); if (sw_likely(output)) { size_t output_len = sprintf(output, "Basic "); output_len += base64_encode((const unsigned char *) input.c_str(), input.size(), output + output_len); basic_auth = std::string((const char *) output, output_len); efree(output); } } void Client::add_header(const char *key, size_t key_len, const char *str, size_t length) const { zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0); zval zheader_new; ZVAL_STRINGL(&zheader_new, str, length); zend::array_add_or_merge(zheaders, key, key_len, &zheader_new); } bool Client::connect() { if (socket) { return true; } if (!body) { body = new String(SW_HTTP_RESPONSE_INIT_SIZE); if (!body) { set_error(ENOMEM, swoole_strerror(ENOMEM), HTTP_ESTATUS_CONNECT_FAILED); return false; } } php_swoole_check_reactor(); auto object = php_swoole_create_socket(socket_type); if (UNEXPECTED(!object)) { set_error(errno, swoole_strerror(errno), HTTP_ESTATUS_CONNECT_FAILED); return false; } ZVAL_OBJ(&zsocket, object); socket = php_swoole_get_socket(&zsocket); if (ssl && !socket->enable_ssl_encrypt()) { set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_CONNECT_FAILED); close(); return false; } // apply settings apply_setting(sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0), false); // reset the properties that depend on the connection websocket = false; #ifdef SW_HAVE_ZLIB accept_websocket_compression = false; #endif double _timeout = connect_timeout == 0 ? network::Socket::default_connect_timeout : connect_timeout; socket->set_timeout(_timeout, SW_TIMEOUT_CONNECT); socket->set_resolve_context(&resolve_context_); socket->set_dtor([this](Socket *_socket) { socket_dtor(); }); socket->set_buffer_allocator(sw_zend_string_allocator()); if (!socket->connect(host, port)) { set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_CONNECT_FAILED); close(); return false; } zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &zsocket); zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1); return true; } void Client::set_error(int error, const char *msg, int status) const { auto ce = swoole_http_client_coro_ce; auto obj = SW_Z8_OBJ_P(zobject); zend_update_property_long(ce, obj, ZEND_STRL("errCode"), error); zend_update_property_string(ce, obj, ZEND_STRL("errMsg"), msg); zend_update_property_long(ce, obj, ZEND_STRL("statusCode"), status); } bool Client::keep_liveness() { if (!socket || !socket->check_liveness()) { if (socket) { /* in progress */ socket->check_bound_co(SW_EVENT_RDWR); set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_SERVER_RESET); close(false); } SW_LOOP_N(max_retries + 1) { if (connect()) { return true; } } return false; } return true; } bool Client::send_request() { zval *zvalue = nullptr; uint32_t header_flag = 0x0; zval *zmethod, *zheaders, *zbody, *zupload_files, *zcookies, *z_download_file; if (path.empty()) { php_swoole_socket_set_error_properties(zobject, SW_ERROR_INVALID_PARAMS); return false; } // when new request, clear all properties about the last response zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("headers")); zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("set_cookie_headers")); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("body"), ""); if (!keep_liveness()) { return false; } zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), 0); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), ""); zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("statusCode"), 0); /* another coroutine is connecting */ socket->check_bound_co(SW_EVENT_WRITE); // clear errno swoole_set_last_error(0); // alloc buffer String *buffer = socket->get_write_buffer(); buffer->clear(); // clear body body->clear(); zmethod = sw_zend_read_property_not_null_ex( swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_METHOD), 0); zheaders = sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_HEADERS), 0); zbody = sw_zend_read_property_not_null_ex( swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_BODY), 0); zupload_files = sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_UPLOAD_FILES), 0); zcookies = sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIES), 0); z_download_file = sw_zend_read_property_not_null_ex( swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_DOWNLOAD_FILE), 0); // ============ host ============ zend::String str_host; if ((ZVAL_IS_ARRAY(zheaders)) && (((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host")))) || ((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("host")))))) { str_host = zvalue; } // ============ download ============ if (z_download_file) { download_file_name = z_download_file; download_offset = zval_get_long(sw_zend_read_property_ex( swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_DOWNLOAD_OFFSET), 0)); } // ============ method ============ { zend::String str_method; const char *_method; size_t method_len; if (zmethod) { str_method = zmethod; _method = str_method.val(); method_len = str_method.len(); } else { _method = zbody ? "POST" : "GET"; method_len = strlen(_method); } this->method = http_server::get_method(_method, method_len); buffer->append(_method, method_len); buffer->append(ZEND_STRL(" ")); } // ============ path & proxy ============ bool require_proxy_authentication = false; if (socket->http_proxy && !socket->ssl_is_enable()) { const static char *pre = "http://"; char *_host = (char *) host.c_str(); size_t _host_len = host.length(); if (str_host.get()) { _host = str_host.val(); _host_len = str_host.len(); } size_t proxy_uri_len = path.length() + _host_len + strlen(pre) + 10; char *proxy_uri = (char *) emalloc(proxy_uri_len); if (nullptr == memchr(_host, ':', _host_len)) { proxy_uri_len = sw_snprintf(proxy_uri, proxy_uri_len, "%s%s:%u%s", pre, _host, port, path.c_str()); } else { proxy_uri_len = sw_snprintf(proxy_uri, proxy_uri_len, "%s%s%s", pre, _host, path.c_str()); } buffer->append(proxy_uri, proxy_uri_len); if (!socket->http_proxy->password.empty()) { require_proxy_authentication = true; } efree(proxy_uri); } else { buffer->append(path.c_str(), path.length()); } // ============ protocol ============ buffer->append(ZEND_STRL(" HTTP/1.1\r\n")); // ============ headers ============ char *key; uint32_t keylen; int keytype; // As much as possible to ensure that Host is the first header. // See: http://tools.ietf.org/html/rfc7230#section-5.4 if (str_host.get()) { add_headers(buffer, ZEND_STRL("Host"), str_host.val(), str_host.len()); } else { // See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23 const std::string *_host; std::string real_host; if (!ssl ? port != 80 : port != 443) { real_host = std_string::format("%s:%u", host.c_str(), port); _host = &real_host; } else { _host = &host; } add_headers(buffer, ZEND_STRL("Host"), _host->c_str(), _host->length()); } if (ZVAL_IS_ARRAY(zheaders)) { SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheaders), key, keylen, keytype, zvalue) { if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) { continue; } if (SW_STRCASEEQ(key, keylen, "Host")) { continue; } if (SW_STRCASEEQ(key, keylen, "Content-Length")) { header_flag |= HTTP_HEADER_CONTENT_LENGTH; // ignore custom Content-Length value continue; } if (SW_STRCASEEQ(key, keylen, "Accept-Encoding")) { #ifdef SW_HAVE_COMPRESSION header_flag |= HTTP_HEADER_ACCEPT_ENCODING; #else php_swoole_error(E_WARNING, "Missing a compression package, 'Accept-Encoding' is ignored"); continue; #endif } zend::String str_value(zvalue); add_headers(buffer, key, keylen, str_value.val(), str_value.len()); if (SW_STRCASEEQ(key, keylen, "Connection")) { header_flag |= HTTP_HEADER_CONNECTION; if (SW_STRCASEEQ(str_value.val(), str_value.len(), "close")) { keep_alive = false; } } } SW_HASHTABLE_FOREACH_END(); } // http proxy authentication if (require_proxy_authentication) { std::string value("Basic "); value += socket->http_proxy->get_auth_str(); add_headers(buffer, ZEND_STRL("Proxy-Authorization"), value.c_str(), value.length()); } if (!basic_auth.empty()) { add_headers(buffer, ZEND_STRL("Authorization"), basic_auth.c_str(), basic_auth.size()); } if (!(header_flag & HTTP_HEADER_CONNECTION)) { if (keep_alive) { add_headers(buffer, ZEND_STRL("Connection"), ZEND_STRL("keep-alive")); } else { add_headers(buffer, ZEND_STRL("Connection"), ZEND_STRL("closed")); } } #ifdef SW_HAVE_COMPRESSION if (http_compression && !(header_flag & HTTP_HEADER_ACCEPT_ENCODING)) { add_headers(buffer, ZEND_STRL("Accept-Encoding"), #if defined(SW_HAVE_ZLIB) && defined(SW_HAVE_BROTLI) ZEND_STRL("gzip, deflate, br") #else #ifdef SW_HAVE_ZLIB ZEND_STRL("gzip, deflate") #else #ifdef SW_HAVE_BROTLI ZEND_STRL("br") #else #ifdef SW_HAVE_ZSTD ZEND_STRL("zstd") #endif #endif #endif #endif ); } #endif // ============ cookies ============ if (ZVAL_IS_ARRAY(zcookies)) { buffer->append(ZEND_STRL("Cookie: ")); int n_cookie = php_swoole_array_length(zcookies); int i = 0; char *encoded_value; SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zcookies), key, keylen, keytype, zvalue) { i++; if (HASH_KEY_IS_STRING != keytype) { continue; } zend::String str_value(zvalue); if (str_value.len() == 0) { continue; } buffer->append(key, keylen); buffer->append("=", 1); size_t encoded_value_len; encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len); if (encoded_value) { buffer->append(encoded_value, encoded_value_len); efree(encoded_value); } if (i < n_cookie) { buffer->append("; ", 2); } } SW_HASHTABLE_FOREACH_END(); buffer->append(ZEND_STRL("\r\n")); } // ============ multipart/form-data ============ if ((has_upload_files = (php_swoole_array_length_safe(zupload_files) > 0))) { char header_buf[2048]; char boundary_str[SW_HTTP_CLIENT_BOUNDARY_TOTAL_SIZE + 1]; // ============ content-type ============ memcpy(boundary_str, SW_HTTP_CLIENT_BOUNDARY_PREKEY, sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1); swoole_random_string(boundary_str + sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1, sizeof(boundary_str) - sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY)); ssize_t n = sw_snprintf(header_buf, sizeof(header_buf), "Content-Type: multipart/form-data; boundary=%.*s\r\n", (int) (sizeof(boundary_str) - 1), boundary_str); buffer->append(header_buf, n); // ============ content-length ============ size_t content_length = 0; // calculate length before encode array if (zbody && ZVAL_IS_ARRAY(zbody)) { SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, zvalue) if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); // strlen("%.*s")*2 = 8 // header + body + CRLF(2) content_length += (sizeof(SW_HTTP_FORM_RAW_DATA_FMT) - SW_HTTP_FORM_RAW_DATA_FMT_LEN - 1) + (sizeof(boundary_str) - 1) + keylen + str_value.len() + 2; SW_HASHTABLE_FOREACH_END(); } zval *zname; zval *ztype; zval *zsize = nullptr; zval *zpath = nullptr; zval *zcontent = nullptr; zval *zfilename; zval *zoffset; // calculate length of files { // upload files SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) { HashTable *ht = Z_ARRVAL_P(zvalue); if (!((zname = zend_hash_str_find(ht, ZEND_STRL("name"))))) { continue; } if (!((zfilename = zend_hash_str_find(ht, ZEND_STRL("filename"))))) { continue; } if (!((zsize = zend_hash_str_find(ht, ZEND_STRL("size"))))) { continue; } if (!((ztype = zend_hash_str_find(ht, ZEND_STRL("type"))))) { continue; } // strlen("%.*s")*4 = 16 // header + body + CRLF(2) content_length += (sizeof(SW_HTTP_FORM_FILE_DATA_FMT) - SW_HTTP_FORM_FILE_DATA_FMT_LEN - 1) + (sizeof(boundary_str) - 1) + Z_STRLEN_P(zname) + Z_STRLEN_P(zfilename) + Z_STRLEN_P(ztype) + Z_LVAL_P(zsize) + 2; } SW_HASHTABLE_FOREACH_END(); } add_content_length(buffer, content_length + sizeof(boundary_str) - 1 + 6); // ============ form-data body ============ if (zbody && ZVAL_IS_ARRAY(zbody)) { SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, zvalue) { if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); n = sw_snprintf(header_buf, sizeof(header_buf), SW_HTTP_FORM_RAW_DATA_FMT, (int) (sizeof(boundary_str) - 1), boundary_str, keylen, key); buffer->append(header_buf, n); buffer->append(str_value.val(), str_value.len()); buffer->append(ZEND_STRL("\r\n")); } SW_HASHTABLE_FOREACH_END(); } if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { goto _send_fail; } { // upload files SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) { if (!((zname = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("name"))))) { continue; } if (!((zfilename = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("filename"))))) { continue; } /** * from disk file */ if (!((zcontent = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("content"))))) { // file path if (!((zpath = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("path"))))) { continue; } // file offset if (!((zoffset = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("offset"))))) { continue; } zcontent = nullptr; } else { zpath = nullptr; zoffset = nullptr; } if (!((zsize = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("size"))))) { continue; } if (!((ztype = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("type"))))) { continue; } /** * part header */ n = sw_snprintf(header_buf, sizeof(header_buf), SW_HTTP_FORM_FILE_DATA_FMT, (int) (sizeof(boundary_str) - 1), boundary_str, (int) Z_STRLEN_P(zname), Z_STRVAL_P(zname), (int) Z_STRLEN_P(zfilename), Z_STRVAL_P(zfilename), (int) Z_STRLEN_P(ztype), Z_STRVAL_P(ztype)); /** * from memory */ if (zcontent) { buffer->clear(); buffer->append(header_buf, n); buffer->append(Z_STRVAL_P(zcontent), Z_STRLEN_P(zcontent)); buffer->append("\r\n", 2); if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { goto _send_fail; } } /** * from disk file */ else { if (socket->send_all(header_buf, n) != n) { goto _send_fail; } if (!socket->sendfile(Z_STRVAL_P(zpath), Z_LVAL_P(zoffset), Z_LVAL_P(zsize))) { goto _send_fail; } if (socket->send_all("\r\n", 2) != 2) { goto _send_fail; } } } SW_HASHTABLE_FOREACH_END(); } n = sw_snprintf(header_buf, sizeof(header_buf), "--%.*s--\r\n", (int) (sizeof(boundary_str) - 1), boundary_str); if (socket->send_all(header_buf, n) != n) { goto _send_fail; } wait_response = true; return true; } // ============ x-www-form-urlencoded or raw ============ else if (zbody) { if (ZVAL_IS_ARRAY(zbody)) { size_t len; add_headers(buffer, ZEND_STRL("Content-Type"), ZEND_STRL("application/x-www-form-urlencoded")); if (php_swoole_array_length(zbody) > 0) { smart_str formstr_s = {}; char *formstr = php_swoole_http_build_query(zbody, &len, &formstr_s); if (formstr == nullptr) { php_swoole_error(E_WARNING, "http_build_query failed"); return false; } add_content_length(buffer, len); buffer->append(formstr, len); smart_str_free(&formstr_s); } else { add_content_length(buffer, 0); } } else { auto sdata = zval_get_string(zbody); add_content_length(buffer, ZSTR_LEN(sdata)); buffer->append(ZSTR_VAL(sdata), ZSTR_LEN(sdata)); zend_string_release(sdata); } } // ============ no body ============ else { if (header_flag & HTTP_HEADER_CONTENT_LENGTH) { add_content_length(buffer, 0); } else { buffer->append(ZEND_STRL("\r\n")); } } swoole_trace_log(SW_TRACE_HTTP_CLIENT, "to [%s:%u%s] by fd#%d in cid#%ld with [%zu] bytes: <get_fd(), Coroutine::get_current_cid(), buffer->length, (int) buffer->length, buffer->str); if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { _send_fail: set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_SEND_FAILED); close(); return false; } wait_response = true; return true; } bool Client::exec(const std::string &_path) { path = _path; // bzero when make a new reqeust resolve_context_ = {}; if (use_default_port) { resolve_context_.with_port = true; } SW_LOOP_N(max_retries + 1) { if (send_request() == false) { return false; } if (defer) { return true; } if (recv_response() == false) { return false; } if (max_retries > 0 && (parser.status_code == SW_HTTP_BAD_GATEWAY || parser.status_code == SW_HTTP_SERVICE_UNAVAILABLE)) { close(true); continue; } return true; } return false; } bool Client::recv_response(double timeout) { if (!wait_response) { return false; } ssize_t retval = 0; size_t total_bytes = 0, parsed_n = 0; String *buffer = socket->get_read_buffer(); bool header_completed = false; off_t header_crlf_offset = 0; // re-init http response parser swoole_llhttp_parser_init(&parser, HTTP_RESPONSE, (void *) this); if (timeout == 0) { timeout = response_timeout == 0 ? network::Socket::default_read_timeout : response_timeout; } Socket::TimeoutController tc(socket, timeout, SW_TIMEOUT_READ); bool success = false; while (true) { if (sw_unlikely(tc.has_timedout(SW_TIMEOUT_READ))) { break; } retval = socket->recv(buffer->str + buffer->length, buffer->size - buffer->length); if (sw_unlikely(retval <= 0)) { if (retval == 0) { socket->set_err(ECONNRESET); if (total_bytes > 0 && !llhttp_should_keep_alive(&parser)) { llhttp_finish(&parser); success = true; break; } } break; } if (!header_completed) { buffer->length += retval; if (swoole_strnpos( buffer->str + header_crlf_offset, buffer->length - header_crlf_offset, ZEND_STRL("\r\n\r\n")) < 0) { if (buffer->length == buffer->size) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Http header too large"); socket->set_err(SW_ERROR_HTTP_INVALID_PROTOCOL); break; } header_crlf_offset = buffer->length > 4 ? buffer->length - 4 : 0; continue; } else { header_completed = true; header_crlf_offset = 0; retval = buffer->length; buffer->clear(); } } total_bytes += retval; parsed_n = swoole_llhttp_parser_execute(&parser, &http_parser_settings, buffer->str, retval); swoole_trace_log(SW_TRACE_HTTP_CLIENT, "parsed_n=%ld, retval=%ld, total_bytes=%ld, completed=%d", parsed_n, retval, total_bytes, completed); if (sw_unlikely(socket->get_socket()->close_wait)) { success = false; break; } if (sw_likely(parser.error == HPE_OK)) { if (sw_unlikely(event_stream && llhttp_message_needs_eof(&parser)) == 1) { llhttp_finish(&parser); } if (completed) { if (parser.upgrade && (size_t) retval > parsed_n + SW_WEBSOCKET_HEADER_LEN) { buffer->length = retval; buffer->offset = parsed_n; buffer->reduce(parsed_n); } success = true; break; } } else { socket->set_err(SW_ERROR_HTTP_INVALID_PROTOCOL); break; } } if (!success) { php_swoole_socket_set_error_properties(zobject, socket); zend::object_set(zobject, ZEND_STRL("statusCode"), socket->errCode == ETIMEDOUT ? HTTP_ESTATUS_REQUEST_TIMEOUT : HTTP_ESTATUS_SERVER_RESET); close(); return false; } /** * TODO: Sec-WebSocket-Accept check */ if (websocket) { socket->open_length_check = true; socket->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN; socket->protocol.package_length_offset = 0; socket->protocol.package_body_offset = 0; socket->protocol.get_package_length = websocket::get_package_length; } // handler keep alive if (!websocket && (!keep_alive || connection_close)) { close(); } else { reset(); } return true; } void Client::recv_websocket_frame(zval *return_value, double timeout) { WebSocket::recv_frame(websocket_settings, continue_frame_buffer, socket, return_value, timeout); if (ZVAL_IS_EMPTY_STRING(return_value)) { close(); return; } if (sw_unlikely(ZVAL_IS_FALSE(return_value))) { php_swoole_socket_set_error_properties(zobject, socket); zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); if (socket->errCode != ETIMEDOUT) { close(); } return; } if (sw_unlikely(ZVAL_IS_NULL(return_value))) { ZVAL_FALSE(return_value); } } bool Client::upgrade(const std::string &_path) { defer = false; char buf[SW_WEBSOCKET_KEY_LENGTH + 1]; zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("requestHeaders"), 0); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("requestMethod"), "GET"); create_token(SW_WEBSOCKET_KEY_LENGTH, buf); add_assoc_string(zheaders, "Connection", "Upgrade"); add_assoc_string(zheaders, "Upgrade", "websocket"); add_assoc_string(zheaders, "Sec-WebSocket-Version", SW_WEBSOCKET_VERSION); add_assoc_str_ex( zheaders, ZEND_STRL("Sec-WebSocket-Key"), php_base64_encode((const uchar *) buf, SW_WEBSOCKET_KEY_LENGTH)); #ifdef SW_HAVE_ZLIB if (websocket_settings.compression) { add_assoc_string(zheaders, "Sec-Websocket-Extensions", SW_WEBSOCKET_EXTENSION_DEFLATE); } #endif return exec(_path); } bool Client::push(zval *zdata, zend_long opcode, uint8_t flags, zend_long code) { if (sw_unlikely(!websocket)) { swoole_set_last_error(SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED); php_swoole_fatal_error(E_WARNING, "websocket handshake failed, cannot push data"); zend_update_property_long( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "websocket handshake failed, cannot push data"); zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_CONNECT_FAILED); return false; } String *buffer = socket->get_write_buffer(); FrameObject frame(zdata, opcode, flags, code); if (websocket_settings.mask) { frame.flags |= WebSocket::FLAG_MASK; } if (accept_websocket_compression) { sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); } if (sw_unlikely(!frame.pack(buffer))) { swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); zend_update_property_long( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); zend_update_property_string( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "websocket frame pack failed"); return false; } if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { php_swoole_socket_set_error_properties(zobject, socket); zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); close(); return false; } else { return true; } } void Client::reset() { wait_response = false; completed = false; event_stream = false; #ifdef SW_HAVE_COMPRESSION compress_method = HTTP_COMPRESS_NONE; compression_error = false; #endif #ifdef SW_HAVE_ZLIB if (gzip_stream_active) { inflateEnd(&gzip_stream); gzip_stream_active = false; } #endif #ifdef SW_HAVE_BROTLI if (brotli_decoder_state) { BrotliDecoderDestroyInstance(brotli_decoder_state); brotli_decoder_state = nullptr; } #endif #ifdef SW_HAVE_ZSTD if (zstd_stream) { ZSTD_freeDStream(zstd_stream); zstd_stream = nullptr; } #endif if (has_upload_files) { zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("uploadFiles")); } if (download_file != nullptr) { download_file.reset(); download_file_name.release(); download_offset = 0; zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("downloadFile")); zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("downloadOffset"), 0); } } void Client::socket_dtor() { delete tmp_write_buffer; tmp_write_buffer = socket->pop_write_buffer(); socket = nullptr; zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); zend_update_property_null(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket")); zval_ptr_dtor(&zsocket); ZVAL_NULL(&zsocket); } /** * The socket member variables cannot be read after Socket::close(), * MUST return to the php layer, otherwise a memory error will occur. * The client, mysql client, http2 client also need to follow this coding convention. */ bool Client::close(const bool should_be_reset) { SocketImpl *_socket = socket; if (!_socket) { return false; } if (in_callback) { _socket->get_socket()->close_wait = 1; return true; } zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); if (!_socket->close()) { php_swoole_socket_set_error_properties(zobject, _socket); return false; } if (should_be_reset) { reset(); } return true; } Client::~Client() { close(); delete body; delete tmp_write_buffer; delete write_func; } static sw_inline HttpClientObject *http_client_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http_client_coro_handlers.offset); } static sw_inline Client *http_client_coro_get_client(const zval *zobject) { Client *phc = http_client_coro_fetch_object(Z_OBJ_P(zobject))->client; if (UNEXPECTED(!phc)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return phc; } static void http_client_coro_free_object(zend_object *object) { HttpClientObject *hcc = http_client_coro_fetch_object(object); if (hcc->client) { delete hcc->client; hcc->client = nullptr; } zend_object_std_dtor(&hcc->std); } static zend_object *http_client_coro_create_object(zend_class_entry *ce) { auto *hcc = (HttpClientObject *) zend_object_alloc(sizeof(HttpClientObject), ce); zend_object_std_init(&hcc->std, ce); object_properties_init(&hcc->std, ce); hcc->std.handlers = &swoole_http_client_coro_handlers; return &hcc->std; } void php_swoole_http_client_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_http_client_coro, "Swoole\\Coroutine\\Http\\Client", "Co\\Http\\Client", swoole_http_client_coro_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_client_coro); SW_SET_CLASS_CLONEABLE(swoole_http_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_client_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_http_client_coro, http_client_coro_create_object, http_client_coro_free_object, HttpClientObject, std); zend_add_parameter_attribute( (zend_function *) zend_hash_str_find_ptr(&swoole_http_client_coro_ce->function_table, SW_STRL("setbasicauth")), 1, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); // client status zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); // client info zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); // request properties zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestMethod"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestHeaders"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestBody"), ZEND_ACC_PUBLIC); // always set by API (make it private?) zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("uploadFiles"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("downloadFile"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("downloadOffset"), 0, ZEND_ACC_PUBLIC); // response properties zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("body"), "", ZEND_ACC_PUBLIC); SW_INIT_CLASS_ENTRY_EX(swoole_http_client_coro_exception, "Swoole\\Coroutine\\Http\\Client\\Exception", "Co\\Http\\Client\\Exception", nullptr, swoole_exception); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED", HTTP_ESTATUS_CONNECT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT", HTTP_ESTATUS_REQUEST_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET", HTTP_ESTATUS_SERVER_RESET); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SEND_FAILED", HTTP_ESTATUS_SEND_FAILED); } static PHP_METHOD(swoole_http_client_coro, __construct) { HttpClientObject *hcc = http_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); char *host; size_t host_len; zend_long port = 0; zend_bool ssl = false; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 3) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_BOOL(ssl) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property_stringl(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, host_len); zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), port); zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("ssl"), ssl); // check host if (host_len == 0) { zend_throw_exception_ex(swoole_http_client_coro_exception_ce, EINVAL, "host is empty"); RETURN_FALSE; } hcc->client = new Client(ZEND_THIS, std::string(host, host_len), port, ssl); } static PHP_METHOD(swoole_http_client_coro, __destruct) {} static PHP_METHOD(swoole_http_client_coro, set) { Client *phc = http_client_coro_get_client(ZEND_THIS); zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_array_length(zset) == 0) { RETURN_FALSE; } else { zval *zsettings = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsettings), Z_ARRVAL_P(zset)); phc->apply_setting(zset); RETURN_TRUE; } } static PHP_METHOD(swoole_http_client_coro, getDefer) { Client *phc = http_client_coro_get_client(ZEND_THIS); RETURN_BOOL(phc->defer); } static PHP_METHOD(swoole_http_client_coro, setDefer) { Client *phc = http_client_coro_get_client(ZEND_THIS); zend_bool defer = true; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(defer) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); phc->defer = defer; RETURN_TRUE; } static PHP_METHOD(swoole_http_client_coro, setMethod) { char *method; size_t method_length; // Notice: maybe string or array ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(method, method_length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property_stringl( swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), method, method_length); RETURN_TRUE; } static PHP_METHOD(swoole_http_client_coro, setHeaders) { zval *headers; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_EX(headers, 0, 1) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestHeaders"), headers); RETURN_TRUE; } static PHP_METHOD(swoole_http_client_coro, setBasicAuth) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *username, *password; size_t username_len, password_len; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(username, username_len) Z_PARAM_STRING(password, password_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); phc->set_basic_auth(std::string(username, username_len), std::string(password, password_len)); } static PHP_METHOD(swoole_http_client_coro, setCookies) { zval *cookies; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_EX(cookies, 0, 1) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("cookies"), cookies); RETURN_TRUE; } static PHP_METHOD(swoole_http_client_coro, setData) { zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestBody"), zdata); RETURN_TRUE; } static PHP_METHOD(swoole_http_client_coro, addFile) { char *path; size_t l_path; char *name; size_t l_name; char *type = nullptr; size_t l_type = 0; char *filename = nullptr; size_t l_filename = 0; zend_long offset = 0; zend_long length = 0; ZEND_PARSE_PARAMETERS_START(2, 6) Z_PARAM_STRING(path, l_path) Z_PARAM_STRING(name, l_name) Z_PARAM_OPTIONAL Z_PARAM_STRING(type, l_type) Z_PARAM_STRING(filename, l_filename) Z_PARAM_LONG(offset) Z_PARAM_LONG(length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (offset < 0) { offset = 0; } if (length < 0) { length = 0; } struct stat file_stat; if (stat(path, &file_stat) < 0) { php_swoole_sys_error(E_WARNING, "stat(%s) failed", path); RETURN_FALSE; } if (file_stat.st_size == 0) { php_swoole_sys_error(E_WARNING, "cannot send empty file[%s]", filename); RETURN_FALSE; } if (file_stat.st_size <= offset) { php_swoole_error(E_WARNING, "parameter $offset[" ZEND_LONG_FMT "] exceeds the file size", offset); RETURN_FALSE; } if (length > file_stat.st_size - offset) { php_swoole_sys_error(E_WARNING, "parameter $length[" ZEND_LONG_FMT "] exceeds the file size", length); RETURN_FALSE; } if (length == 0) { length = file_stat.st_size - offset; } if (l_type == 0) { type = (char *) swoole::mime_type::get(path).c_str(); l_type = strlen(type); } if (l_filename == 0) { char *dot = strrchr(path, '/'); if (dot == nullptr) { filename = path; l_filename = l_path; } else { filename = dot + 1; l_filename = strlen(filename); } } zval *zupload_files = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("uploadFiles"), 0); zval zupload_file; array_init(&zupload_file); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("path"), path, l_path); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("name"), name, l_name); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("filename"), filename, l_filename); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("type"), type, l_type); add_assoc_long(&zupload_file, "size", length); add_assoc_long(&zupload_file, "offset", offset); RETURN_BOOL(add_next_index_zval(zupload_files, &zupload_file) == SUCCESS); } static PHP_METHOD(swoole_http_client_coro, addData) { char *data; size_t l_data; char *name; size_t l_name; char *type = nullptr; size_t l_type = 0; char *filename = nullptr; size_t l_filename = 0; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(data, l_data) Z_PARAM_STRING(name, l_name) Z_PARAM_OPTIONAL Z_PARAM_STRING(type, l_type) Z_PARAM_STRING(filename, l_filename) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (l_type == 0) { type = (char *) "application/octet-stream"; l_type = strlen(type); } if (l_filename == 0) { filename = name; l_filename = l_name; } zval *zupload_files = sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("uploadFiles"), 0); zval zupload_file; array_init(&zupload_file); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("content"), data, l_data); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("name"), name, l_name); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("filename"), filename, l_filename); add_assoc_stringl_ex(&zupload_file, ZEND_STRL("type"), type, l_type); add_assoc_long(&zupload_file, "size", l_data); RETURN_BOOL(add_next_index_zval(zupload_files, &zupload_file) == SUCCESS); } static PHP_METHOD(swoole_http_client_coro, execute) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(path, path_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(phc->exec(std::string(path, path_len))); } static PHP_METHOD(swoole_http_client_coro, get) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(path, path_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), "GET"); RETURN_BOOL(phc->exec(std::string(path, path_len))); } static PHP_METHOD(swoole_http_client_coro, post) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; zval *post_data; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(path, path_len) Z_PARAM_ZVAL(post_data) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), "POST"); zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestBody"), post_data); RETURN_BOOL(phc->exec(std::string(path, path_len))); } static PHP_METHOD(swoole_http_client_coro, download) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *path; size_t path_len; zval *download_file; zend_long offset = 0; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STRING(path, path_len) Z_PARAM_ZVAL(download_file) Z_PARAM_OPTIONAL Z_PARAM_LONG(offset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("downloadFile"), download_file); zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("downloadOffset"), offset); RETURN_BOOL(phc->exec(std::string(path, path_len))); } static PHP_METHOD(swoole_http_client_coro, upgrade) { Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(path, path_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(phc->upgrade(std::string(path, path_len))); } static PHP_METHOD(swoole_http_client_coro, push) { Client *phc = http_client_coro_get_client(ZEND_THIS); if (!phc->is_available()) { RETURN_FALSE; } zval *zdata; zend_long opcode = WebSocket::OPCODE_TEXT; zval *zflags = nullptr; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(opcode) Z_PARAM_ZVAL_EX(zflags, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zflags != nullptr) { flags = zval_get_long(zflags); } SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); RETURN_BOOL(phc->push(zdata, opcode, flags & WebSocket::FLAGS_ALL)); } static PHP_METHOD(swoole_http_client_coro, recv) { Client *phc = http_client_coro_get_client(ZEND_THIS); if (!phc->is_available()) { RETURN_FALSE; } double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); if (phc->websocket) { phc->recv_websocket_frame(return_value, timeout); } else { RETURN_BOOL(phc->recv_response(timeout)); } } static PHP_METHOD(swoole_http_client_coro, close) { Client *phc = http_client_coro_get_client(ZEND_THIS); SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); RETURN_BOOL(phc->close()); } static PHP_METHOD(swoole_http_client_coro, getBody) { SW_RETURN_PROPERTY("body"); } static PHP_METHOD(swoole_http_client_coro, getHeaders) { SW_RETURN_PROPERTY("headers"); } static PHP_METHOD(swoole_http_client_coro, getCookies) { SW_RETURN_PROPERTY("cookies"); } static PHP_METHOD(swoole_http_client_coro, getStatusCode) { SW_RETURN_PROPERTY("statusCode"); } static PHP_METHOD(swoole_http_client_coro, getHeaderOut) { Client *phc = http_client_coro_get_client(ZEND_THIS); phc->get_header_out(return_value); } static PHP_METHOD(swoole_http_client_coro, getsockname) { Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getsockname(return_value); } static PHP_METHOD(swoole_http_client_coro, getpeername) { Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getpeername(return_value); } static PHP_METHOD(swoole_http_client_coro, getPeerCert) { Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getpeercert(return_value); } static PHP_METHOD(swoole_http_client_coro, ping) { Client *phc = http_client_coro_get_client(ZEND_THIS); if (!phc->is_available() || !phc->websocket) { RETURN_FALSE; } zend_string *zdata = zend_empty_string; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_STR(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval zpayload = {}; ZVAL_STR(&zpayload, zdata); RETURN_BOOL(phc->push(&zpayload, WebSocket::OPCODE_PING, WebSocket::FLAG_FIN)); } static PHP_METHOD(swoole_http_client_coro, disconnect) { Client *phc = http_client_coro_get_client(ZEND_THIS); if (!phc->is_available() || !phc->websocket) { RETURN_FALSE; } zend_long code = WebSocket::CLOSE_NORMAL; zend_string *reason = zend_empty_string; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(code) Z_PARAM_STR(reason) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval zpayload = {}; ZVAL_STR(&zpayload, reason); RETVAL_BOOL(phc->push(&zpayload, WebSocket::OPCODE_CLOSE, WebSocket::FLAG_FIN, code)); phc->close(); // Regardless of the outcome, the connection will be closed. } ================================================ FILE: ext-src/swoole_http_cookie.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_http_cookie_arginfo.h" END_EXTERN_C() using HttpCookie = swoole::http::Cookie; #define ILLEGAL_COOKIE_CHARACTER_PRINT "\",\", \";\", \" \", \"\\t\", \"\\r\", \"\\n\", \"\\013\", or \"\\014\"" #define ILLEGAL_COOKIE_CHARACTER ",; \t\r\n\013\014" static constexpr zend_long maxValidSeconds = 253402300800; zend_class_entry *swoole_http_cookie_ce; static zend_object_handlers swoole_http_cookie_handlers; struct HttpCookieObject { HttpCookie *cookie; zend_object std; }; static sw_inline HttpCookieObject *php_swoole_http_cookie_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http_cookie_handlers.offset); } static HttpCookie *php_swoole_http_get_cookie(const zval *zobject) { return php_swoole_http_cookie_fetch_object(Z_OBJ_P(zobject))->cookie; } HttpCookie *php_swoole_http_get_cooke_safety(const zval *zobject) { HttpCookie *cookie = php_swoole_http_get_cookie(zobject); if (!cookie) { swoole_set_last_error(SW_ERROR_HTTP_COOKIE_UNAVAILABLE); return nullptr; } return cookie; } void php_swoole_http_response_set_cookie(const zval *zobject, HttpCookie *cookie) { php_swoole_http_cookie_fetch_object(Z_OBJ_P(zobject))->cookie = cookie; } static zend_object *php_swoole_http_cookie_create_object(zend_class_entry *ce) { auto *httpCookieObject = static_cast(zend_object_alloc(sizeof(HttpCookieObject), ce)); zend_object_std_init(&httpCookieObject->std, ce); object_properties_init(&httpCookieObject->std, ce); httpCookieObject->std.handlers = &swoole_http_cookie_handlers; return &httpCookieObject->std; } static void php_swoole_http_cookie_free_object(zend_object *object) { auto *httpCookieObject = php_swoole_http_cookie_fetch_object(object); delete httpCookieObject->cookie; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http_cookie, __construct); static PHP_METHOD(swoole_http_cookie, withName); static PHP_METHOD(swoole_http_cookie, withValue); static PHP_METHOD(swoole_http_cookie, withExpires); static PHP_METHOD(swoole_http_cookie, withPath); static PHP_METHOD(swoole_http_cookie, withDomain); static PHP_METHOD(swoole_http_cookie, withSecure); static PHP_METHOD(swoole_http_cookie, withHttpOnly); static PHP_METHOD(swoole_http_cookie, withSameSite); static PHP_METHOD(swoole_http_cookie, withPriority); static PHP_METHOD(swoole_http_cookie, withPartitioned); static PHP_METHOD(swoole_http_cookie, toArray); static PHP_METHOD(swoole_http_cookie, toString); static PHP_METHOD(swoole_http_cookie, reset); SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_http_cookie_methods[] = { PHP_ME(swoole_http_cookie, __construct, arginfo_class_Swoole_Http_Cookie___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withName, arginfo_class_Swoole_Http_Cookie_withName, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withValue, arginfo_class_Swoole_Http_Cookie_withValue, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withExpires, arginfo_class_Swoole_Http_Cookie_withExpires, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withPath, arginfo_class_Swoole_Http_Cookie_withPath, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withDomain, arginfo_class_Swoole_Http_Cookie_withDomain, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withSecure, arginfo_class_Swoole_Http_Cookie_withSecure, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withHttpOnly, arginfo_class_Swoole_Http_Cookie_withHttpOnly, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withSameSite, arginfo_class_Swoole_Http_Cookie_withSameSite, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withPriority, arginfo_class_Swoole_Http_Cookie_withPriority, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, withPartitioned, arginfo_class_Swoole_Http_Cookie_withPartitioned, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, toString, arginfo_class_Swoole_Http_Cookie_toString, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, toArray, arginfo_class_Swoole_Http_Cookie_toArray, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_cookie, reset, arginfo_class_Swoole_Http_Cookie_reset, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_http_cookie_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_http_cookie, "Swoole\\Http\\Cookie", nullptr, swoole_http_cookie_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_cookie); SW_SET_CLASS_CLONEABLE(swoole_http_cookie, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_cookie, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_cookie, php_swoole_http_cookie_create_object, php_swoole_http_cookie_free_object, HttpCookieObject, std); } #define HTTP_COOKIE_WITH_STR(field) \ if (field) { \ zend_string_release(field); \ } \ if (_##field && ZSTR_LEN(_##field) > 0) { \ zend_string_addref(_##field); \ field = _##field; \ } else { \ field = nullptr; \ } \ return this; HttpCookie *HttpCookie::withName(zend_string *_name) { HTTP_COOKIE_WITH_STR(name); } HttpCookie *HttpCookie::withValue(zend_string *_value) { HTTP_COOKIE_WITH_STR(value); } HttpCookie *HttpCookie::withDomain(zend_string *_domain) { HTTP_COOKIE_WITH_STR(domain); } HttpCookie *HttpCookie::withPath(zend_string *_path) { HTTP_COOKIE_WITH_STR(path); } HttpCookie *HttpCookie::withSameSite(zend_string *_sameSite) { HTTP_COOKIE_WITH_STR(sameSite); } HttpCookie *HttpCookie::withPriority(zend_string *_priority) { HTTP_COOKIE_WITH_STR(priority); } HttpCookie *HttpCookie::withExpires(zend_long _expires) { expires = _expires; return this; } HttpCookie *HttpCookie::withSecure(zend_bool _secure) { secure = _secure; return this; } HttpCookie *HttpCookie::withHttpOnly(zend_bool _httpOnly) { httpOnly = _httpOnly; return this; } HttpCookie *HttpCookie::withPartitioned(zend_bool _partitioned) { partitioned = _partitioned; return this; } zend_string *HttpCookie::toString() { zend_string *date = nullptr; if (name == nullptr || ZSTR_LEN(name) == 0) { php_swoole_error(E_WARNING, "The name cannot be empty"); return nullptr; } if (strpbrk(ZSTR_VAL(name), "=" ILLEGAL_COOKIE_CHARACTER) != nullptr) { php_swoole_error(E_WARNING, "The name cannot contain \"=\", " ILLEGAL_COOKIE_CHARACTER_PRINT); return nullptr; } smart_str_append(&buffer_, name); if (!value) { smart_str_appends(&buffer_, "=deleted; expires="); date = php_format_date(ZEND_STRL("D, d-M-Y H:i:s T"), 1, 0); smart_str_append(&buffer_, date); smart_str_appends(&buffer_, "; Max-Age=0"); zend_string_free(date); } else { if (!encode_ && strpbrk(ZSTR_VAL(value), ILLEGAL_COOKIE_CHARACTER) != nullptr) { php_swoole_error(E_WARNING, "The value cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); return nullptr; } smart_str_appendc(&buffer_, '='); if (encode_) { zend_string *encoded_value = php_url_encode(ZSTR_VAL(value), ZSTR_LEN(value)); smart_str_append(&buffer_, encoded_value); zend_string_free(encoded_value); } else { smart_str_append(&buffer_, value); } if (expires > 0) { if (expires >= maxValidSeconds) { php_swoole_error(E_WARNING, "The expires cannot have a year greater than 9999"); return nullptr; } smart_str_appends(&buffer_, "; expires="); date = php_format_date(ZEND_STRL("D, d-M-Y H:i:s T"), expires, 0); smart_str_append(&buffer_, date); smart_str_appends(&buffer_, "; Max-Age="); double diff = difftime(expires, php_time()); smart_str_append_long(&buffer_, (zend_long) (diff >= 0 ? diff : 0)); zend_string_free(date); } } if (path && ZSTR_LEN(path) > 0) { if (strpbrk(ZSTR_VAL(path), ILLEGAL_COOKIE_CHARACTER) != nullptr) { php_swoole_error(E_WARNING, "The path option cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); return nullptr; } smart_str_appends(&buffer_, "; path="); smart_str_append(&buffer_, path); } if (domain && ZSTR_LEN(domain) > 0) { if (strpbrk(ZSTR_VAL(domain), ILLEGAL_COOKIE_CHARACTER) != nullptr) { php_swoole_error(E_WARNING, "The domain option cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); return nullptr; } smart_str_appends(&buffer_, "; domain="); smart_str_append(&buffer_, domain); } if (secure) { smart_str_appends(&buffer_, "; secure"); } if (httpOnly) { smart_str_appends(&buffer_, "; HttpOnly"); } if (sameSite && ZSTR_LEN(sameSite) > 0) { smart_str_appends(&buffer_, "; SameSite="); smart_str_append(&buffer_, sameSite); } if (priority && ZSTR_LEN(priority) > 0) { smart_str_appends(&buffer_, "; Priority="); smart_str_append(&buffer_, priority); } if (partitioned) { smart_str_appends(&buffer_, "; Partitioned"); } return smart_str_extract(&buffer_); } void HttpCookie::reset() { expires = 0; secure = false; httpOnly = false; partitioned = false; encode_ = true; if (name) { zend_string_release(name); name = nullptr; } if (value) { zend_string_release(value); value = nullptr; } if (path) { zend_string_release(path); path = nullptr; } if (domain) { zend_string_release(domain); domain = nullptr; } if (sameSite) { zend_string_release(sameSite); sameSite = nullptr; } if (priority) { zend_string_release(priority); priority = nullptr; } smart_str_free_ex(&buffer_, false); } #define HTTP_COOKIE_ADD_STR_TO_ARRAY(field) \ if (field) { \ add_assoc_str(return_value, #field, field); \ } else { \ add_assoc_string(return_value, #field, ""); \ } void HttpCookie::toArray(zval *return_value) const { array_init(return_value); HTTP_COOKIE_ADD_STR_TO_ARRAY(name); HTTP_COOKIE_ADD_STR_TO_ARRAY(value); HTTP_COOKIE_ADD_STR_TO_ARRAY(path); HTTP_COOKIE_ADD_STR_TO_ARRAY(domain); HTTP_COOKIE_ADD_STR_TO_ARRAY(sameSite); HTTP_COOKIE_ADD_STR_TO_ARRAY(priority); add_assoc_bool(return_value, "encode", encode_); add_assoc_long(return_value, "expires", expires); add_assoc_bool(return_value, "secure", secure); add_assoc_bool(return_value, "httpOnly", httpOnly); add_assoc_bool(return_value, "partitioned", partitioned); } HttpCookie::~Cookie() { reset(); } static PHP_METHOD(swoole_http_cookie, __construct) { zend_bool encode = true; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(encode) ZEND_PARSE_PARAMETERS_END(); php_swoole_http_response_set_cookie(ZEND_THIS, new HttpCookie(encode)); } #define PHP_METHOD_HTTP_COOKIE_WITH_STR(field) \ zend_string *field; \ HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); \ \ ZEND_PARSE_PARAMETERS_START(1, 1) \ Z_PARAM_STR(field) \ ZEND_PARSE_PARAMETERS_END(); \ \ cookie->with##field(field); \ RETURN_ZVAL(ZEND_THIS, 1, 0); #define PHP_METHOD_HTTP_COOKIE_WITH_BOOL(field) \ zend_bool field = false; \ HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); \ \ ZEND_PARSE_PARAMETERS_START(0, 1) \ Z_PARAM_OPTIONAL \ Z_PARAM_BOOL(field) \ ZEND_PARSE_PARAMETERS_END(); \ \ cookie->with##field(field); \ RETURN_ZVAL(ZEND_THIS, 1, 0); static PHP_METHOD(swoole_http_cookie, withName) { PHP_METHOD_HTTP_COOKIE_WITH_STR(Name); } static PHP_METHOD(swoole_http_cookie, withValue) { PHP_METHOD_HTTP_COOKIE_WITH_STR(Value); } static PHP_METHOD(swoole_http_cookie, withExpires) { zend_long expires = 0; HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(expires) ZEND_PARSE_PARAMETERS_END(); cookie->withExpires(expires); RETURN_ZVAL(ZEND_THIS, 1, 0); } static PHP_METHOD(swoole_http_cookie, withPath) { PHP_METHOD_HTTP_COOKIE_WITH_STR(Path); } static PHP_METHOD(swoole_http_cookie, withDomain) { PHP_METHOD_HTTP_COOKIE_WITH_STR(Domain); } static PHP_METHOD(swoole_http_cookie, withSecure) { PHP_METHOD_HTTP_COOKIE_WITH_BOOL(Secure); } static PHP_METHOD(swoole_http_cookie, withHttpOnly) { PHP_METHOD_HTTP_COOKIE_WITH_BOOL(HttpOnly); } static PHP_METHOD(swoole_http_cookie, withSameSite) { PHP_METHOD_HTTP_COOKIE_WITH_STR(SameSite); } static PHP_METHOD(swoole_http_cookie, withPriority) { PHP_METHOD_HTTP_COOKIE_WITH_STR(Priority); } static PHP_METHOD(swoole_http_cookie, withPartitioned) { PHP_METHOD_HTTP_COOKIE_WITH_BOOL(Partitioned); } static PHP_METHOD(swoole_http_cookie, toString) { auto cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); auto cookie_str = cookie->toString(); if (!cookie_str) { cookie->reset(); RETURN_FALSE; } ZVAL_STR(return_value, cookie_str); } static PHP_METHOD(swoole_http_cookie, toArray) { php_swoole_http_get_cooke_safety(ZEND_THIS)->toArray(return_value); } static PHP_METHOD(swoole_http_cookie, reset) { php_swoole_http_get_cooke_safety(ZEND_THIS)->reset(); } ================================================ FILE: ext-src/swoole_http_request.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" SW_EXTERN_C_BEGIN #include "ext/standard/url.h" #include "stubs/php_swoole_http_request_arginfo.h" #include "thirdparty/php/main/SAPI.h" SW_EXTERN_C_END enum http_upload_errno { HTTP_UPLOAD_ERR_OK = 0, HTTP_UPLOAD_ERR_INI_SIZE, HTTP_UPLOAD_ERR_FORM_SIZE, HTTP_UPLOAD_ERR_PARTIAL, HTTP_UPLOAD_ERR_NO_FILE, HTTP_UPLOAD_ERR_NO_TMP_DIR = 6, HTTP_UPLOAD_ERR_CANT_WRITE, HTTP_UPLOAD_ERR_FILE_READY, }; using HttpRequest = swoole::http::Request; using HttpContext = swoole::http::Context; using swoole::Connection; using swoole::ListenPort; using swoole::microtime; using swoole::Server; using swoole::http_server::ParseCookieCallback; static int http_request_on_url(llhttp_t *parser, const char *at, size_t length); static int http_request_on_body(llhttp_t *parser, const char *at, size_t length); static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length); static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length); static int http_request_on_headers_complete(llhttp_t *parser); static int http_request_message_complete(llhttp_t *parser); static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_complete(multipart_parser *p); static int multipart_body_on_data_end(multipart_parser *p); static HttpContext *http_request_get_and_check_context(const zval *zobject) { auto *ctx = php_swoole_http_request_get_context(zobject); if (!ctx) { swoole_set_last_error(SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); } return ctx; } static inline char *http_trim_double_quote(char *ptr, size_t *len) { size_t i; char *tmp = ptr; // ltrim('"') for (i = 0; i < *len; i++) { if (tmp[0] == '"') { (*len)--; tmp++; continue; } else { break; } } // rtrim('"') for (i = (*len); i > 0; i--) { if (tmp[i - 1] == '"') { tmp[i - 1] = 0; (*len)--; continue; } else { break; } } return tmp; } // clang-format off static constexpr llhttp_settings_t http_parser_settings = { nullptr, // on_message_begin nullptr, // on_protocol http_request_on_url, // on_url nullptr, // on_status nullptr, // on_method nullptr, // on_version http_request_on_header_field, // on_header_field http_request_on_header_value, // on_header_value nullptr, // on_chunk_extension_name nullptr, // on_chunk_extension_value http_request_on_headers_complete, // on_headers_complete http_request_on_body, // on_body http_request_message_complete, // on_message_complete nullptr, // on_protocol_complete nullptr, // on_url_complete nullptr, // on_status_complete nullptr, // on_method_complete nullptr, // on_version_complete nullptr, // on_header_field_complete nullptr, // on_header_value_complete nullptr, // on_chunk_extension_name_complete nullptr, // on_chunk_extension_value_complete nullptr, // on_chunk_header nullptr, // on_chunk_complete nullptr, // on_reset }; static constexpr multipart_parser_settings mt_parser_settings = { multipart_body_on_header_field, multipart_body_on_header_value, multipart_body_on_data, nullptr, multipart_body_on_header_complete, multipart_body_on_data_end, nullptr, }; // clang-format on size_t HttpContext::parse(const char *data, size_t length) { return swoole_llhttp_parser_execute(&parser, &http_parser_settings, data, length); } bool HttpContext::parse_multipart_data(const char *at, size_t length) const { ssize_t n = multipart_parser_execute(mt_parser, at, length); if (n < 0) { int l_error = multipart_parser_error_msg(mt_parser, sw_tg_buffer()->str, sw_tg_buffer()->size); swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, reason: %.*s", l_error, sw_tg_buffer()->str); return false; } else if (n != (ssize_t) length) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %lu/%zu bytes processed", n, length); return false; } return true; } zend_class_entry *swoole_http_request_ce; static zend_object_handlers swoole_http_request_handlers; struct HttpRequestObject { HttpContext *ctx; zend_object std; }; static sw_inline HttpRequestObject *http_request_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http_request_handlers.offset); } HttpContext *php_swoole_http_request_get_context(const zval *zobject) { return http_request_fetch_object(Z_OBJ_P(zobject))->ctx; } void php_swoole_http_request_set_context(const zval *zobject, HttpContext *ctx) { http_request_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; } static void http_request_free_object(zend_object *object) { auto *request = http_request_fetch_object(object); auto *ctx = request->ctx; if (ctx) { zval *ztmpfiles = ctx->request.ztmpfiles; if (ztmpfiles && ZVAL_IS_ARRAY(ztmpfiles)) { zval *z_file_path; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmpfiles), z_file_path) { if (Z_TYPE_P(z_file_path) != IS_STRING) { continue; } unlink(Z_STRVAL_P(z_file_path)); if (SG(rfc1867_uploaded_files)) { zend_hash_str_del(SG(rfc1867_uploaded_files), Z_STRVAL_P(z_file_path), Z_STRLEN_P(z_file_path)); } } SW_HASHTABLE_FOREACH_END(); } ctx->request.zobject = nullptr; ctx->free(); } zend_object_std_dtor(&request->std); } static zend_object *http_request_create_object(zend_class_entry *ce) { auto *request = static_cast(zend_object_alloc(sizeof(HttpRequestObject), ce)); zend_object_std_init(&request->std, ce); object_properties_init(&request->std, ce); request->std.handlers = &swoole_http_request_handlers; return &request->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http_request, getData); static PHP_METHOD(swoole_http_request, create); static PHP_METHOD(swoole_http_request, parse); static PHP_METHOD(swoole_http_request, isCompleted); static PHP_METHOD(swoole_http_request, getMethod); static PHP_METHOD(swoole_http_request, getContent); SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_http_request_methods[] = { PHP_ME(swoole_http_request, getContent, arginfo_class_Swoole_Http_Request_getContent, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_http_request, rawContent, getContent, arginfo_class_Swoole_Http_Request_getContent, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, getData, arginfo_class_Swoole_Http_Request_getData, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, create, arginfo_class_Swoole_Http_Request_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_http_request, parse, arginfo_class_Swoole_Http_Request_parse, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, isCompleted, arginfo_class_Swoole_Http_Request_isCompleted, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, getMethod, arginfo_class_Swoole_Http_Request_getMethod, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_http_request_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_http_request, "Swoole\\Http\\Request", nullptr, swoole_http_request_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_request); SW_SET_CLASS_CLONEABLE(swoole_http_request, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_request, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_http_request, http_request_create_object, http_request_free_object, HttpRequestObject, std); zend_declare_property_long(swoole_http_request_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_request_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("header"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("server"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("cookie"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("get"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("files"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("post"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("tmpfiles"), ZEND_ACC_PUBLIC); } static int http_request_on_url(llhttp_t *parser, const char *at, size_t length) { const char *query_start = (const char *) memchr(at, '?', length); size_t path_len = query_start ? (size_t) (query_start - at) : length; auto *ctx = static_cast(parser->data); ctx->request.path = estrndup(at, path_len); ctx->request.path_len = path_len; if (!query_start || (length - path_len) <= 1) { return 0; } const char *query_str = query_start + 1; size_t query_len = length - path_len - 1; zval tmp; HashTable *ht = Z_ARR_P(ctx->request.zserver); ZVAL_STRINGL(&tmp, (char *) query_str, query_len); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_QUERY_STRING), &tmp); // parse url params swoole_php_treat_data( PARSE_STRING, estrndup((char *) query_str, query_len), // it will be freed by treat_data swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, SW_ZSTR_KNOWN(SW_ZEND_STR_GET))); return 0; } static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length) { auto *ctx = static_cast(parser->data); ctx->current_header_name = at; ctx->current_header_name_len = length; return 0; } bool HttpContext::init_multipart_parser(const char *boundary_str, int boundary_len) { mt_parser = multipart_parser_init(boundary_str, boundary_len, &mt_parser_settings); if (!mt_parser) { php_swoole_fatal_error(E_WARNING, "multipart_parser_init() failed"); return false; } form_data_buffer = new String(SW_BUFFER_SIZE_STD); mt_parser->data = this; return true; } bool HttpContext::get_multipart_boundary( const char *at, size_t length, size_t offset, char **out_boundary_str, int *out_boundary_len) { if (!http_server::parse_multipart_boundary(at, length, offset, out_boundary_str, out_boundary_len)) { swoole_warning("boundary of multipart/form-data not found, fd:%ld", fd); /* make it same with protocol error */ parser.error = HPE_INVALID_HEADER_TOKEN; return false; } return true; } void swoole_http_parse_cookie(zval *zcookies, const char *at, size_t length) { if (length == 0) { return; } swoole_php_treat_data(PARSE_COOKIE, estrndup(at, length), zcookies); } static void http_request_add_upload_file(HttpContext *ctx, const char *file, size_t l_file) { zval *zfiles = swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.ztmpfiles, SW_ZSTR_KNOWN(SW_ZEND_STR_TMPFILES)); add_next_index_stringl(zfiles, file, l_file); // support is_upload_file zend_hash_str_add_ptr(SG(rfc1867_uploaded_files), file, l_file, (char *) file); } bool swoole_http_token_list_contains_value(const char *at, size_t length, const char *value) { if (0 == length) { return false; } if (SW_STRCASEEQ(at, length, value)) { return true; } char *var; const char *separator = ",\0"; char *strtok_buf = nullptr; size_t var_len; char *_c = sw_tg_buffer()->str; memcpy(_c, at, length); _c[length] = '\0'; var = php_strtok_r(_c, separator, &strtok_buf); while (var) { var_len = swoole::ltrim(&var, strlen(var)); var_len = swoole::rtrim(var, var_len); if (swoole_strcaseeq(var, var_len, value, strlen(value))) { return true; } var = php_strtok_r(nullptr, separator, &strtok_buf); } return false; } static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) { auto *ctx = static_cast(parser->data); zval *zheader = ctx->request.zheader; const char *header_name = ctx->current_header_name; size_t header_len = ctx->current_header_name_len; if (ctx->parse_cookie && SW_STRCASEEQ(header_name, header_len, "cookie")) { zval *zcookie = swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zcookie, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE)); swoole_http_parse_cookie(zcookie, at, length); return 0; } else if (SW_STRCASEEQ(header_name, header_len, "upgrade") && swoole_http_token_list_contains_value(at, length, "websocket")) { ctx->websocket = 1; if (ctx->is_co_socket()) { goto _add_header; } auto *serv = ctx->get_async_server(); Connection *conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_CLOSED, "session[%ld] is closed", ctx->fd); return -1; } ListenPort *port = serv->get_port_by_server_fd(conn->server_fd); if (port->open_websocket_protocol) { conn->websocket_status = swoole::websocket::STATUS_CONNECTION; } } else if ((parser->method == HTTP_POST || parser->method == HTTP_PUT || parser->method == HTTP_DELETE || parser->method == HTTP_PATCH) && SW_STRCASEEQ(header_name, header_len, "content-type")) { if (SW_STR_ISTARTS_WITH(at, length, "application/x-www-form-urlencoded")) { ctx->request.post_form_urlencoded = 1; } else if (SW_STR_ISTARTS_WITH(at, length, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; if (!ctx->get_multipart_boundary(at, length, offset, &boundary_str, &boundary_len)) { return -1; } swoole_trace_log(SW_TRACE_HTTP, "form_data, boundary_str=%s", boundary_str); ctx->init_multipart_parser(boundary_str, boundary_len); } } #ifdef SW_HAVE_COMPRESSION else if (ctx->enable_compression && SW_STRCASEEQ(header_name, header_len, "accept-encoding")) { ctx->set_compression_method(at, length); } #endif else if (SW_STRCASEEQ(header_name, header_len, "transfer-encoding") && SW_STR_ISTARTS_WITH(at, length, "chunked")) { ctx->recv_chunked = 1; } _add_header: zval tmp; ZVAL_STRINGL(&tmp, (char *) at, length); /** * some common request header key */ if (SW_STRCASEEQ(header_name, header_len, "host")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_HOST), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "user-agent")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_USER_AGENT), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "accept")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_ACCEPT), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "content-type")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONTENT_TYPE), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "content-length")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONTENT_LENGTH), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "authorization")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_AUTHORIZATION), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "connection")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONNECTION), &tmp); } else if (SW_STRCASEEQ(header_name, header_len, "accept-encoding")) { zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_ACCEPT_ENCODING), &tmp); } else { char *new_header_name = estrndup(header_name, header_len); zend_str_tolower_copy(new_header_name, header_name, header_len); zend::array_add_or_merge(zheader, new_header_name, header_len, &tmp); efree(new_header_name); } return 0; } static int http_request_on_headers_complete(llhttp_t *parser) { auto *ctx = static_cast(parser->data); const char *vpath = ctx->request.path, *end = vpath + ctx->request.path_len, *p = end; ctx->request.version = parser->http_major * 100 + parser->http_minor; ctx->request.ext = end; ctx->request.ext_len = 0; while (p > vpath) { --p; if (*p == '.') { ++p; ctx->request.ext = p; ctx->request.ext_len = end - p; break; } } HashTable *ht = Z_ARR_P(ctx->request.zserver); http_server_add_server_array( ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_METHOD2), llhttp_method_name((enum llhttp_method) parser->method)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_URI), ctx->request.path, ctx->request.path_len); // path_info should be decoded zend_string *zstr_path = zend_string_init(ctx->request.path, ctx->request.path_len, false); ZSTR_LEN(zstr_path) = php_url_decode(ZSTR_VAL(zstr_path), ZSTR_LEN(zstr_path)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_PATH_INFO), zstr_path); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); http_server_add_server_array( ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), (ctx->request.version == 101 ? SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP11) : SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP10))); ctx->keepalive = llhttp_should_keep_alive(parser); ctx->current_header_name = nullptr; return 0; } static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length) { auto *ctx = static_cast(p->data); return http_request_on_header_field(&ctx->parser, at, length); } static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length) { char value_buf[SW_HTTP_FORM_KEYLEN]; size_t value_len; int ret = 0; auto *ctx = static_cast(p->data); /** * Hash collision attack */ if (ctx->input_var_num > PG(max_input_vars)) { php_swoole_error(E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". " "To increase the limit change max_input_vars in php.ini", PG(max_input_vars)); return SW_OK; } else { ctx->input_var_num++; } size_t header_len = ctx->current_header_name_len; zend::CharPtr _header_name; _header_name.assign_tolower(ctx->current_header_name, header_len); char *header_name = _header_name.get(); if (SW_STRCASEEQ(header_name, header_len, "content-disposition")) { size_t offset = 0; if (swoole_strnpos(at, length, ZEND_STRL("form-data;")) >= 0) { offset += sizeof("form-data;") - 1; } else if (swoole_strnpos(at, length, ZEND_STRL("attachment;")) >= 0) { offset += sizeof("attachment;") - 1; } else { swoole_warning("Unsupported Content-Disposition [%.*s]", (int) length, at); return ret; } zval tmp_array; array_init(&tmp_array); swoole_http_parse_cookie(&tmp_array, at + offset, length - offset); zval *zform_name; if (!(zform_name = zend_hash_str_find(Z_ARRVAL(tmp_array), ZEND_STRL("name")))) { return ret; } if (Z_STRLEN_P(zform_name) >= SW_HTTP_FORM_KEYLEN) { swoole_warning("form_name[%s] is too large", Z_STRVAL_P(zform_name)); ret = -1; return ret; } swoole_strlcpy(value_buf, Z_STRVAL_P(zform_name), sizeof(value_buf)); value_len = Z_STRLEN_P(zform_name); char *tmp = http_trim_double_quote(value_buf, &value_len); zval *zfilename; // POST form data if (!(zfilename = zend_hash_str_find(Z_ARRVAL(tmp_array), ZEND_STRL("filename")))) { ctx->current_form_data_name = estrndup(tmp, value_len); ctx->current_form_data_name_len = value_len; } // upload file else { if (Z_STRLEN_P(zfilename) >= SW_HTTP_FORM_KEYLEN) { swoole_warning("filename[%s] is too large", Z_STRVAL_P(zfilename)); ret = -1; return ret; } ctx->current_input_name = estrndup(tmp, value_len); ctx->current_input_name_len = value_len; zval *z_multipart_header = sw_malloc_zval(); array_init(z_multipart_header); if (ctx->tmp_content_type) { add_assoc_stringl(z_multipart_header, "type", ctx->tmp_content_type, ctx->tmp_content_type_len); ctx->tmp_content_type = nullptr; } else { add_assoc_string(z_multipart_header, "type", (char *) ""); } add_assoc_string(z_multipart_header, "tmp_name", (char *) ""); add_assoc_long(z_multipart_header, "size", 0); swoole_strlcpy(value_buf, Z_STRVAL_P(zfilename), sizeof(value_buf)); value_len = Z_STRLEN_P(zfilename); tmp = http_trim_double_quote(value_buf, &value_len); add_assoc_stringl(z_multipart_header, "name", tmp, value_len); if (value_len == 0) { add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_NO_FILE); } else { add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_OK); } ctx->current_multipart_header = z_multipart_header; } zval_ptr_dtor(&tmp_array); } else if (SW_STRCASEEQ(header_name, header_len, "content-type")) { if (ctx->current_multipart_header) { zval *z_multipart_header = ctx->current_multipart_header; zval *zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); if (zerr && Z_TYPE_P(zerr) == IS_LONG && Z_LVAL_P(zerr) == HTTP_UPLOAD_ERR_OK) { add_assoc_stringl(z_multipart_header, "type", (char *) at, length); } } else { ctx->tmp_content_type = at; ctx->tmp_content_type_len = length; } } else if (SW_STRCASEEQ(header_name, header_len, SW_HTTP_UPLOAD_FILE)) { /** * When the "SW_HTTP_UPLOAD_FILE" header appears in the request, it indicates that the uploaded file has been * saved in a temporary file. The binary content in the message body will be replaced with the temporary * filename. However, the Content-Length still reflects the original message size, causing llhttp to believe * there is still data to be received. As a result, llhttp fails to trigger the message callback. Therefore, we * need to set `ctx->completed = 1` to indicate that the message processing is complete. */ ctx->completed = 1; zval *z_multipart_header = ctx->current_multipart_header; std::string tmp_file(at, length); add_assoc_stringl(z_multipart_header, "tmp_name", at, length); add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_FILE_READY); add_assoc_long(z_multipart_header, "size", swoole::file_get_size(tmp_file)); http_request_add_upload_file(ctx, tmp_file.c_str(), tmp_file.length()); } return ret; } static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length) { auto *ctx = static_cast(p->data); if (ctx->current_form_data_name) { ctx->form_data_buffer->append(at, length); return 0; } if (p->fp == nullptr) { return 0; } ssize_t n = fwrite(at, sizeof(char), length, p->fp); if (n != (off_t) length) { zval *z_multipart_header = ctx->current_multipart_header; add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_CANT_WRITE); fclose(p->fp); p->fp = nullptr; swoole_sys_warning("write upload file failed"); } return 0; } #if 0 static void get_random_file_name(char *des, const char *src) { unsigned char digest[16] = {}; char buf[19] = {}; int n = sprintf(buf, "%s%d", src, swoole_system_random(0, 9999)); PHP_MD5_CTX ctx; PHP_MD5Init(&ctx); PHP_MD5Update(&ctx, buf, n); PHP_MD5Final(digest, &ctx); make_digest_ex(des, digest, 16); } #endif static int multipart_body_on_header_complete(multipart_parser *p) { auto *ctx = static_cast(p->data); if (!ctx->current_input_name) { return 0; } zval *z_multipart_header = ctx->current_multipart_header; zval *zerr = nullptr; if (!(zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")))) { return 0; } if (Z_TYPE_P(zerr) == IS_LONG && Z_LVAL_P(zerr) != HTTP_UPLOAD_ERR_OK) { return 0; } char file_path[SW_HTTP_UPLOAD_TMPDIR_SIZE]; sw_snprintf(file_path, SW_HTTP_UPLOAD_TMPDIR_SIZE, "%s/swoole.upfile.XXXXXX", ctx->upload_tmp_dir.c_str()); int tmpfile = swoole_tmpfile(file_path); if (tmpfile < 0) { return 0; } FILE *fp = fdopen(tmpfile, "wb+"); if (fp == nullptr) { add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_NO_TMP_DIR); swoole_sys_warning("fopen(%s) failed", file_path); return 0; } p->fp = fp; add_assoc_string(z_multipart_header, "tmp_name", file_path); http_request_add_upload_file(ctx, file_path, strlen(file_path)); return 0; } static int multipart_body_on_data_end(multipart_parser *p) { auto *ctx = static_cast(p->data); if (ctx->current_form_data_name) { php_register_variable_safe( ctx->current_form_data_name, ctx->form_data_buffer->str, ctx->form_data_buffer->length, swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); efree(ctx->current_form_data_name); ctx->current_form_data_name = nullptr; ctx->current_form_data_name_len = 0; ctx->form_data_buffer->clear(); return 0; } if (!ctx->current_input_name) { return 0; } zval *z_multipart_header = ctx->current_multipart_header; if (p->fp != nullptr) { long size = swoole::file_get_size(p->fp); add_assoc_long(z_multipart_header, "size", size); fclose(p->fp); p->fp = nullptr; } zval *zerr; if (!(zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")))) { return 0; } if (zval_get_long(zerr) == HTTP_UPLOAD_ERR_FILE_READY) { add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_OK); } zval *zfiles = swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zfiles, SW_ZSTR_KNOWN(SW_ZEND_STR_FILES)); int input_path_pos = swoole_strnpos(ctx->current_input_name, ctx->current_input_name_len, ZEND_STRL("[")); if (ctx->parse_files && input_path_pos > 0) { char meta_name[SW_HTTP_FORM_KEYLEN + sizeof("[tmp_name]") - 1]; char *input_path = ctx->current_input_name + input_path_pos; char *meta_path = meta_name + input_path_pos; size_t meta_path_len = sizeof(meta_name) - input_path_pos; swoole_strlcpy(meta_name, ctx->current_input_name, sizeof(meta_name)); zval *zname = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("name")); zval *ztype = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("type")); zval *zfile = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("tmp_name")); zval *zerror = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); zval *zsize = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("size")); sw_snprintf(meta_path, meta_path_len, "[name]%s", input_path); php_register_variable_ex(meta_name, zname, zfiles); sw_snprintf(meta_path, meta_path_len, "[type]%s", input_path); php_register_variable_ex(meta_name, ztype, zfiles); sw_snprintf(meta_path, meta_path_len, "[tmp_name]%s", input_path); php_register_variable_ex(meta_name, zfile, zfiles); sw_snprintf(meta_path, meta_path_len, "[error]%s", input_path); php_register_variable_ex(meta_name, zerror, zfiles); sw_snprintf(meta_path, meta_path_len, "[size]%s", input_path); php_register_variable_ex(meta_name, zsize, zfiles); } else { php_register_variable_ex(ctx->current_input_name, z_multipart_header, zfiles); } efree(ctx->current_input_name); ctx->current_input_name = nullptr; ctx->current_input_name_len = 0; efree(ctx->current_multipart_header); ctx->current_multipart_header = nullptr; return 0; } static int http_request_on_body(llhttp_t *parser, const char *at, size_t length) { if (length == 0) { return 0; } auto *ctx = static_cast(parser->data); bool is_beginning = (ctx->request.chunked_body ? ctx->request.chunked_body->length : ctx->request.body_length) == 0; if (ctx->recv_chunked) { if (ctx->request.chunked_body == nullptr) { ctx->request.chunked_body = new swoole::String(SW_BUFFER_SIZE_STD); } ctx->request.chunked_body->append(at, length); } else { ctx->request.body_at = at - ctx->request.body_length; ctx->request.body_length += length; } if (ctx->mt_parser != nullptr) { if (is_beginning) { /* Compatibility: some clients may send extra EOL */ do { if (*at != '\r' && *at != '\n') { break; } at++; length--; } while (length != 0); } if (!ctx->parse_multipart_data(at, length)) { return -1; } } return ctx->completed ? HPE_PAUSED : 0; } static int http_request_message_complete(llhttp_t *parser) { auto *ctx = static_cast(parser->data); size_t content_length = ctx->request.chunked_body ? ctx->request.chunked_body->length : ctx->request.body_length; if (ctx->request.chunked_body != nullptr && ctx->parse_body && ctx->request.post_form_urlencoded) { /* parse dechunked content */ swoole_php_treat_data( PARSE_STRING, estrndup(ctx->request.chunked_body->str, content_length), // do not free, it will be freed by treat_data swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); } else if (!ctx->recv_chunked && ctx->parse_body && ctx->request.post_form_urlencoded && ctx->request.body_at) { swoole_php_treat_data( PARSE_STRING, estrndup(ctx->request.body_at, ctx->request.body_length), // do not free, it will be freed by treat_data swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); } if (ctx->mt_parser) { multipart_parser_free(ctx->mt_parser); ctx->mt_parser = nullptr; } if (ctx->form_data_buffer) { delete ctx->form_data_buffer; ctx->form_data_buffer = nullptr; } ctx->completed = 1; swoole_trace_log(SW_TRACE_HTTP, "request body length=%ld", content_length); /* The analysis of the http protocol has been completed, no further processing is needed. */ return HPE_PAUSED; } #ifdef SW_HAVE_COMPRESSION void HttpContext::set_compression_method(const char *accept_encoding, size_t length) { #ifdef SW_HAVE_BROTLI if (swoole_strnpos(accept_encoding, length, ZEND_STRL("br")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_BR; } else #endif #ifdef SW_HAVE_ZLIB if (swoole_strnpos(accept_encoding, length, ZEND_STRL("gzip")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_GZIP; } else if (swoole_strnpos(accept_encoding, length, ZEND_STRL("deflate")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_DEFLATE; } else #endif #ifdef SW_HAVE_ZSTD if (swoole_strnpos(accept_encoding, length, ZEND_STRL("zstd")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_ZSTD; } else #endif { accept_compression = 0; } } const char *HttpContext::get_content_encoding() const { #ifdef SW_HAVE_ZLIB if (compression_method == HTTP_COMPRESS_GZIP) { return "gzip"; } else if (compression_method == HTTP_COMPRESS_DEFLATE) { return "deflate"; } else #endif #ifdef SW_HAVE_BROTLI if (compression_method == HTTP_COMPRESS_BR) { return "br"; } else #endif #ifdef SW_HAVE_ZSTD if (compression_method == HTTP_COMPRESS_ZSTD) { return "zstd"; } else #endif { return nullptr; } } #endif static PHP_METHOD(swoole_http_request, getContent) { HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } HttpRequest *req = &ctx->request; if (req->body_length > 0) { zval *zdata = &req->zdata; RETURN_STRINGL(Z_STRVAL_P(zdata) + Z_STRLEN_P(zdata) - req->body_length, req->body_length); } else if (req->chunked_body && req->chunked_body->length != 0) { RETURN_STRINGL(req->chunked_body->str, req->chunked_body->length); } else if (ctx->get_http2_data_length() > 0) { RETURN_STRINGL(req->h2_data_buffer->str, req->h2_data_buffer->length); } RETURN_EMPTY_STRING(); } static PHP_METHOD(swoole_http_request, getData) { HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } if (ctx->http2) { php_swoole_fatal_error(E_WARNING, "unable to get data from HTTP2 request"); RETURN_FALSE; } if (Z_TYPE(ctx->request.zdata) == IS_STRING) { RETURN_ZVAL(&ctx->request.zdata, 1, 0); } RETURN_EMPTY_STRING(); } static PHP_METHOD(swoole_http_request, create) { zval *zoptions = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ARRAY(zoptions) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *ctx = new HttpContext(); object_init_ex(return_value, swoole_http_request_ce); zval *zrequest_object = &ctx->request._zobject; ctx->request.zobject = zrequest_object; *zrequest_object = *return_value; php_swoole_http_request_set_context(zrequest_object, ctx); ctx->parse_cookie = 1; ctx->parse_body = 1; ctx->parse_files = 1; #ifdef SW_HAVE_COMPRESSION ctx->enable_compression = 1; ctx->compression_level = SW_Z_BEST_SPEED; #endif ctx->upload_tmp_dir = "/tmp"; if (zoptions && ZVAL_IS_ARRAY(zoptions)) { char *key; uint32_t keylen; int keytype; zval *zvalue; SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zoptions), key, keylen, keytype, zvalue) { if (SW_STRCASEEQ(key, keylen, "parse_cookie")) { ctx->parse_cookie = zval_is_true(zvalue); } else if (SW_STRCASEEQ(key, keylen, "parse_body")) { ctx->parse_body = zval_is_true(zvalue); } else if (SW_STRCASEEQ(key, keylen, "parse_files")) { ctx->parse_files = zval_is_true(zvalue); } #ifdef SW_HAVE_COMPRESSION else if (SW_STRCASEEQ(key, keylen, "enable_compression")) { ctx->enable_compression = zval_is_true(zvalue); } else if (SW_STRCASEEQ(key, keylen, "compression_level")) { ctx->compression_level = zval_get_long(zvalue); } #endif #ifdef SW_HAVE_ZLIB else if (SW_STRCASEEQ(key, keylen, "websocket_compression")) { ctx->websocket_compression = zval_is_true(zvalue); } #endif else if (SW_STRCASEEQ(key, keylen, "upload_tmp_dir")) { ctx->upload_tmp_dir = zend::String(zvalue).to_std_string(); } (void) keytype; } SW_HASHTABLE_FOREACH_END(); } llhttp_t *parser = &ctx->parser; swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); swoole_http_init_and_read_property( swoole_http_request_ce, zrequest_object, &ctx->request.zserver, ZEND_STRL("server")); swoole_http_init_and_read_property( swoole_http_request_ce, zrequest_object, &ctx->request.zheader, ZEND_STRL("header")); } static PHP_METHOD(swoole_http_request, parse) { HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx) || ctx->completed) { RETURN_FALSE; } char *str; size_t l_str; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(str, l_str) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (Z_TYPE(ctx->request.zdata) != IS_STRING) { ZVAL_STRINGL(&ctx->request.zdata, str, l_str); } else { size_t len = Z_STRLEN(ctx->request.zdata) + l_str; zend_string *new_str = zend_string_alloc(len + 1, false); memcpy(new_str->val, Z_STRVAL(ctx->request.zdata), Z_STRLEN(ctx->request.zdata)); memcpy(new_str->val + Z_STRLEN(ctx->request.zdata), str, l_str); new_str->val[len] = 0; new_str->len = len; zval_dtor(&ctx->request.zdata); ZVAL_STR(&ctx->request.zdata, new_str); } RETURN_LONG(ctx->parse(str, l_str)); } static PHP_METHOD(swoole_http_request, getMethod) { HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } if (ctx->http2) { zval *zmethod = zend_hash_str_find(Z_ARR_P(ctx->request.zserver), ZEND_STRL("request_method")); RETURN_ZVAL(zmethod, 1, 0); } else { const char *method = llhttp_method_name((enum llhttp_method)(ctx->parser).method); RETURN_STRING(method); } } static PHP_METHOD(swoole_http_request, isCompleted) { HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } RETURN_BOOL(ctx->completed); } ================================================ FILE: ext-src/swoole_http_response.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" #include "php_swoole_websocket.h" #include "swoole_util.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_http_response_arginfo.h" END_EXTERN_C() using swoole::Connection; using swoole::Server; using swoole::String; using swoole::substr_len; using swoole::WebSocketSettings; using swoole::websocket::FrameObject; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; using HttpCookie = swoole::http::Cookie; namespace WebSocket = swoole::websocket; namespace HttpServer = swoole::http_server; zend_class_entry *swoole_http_response_ce; static zend_object_handlers swoole_http_response_handlers; static SW_THREAD_LOCAL struct { time_t time; zend_string *date = nullptr; } date_cache{}; static inline void http_response_header_key_format(char *key, int length) { int state = 0; for (int i = 0; i < length; i++) { if (state == 0) { if (key[i] >= 97 && key[i] <= 122) { key[i] -= 32; } state = 1; } else if (key[i] == '-') { state = 0; } else { if (key[i] >= 65 && key[i] <= 90) { key[i] += 32; } } } } String *HttpContext::get_write_buffer() { if (is_co_socket()) { return get_co_socket()->get_write_buffer(); } else { if (!write_buffer) { write_buffer = new String(SW_BUFFER_SIZE_STD, sw_php_allocator()); } return write_buffer; } } struct HttpResponseObject { HttpContext *ctx; zend_object std; }; static HttpResponseObject *http_response_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http_response_handlers.offset); } HttpContext *php_swoole_http_response_get_context(const zval *zobject) { return http_response_fetch_object(Z_OBJ_P(zobject))->ctx; } void php_swoole_http_response_set_context(const zval *zobject, HttpContext *ctx) { http_response_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; } static HttpContext *http_response_get_and_check_context(const zval *zobject) { auto *ctx = php_swoole_http_response_get_context(zobject); if (!ctx || (ctx->end_ || ctx->detached)) { swoole_set_last_error(SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); return nullptr; } return ctx; } static void http_response_free_object(zend_object *object) { HttpResponseObject *response = http_response_fetch_object(object); HttpContext *ctx = response->ctx; zval ztmp; /* bool, not required to release it */ if (ctx) { if (ctx->onAfterResponse) { ctx->onAfterResponse(ctx); } if (!ctx->end_ && (ctx->send_chunked || !ctx->send_header_) && !ctx->detached && sw_reactor()) { if (ctx->response.status == 0) { ctx->response.status = SW_HTTP_INTERNAL_SERVER_ERROR; } if (ctx->is_available()) { ctx->end(nullptr, &ztmp); } } ctx->response.zobject = nullptr; ctx->free(); } zend_object_std_dtor(&response->std); } static zend_object *http_response_create_object(zend_class_entry *ce) { auto *response = static_cast(zend_object_alloc(sizeof(HttpResponseObject), ce)); zend_object_std_init(&response->std, ce); object_properties_init(&response->std, ce); response->std.handlers = &swoole_http_response_handlers; return &response->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http_response, write); static PHP_METHOD(swoole_http_response, end); static PHP_METHOD(swoole_http_response, sendfile); static PHP_METHOD(swoole_http_response, redirect); static PHP_METHOD(swoole_http_response, cookie); static PHP_METHOD(swoole_http_response, rawcookie); static PHP_METHOD(swoole_http_response, header); static PHP_METHOD(swoole_http_response, initHeader); static PHP_METHOD(swoole_http_response, isWritable); static PHP_METHOD(swoole_http_response, detach); static PHP_METHOD(swoole_http_response, create); /** * for WebSocket Client */ static PHP_METHOD(swoole_http_response, upgrade); static PHP_METHOD(swoole_http_response, push); static PHP_METHOD(swoole_http_response, recv); static PHP_METHOD(swoole_http_response, close); static PHP_METHOD(swoole_http_response, trailer); static PHP_METHOD(swoole_http_response, ping); static PHP_METHOD(swoole_http_response, goaway); static PHP_METHOD(swoole_http_response, status); static PHP_METHOD(swoole_http_response, disconnect); SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_http_response_methods[] = { PHP_ME(swoole_http_response, initHeader, arginfo_class_Swoole_Http_Response_initHeader, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, isWritable, arginfo_class_Swoole_Http_Response_isWritable, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_http_response, setCookie, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, rawcookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_http_response, setRawCookie, rawcookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_http_response, setStatusCode, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_http_response, setHeader, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, trailer, arginfo_class_Swoole_Http_Response_trailer, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, ping, arginfo_class_Swoole_Http_Response_ping, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, goaway, arginfo_class_Swoole_Http_Response_goaway, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, write, arginfo_class_Swoole_Http_Response_write, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, end, arginfo_class_Swoole_Http_Response_end, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, sendfile, arginfo_class_Swoole_Http_Response_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, redirect, arginfo_class_Swoole_Http_Response_redirect, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, detach, arginfo_class_Swoole_Http_Response_detach, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, create, arginfo_class_Swoole_Http_Response_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) /** * WebSocket */ PHP_ME(swoole_http_response, upgrade, arginfo_class_Swoole_Http_Response_upgrade, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, push, arginfo_class_Swoole_Http_Response_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, recv, arginfo_class_Swoole_Http_Response_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, close, arginfo_class_Swoole_Http_Response_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, disconnect, arginfo_class_Swoole_Http_Response_disconnect, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_http_response_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_http_response, "Swoole\\Http\\Response", nullptr, swoole_http_response_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_response); SW_SET_CLASS_CLONEABLE(swoole_http_response, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_response, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_http_response, http_response_create_object, http_response_free_object, HttpResponseObject, std); zend_declare_property_long(swoole_http_response_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("header"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("cookie"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("trailer"), ZEND_ACC_PUBLIC); } void php_swoole_http_response_rshutdown() { if (date_cache.date) { zend_string_release(date_cache.date); date_cache = {}; } } static PHP_METHOD(swoole_http_response, write) { zend_string *sdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(sdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } #ifdef SW_HAVE_COMPRESSION // Why not enable compression? // If both compression and chunked encoding are enabled, // then the content stream is first compressed, then chunked; // so the chunk encoding itself is not compressed, // **and the data in each chunk is not compressed individually.** // The remote endpoint then decodes the stream by concatenating the chunks and decompressing the result. ctx->accept_compression = 0; #endif ctx->write(sdata, return_value); } static int http_response_parse_header_name(const char *key, size_t keylen) { if (SW_STRCASEEQ(key, keylen, "Server")) { return HTTP_HEADER_SERVER; } else if (SW_STRCASEEQ(key, keylen, "Connection")) { return HTTP_HEADER_CONNECTION; } else if (SW_STRCASEEQ(key, keylen, "Date")) { return HTTP_HEADER_DATE; } else if (SW_STRCASEEQ(key, keylen, "Content-Length")) { return HTTP_HEADER_CONTENT_LENGTH; } else if (SW_STRCASEEQ(key, keylen, "Content-Type")) { return HTTP_HEADER_CONTENT_TYPE; } else if (SW_STRCASEEQ(key, keylen, "Transfer-Encoding")) { return HTTP_HEADER_TRANSFER_ENCODING; } else if (SW_STRCASEEQ(key, keylen, "Content-Encoding")) { return HTTP_HEADER_CONTENT_ENCODING; } return 0; } zend_string *php_swoole_http_get_date() { time_t now = time(nullptr); if (now != date_cache.time) { if (date_cache.date) { zend_string_release(date_cache.date); } date_cache.time = now; date_cache.date = php_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); } return date_cache.date; } static void http_response_set_date_header(String *response) { auto date_str = php_swoole_http_get_date(); response->append(ZEND_STRL("Date: ")); response->append(ZSTR_VAL(date_str), ZSTR_LEN(date_str)); response->append(ZEND_STRL("\r\n")); } static void http_response_add_custom_header(String *http_buffer, const char *key, size_t l_key, zval *value) { if (ZVAL_IS_NULL(value)) { return; } zend::String str_value(value); str_value.rtrim(); if (swoole_http_has_crlf(str_value.val(), str_value.len())) { return; } http_buffer->append(key, l_key); http_buffer->append(SW_STRL(": ")); http_buffer->append(str_value.val(), str_value.len()); http_buffer->append(SW_STRL("\r\n")); } void HttpContext::build_header(String *http_buffer, const char *body, size_t length) { assert(send_header_ == 0); // http status line http_buffer->append(ZEND_STRL("HTTP/1.1 ")); if (response.reason) { http_buffer->append(response.status); http_buffer->append(ZEND_STRL(" ")); http_buffer->append(response.reason, strlen(response.reason)); } else { const char *status = HttpServer::get_status_message(response.status); http_buffer->append((char *) status, strlen(status)); } http_buffer->append(ZEND_STRL("\r\n")); // http headers uint32_t header_flags = 0x0; zval *zheader = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); if (ZVAL_IS_ARRAY(zheader)) { #ifdef SW_HAVE_COMPRESSION zend_string *content_type = nullptr; #endif zval *zvalue; zend_string *string_key; zend_ulong num_key; ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zheader), num_key, string_key, zvalue) { if (!string_key) { string_key = zend_long_to_str(num_key); } else { zend_string_addref(string_key); } zend::String key(string_key, false); int key_header = http_response_parse_header_name(ZSTR_VAL(string_key), ZSTR_LEN(string_key)); if (key_header > 0) { #ifdef SW_HAVE_COMPRESSION if (key_header == HTTP_HEADER_CONTENT_TYPE && accept_compression && compression_types) { content_type = zval_get_string(zvalue); } if (key_header == HTTP_HEADER_CONTENT_ENCODING && ZVAL_IS_STRING(zvalue)) { accept_compression = 0; } // https://github.com/swoole/swoole-src/issues/4857 // https://github.com/swoole/swoole-src/issues/5026 if (key_header == HTTP_HEADER_CONTENT_LENGTH && accept_compression) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP_CONFLICT_HEADER, "The client has set 'Accept-Encoding', 'Content-Length' will be ignored"); continue; } #endif // https://github.com/swoole/swoole-src/issues/4857 // https://github.com/swoole/swoole-src/issues/5026 if (key_header == HTTP_HEADER_CONTENT_LENGTH && send_chunked) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP_CONFLICT_HEADER, "You have set 'Transfer-Encoding', 'Content-Length' will be ignored"); continue; } header_flags |= key_header; if (ZVAL_IS_STRING(zvalue) && Z_STRLEN_P(zvalue) == 0) { continue; } } if (ZVAL_IS_ARRAY(zvalue)) { zval *zvalue_2; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) { http_response_add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue_2); } SW_HASHTABLE_FOREACH_END(); } else { http_response_add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue); } } ZEND_HASH_FOREACH_END(); #ifdef SW_HAVE_COMPRESSION if (accept_compression && compression_types) { std::string str_content_type = content_type ? std::string(ZSTR_VAL(content_type), ZSTR_LEN(content_type)) : std::string(ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); accept_compression = compression_types->find(str_content_type) != compression_types->end(); if (content_type) { zend_string_release(content_type); } } #endif } // http cookies zval *zcookie = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); if (ZVAL_IS_ARRAY(zcookie)) { zval *zvalue; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zcookie), zvalue) { if (Z_TYPE_P(zvalue) != IS_STRING || swoole_http_has_crlf(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue))) { continue; } http_buffer->append(ZEND_STRL("Set-Cookie: ")); http_buffer->append(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue)); http_buffer->append(ZEND_STRL("\r\n")); } SW_HASHTABLE_FOREACH_END(); } if (!(header_flags & HTTP_HEADER_SERVER)) { http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); } if (!(header_flags & HTTP_HEADER_DATE)) { http_response_set_date_header(http_buffer); } // websocket protocol (subsequent header info is unnecessary) if (upgrade == 1) { http_buffer->append(ZEND_STRL("\r\n")); send_header_ = 1; return; } if (!(header_flags & HTTP_HEADER_CONNECTION)) { if (keepalive) { http_buffer->append(ZEND_STRL("Connection: keep-alive\r\n")); } else { http_buffer->append(ZEND_STRL("Connection: close\r\n")); } } if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) { http_buffer->append(ZEND_STRL("Content-Type: " SW_HTTP_DEFAULT_CONTENT_TYPE "\r\n")); } if (send_chunked) { SW_ASSERT(length == 0); if (!(header_flags & HTTP_HEADER_TRANSFER_ENCODING)) { http_buffer->append(ZEND_STRL("Transfer-Encoding: chunked\r\n")); } } // Content-Length else if (length > 0 || parser.method != HTTP_HEAD) { #ifdef SW_HAVE_COMPRESSION if (compress(body, length)) { length = zlib_buffer->length; const char *content_encoding = get_content_encoding(); http_buffer->append(ZEND_STRL("Content-Encoding: ")); http_buffer->append((char *) content_encoding, strlen(content_encoding)); http_buffer->append(ZEND_STRL("\r\n")); } #endif if (!(header_flags & HTTP_HEADER_CONTENT_LENGTH)) { http_buffer->append(ZEND_STRL("Content-Length: ")); char result[128]; int convert_result = swoole_itoa(result, length); http_buffer->append(result, convert_result); http_buffer->append(ZEND_STRL("\r\n")); } } http_buffer->append(ZEND_STRL("\r\n")); send_header_ = 1; } ssize_t HttpContext::build_trailer(String *http_buffer) const { char *buf = sw_tg_buffer()->str; size_t l_buf = sw_tg_buffer()->size; ssize_t ret = 0; zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); uint32_t size = php_swoole_array_length_safe(ztrailer); if (size > 0) { const char *key; uint32_t keylen; int type; zval *zvalue; SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(ztrailer), key, keylen, type, zvalue) { if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { continue; } if (!ZVAL_IS_NULL(zvalue)) { zend::String str_value(zvalue); size_t n = sw_snprintf( buf, l_buf, "%.*s: %.*s\r\n", (int) keylen, key, (int) str_value.len(), str_value.val()); http_buffer->append(buf, n); ret += n; } } SW_HASHTABLE_FOREACH_END(); (void) type; http_buffer->append(ZEND_STRL("\r\n")); } return ret; } #ifdef SW_HAVE_ZLIB voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size) { return (voidpf) safe_emalloc(items, size, 0); } void php_zlib_free(voidpf opaque, voidpf address) { efree((void *) address); } #endif #ifdef SW_HAVE_BROTLI void *php_brotli_alloc(void *opaque, size_t size) { return emalloc(size); } void php_brotli_free(void *opaque, void *address) { efree(address); } #endif #ifdef SW_HAVE_COMPRESSION bool HttpContext::compress(const char *data, size_t length) { #ifdef SW_HAVE_ZLIB int encoding; #endif if (!accept_compression || length < compression_min_length) { return false; } if (false) { return false; } #ifdef SW_HAVE_ZLIB // gzip: 0x1f else if (compression_method == HTTP_COMPRESS_GZIP) { encoding = 0x1f; } // deflate: -0xf else if (compression_method == HTTP_COMPRESS_DEFLATE) { encoding = -0xf; } #endif #ifdef SW_HAVE_BROTLI else if (compression_method == HTTP_COMPRESS_BR) { if (compression_level < BROTLI_MIN_QUALITY) { compression_level = BROTLI_MIN_QUALITY; } else if (compression_level > BROTLI_MAX_QUALITY) { compression_level = BROTLI_MAX_QUALITY; } size_t memory_size = BrotliEncoderMaxCompressedSize(length); zlib_buffer = std::make_shared(memory_size); size_t input_size = length; const auto *input_buffer = reinterpret_cast(data); size_t encoded_size = zlib_buffer->size; auto *encoded_buffer = reinterpret_cast(zlib_buffer->str); if (BROTLI_TRUE != BrotliEncoderCompress(compression_level, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, input_size, input_buffer, &encoded_size, encoded_buffer)) { swoole_warning("BrotliEncoderCompress() failed"); return false; } else { zlib_buffer->length = encoded_size; content_compressed = 1; return true; } } #endif #ifdef SW_HAVE_ZSTD else if (compression_method == HTTP_COMPRESS_ZSTD) { int zstd_compress_level = compression_level; int zstd_max_level = ZSTD_maxCLevel(); int zstd_min_level = ZSTD_minCLevel(); zstd_compress_level = (zstd_compress_level > zstd_max_level) ? zstd_max_level : (zstd_compress_level < zstd_min_level ? zstd_min_level : zstd_compress_level); size_t compress_size = ZSTD_compressBound(length); zlib_buffer = std::make_shared(compress_size); size_t zstd_compress_result = ZSTD_compress((void *) zlib_buffer->str, compress_size, (void *) data, length, zstd_compress_level); if (ZSTD_isError(zstd_compress_result)) { swoole_warning("ZSTD_compress() failed, Error: [%s]", ZSTD_getErrorName(zstd_compress_result)); return false; } zlib_buffer->length = zstd_compress_result; content_compressed = 1; return true; } #endif else { swoole_warning("Unknown compression method"); return false; } #ifdef SW_HAVE_ZLIB if (compression_level < Z_NO_COMPRESSION) { compression_level = Z_DEFAULT_COMPRESSION; } else if (compression_level == Z_NO_COMPRESSION) { compression_level = Z_BEST_SPEED; } else if (compression_level > Z_BEST_COMPRESSION) { compression_level = Z_BEST_COMPRESSION; } size_t memory_size = ((size_t)((double) length * (double) 1.015)) + 10 + 8 + 4 + 1; zlib_buffer = std::make_shared(memory_size); z_stream zstream = {}; zstream.zalloc = php_zlib_alloc; zstream.zfree = php_zlib_free; int status = deflateInit2(&zstream, compression_level, Z_DEFLATED, encoding, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (status != Z_OK) { swoole_warning("deflateInit2() failed, Error: [%d]", status); return false; } zstream.next_in = (Bytef *) data; zstream.avail_in = length; zstream.next_out = (Bytef *) zlib_buffer->str; zstream.avail_out = zlib_buffer->size; status = deflate(&zstream, Z_FINISH); deflateEnd(&zstream); if (status != Z_STREAM_END) { swoole_warning("deflate() failed, Error: [%d]", status); return false; } zlib_buffer->length = zstream.total_out; zlib_buffer->offset = 0; content_compressed = 1; return true; #endif } #endif static PHP_METHOD(swoole_http_response, initHeader) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } zval *zresponse_object = ctx->response.zobject; swoole_http_init_and_read_property( swoole_http_response_ce, zresponse_object, &ctx->response.zheader, ZEND_STRL("header")); swoole_http_init_and_read_property( swoole_http_response_ce, zresponse_object, &ctx->response.zcookie, ZEND_STRL("cookie")); swoole_http_init_and_read_property( swoole_http_response_ce, zresponse_object, &ctx->response.ztrailer, ZEND_STRL("trailer")); RETURN_TRUE; } static PHP_METHOD(swoole_http_response, isWritable) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (!ctx || (ctx->end_ || ctx->detached)) { RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_http_response, end) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } zend_string *sdata = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_STR_OR_NULL(sdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ctx->onAfterResponse) { ctx->onAfterResponse(ctx); } if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE)) { swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE, ctx); } ctx->end(sdata, return_value); } void HttpContext::send_trailer(zval *return_value) { String *http_buffer = get_write_buffer(); http_buffer->clear(); if (build_trailer(http_buffer) == 0) { return; } if (!send(this, http_buffer->str, http_buffer->length)) { end_ = 1; close(this); RETURN_FALSE; } } bool HttpContext::send_file(zend_string *file, off_t offset, size_t length) { if (http2) { return swoole_http2_server_send_file(this, file, offset, length); } zval *zheader = sw_zend_read_and_convert_property_array(swoole_http_response_ce, response.zobject, ZEND_STRL("header"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("Content-Type"))) { auto mime_type = swoole::mime_type::get({file->val, file->len}); add_assoc_stringl(zheader, "Content-Type", mime_type.c_str(), mime_type.length()); } if (!send_header_) { #ifdef SW_HAVE_COMPRESSION accept_compression = 0; #endif String *http_buffer = get_write_buffer(); http_buffer->clear(); build_header(http_buffer, nullptr, length); if (!send(this, http_buffer->str, http_buffer->length)) { send_header_ = 0; return false; } } if (length > 0 && !sendfile(this, file, offset, length)) { close(this); return false; } end_ = 1; if (!keepalive) { close(this); } return true; } void HttpContext::write(zend_string *sdata, zval *return_value) { if (http2) { RETURN_BOOL(swoole_http2_server_write(this, sdata)); } String *http_buffer = get_write_buffer(); if (!send_header_) { send_chunked = 1; http_buffer->clear(); build_header(http_buffer, nullptr, 0); if (!send(this, http_buffer->str, http_buffer->length)) { send_chunked = 0; send_header_ = 0; RETURN_FALSE; } } auto data = ZSTR_VAL(sdata); size_t length = ZSTR_LEN(sdata); if (length == 0) { php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } http_buffer->clear(); char *hex_string = swoole_dec2hex(length, 16); int hex_len = strlen(hex_string); //"%.*s\r\n%.*s\r\n", hex_len, hex_string, body.length, body.str http_buffer->append(hex_string, hex_len); http_buffer->append(ZEND_STRL("\r\n")); http_buffer->append(data, length); http_buffer->append(ZEND_STRL("\r\n")); sw_free(hex_string); RETURN_BOOL(send(this, http_buffer->str, http_buffer->length)); } void HttpContext::end(zend_string *sdata, zval *return_value) { if (http2) { RETURN_BOOL(swoole_http2_server_end(this, sdata)); } if (send_chunked) { if (sdata && ZSTR_LEN(sdata) > 0) { zval retval; write(sdata, &retval); if (ZVAL_IS_FALSE(&retval)) { RETURN_FALSE; } } if (send_trailer_) { if (!send(this, ZEND_STRL("0\r\n"))) { RETURN_FALSE; } send_trailer(return_value); send_trailer_ = 0; } else { if (!send(this, ZEND_STRL("0\r\n\r\n"))) { RETURN_FALSE; } } send_chunked = 0; } else { const char *data = sdata ? ZSTR_VAL(sdata) : nullptr; size_t length = sdata ? ZSTR_LEN(sdata) : 0; String *http_buffer = get_write_buffer(); http_buffer->clear(); #ifdef SW_HAVE_ZLIB if (upgrade) { Server *serv = nullptr; Connection *conn = nullptr; if (!is_co_socket()) { serv = get_async_server(); conn = serv->get_connection_verify(fd); } bool enable_websocket_compression = is_co_socket() ? websocket_settings.compression : serv->websocket_compression; bool accept_websocket_compression = false; zval *pData; if (enable_websocket_compression && request.zobject && ((pData = zend_hash_str_find(Z_ARRVAL_P(request.zheader), ZEND_STRL("sec-websocket-extensions")))) && Z_TYPE_P(pData) == IS_STRING) { const std::string value(Z_STRVAL_P(pData), Z_STRLEN_P(pData)); if (value.substr(0, value.find_first_of(';')) == "permessage-deflate") { accept_websocket_compression = true; set_header(ZEND_STRL("Sec-Websocket-Extensions"), ZEND_STRL(SW_WEBSOCKET_EXTENSION_DEFLATE), false); } } websocket_compression = accept_websocket_compression; if (conn) { conn->websocket_compression = accept_websocket_compression; } } #endif build_header(http_buffer, data, length); if (length > 0) { #ifdef SW_HAVE_COMPRESSION if (content_compressed) { data = zlib_buffer->str; length = zlib_buffer->length; } #endif // send twice to reduce memory copy if (length > SW_HTTP_MAX_APPEND_DATA) { if (!send(this, http_buffer->str, http_buffer->length)) { send_header_ = 0; RETURN_FALSE; } if (!send(this, data, length)) { end_ = 1; close(this); RETURN_FALSE; } goto _skip_copy; } else { http_buffer->append(data, length); } } if (!send(this, http_buffer->str, http_buffer->length)) { end_ = 1; close(this); RETURN_FALSE; } } _skip_copy: if (upgrade && !is_co_socket()) { auto *serv = get_async_server(); auto *conn = serv->get_connection_verify(fd); if (conn && conn->websocket_status == websocket::STATUS_HANDSHAKE) { if (response.status == 101) { conn->websocket_status = websocket::STATUS_ACTIVE; } else { /* connection should be closed when handshake failed */ conn->websocket_status = websocket::STATUS_HANDSHAKE_FAILED; keepalive = 0; } } } if (!keepalive) { close(this); } end_ = 1; RETURN_TRUE; } bool HttpContext::set_header(const char *k, size_t klen, const char *v, size_t vlen, bool format) { zend::Variable ztmp(v, vlen); return set_header(k, klen, ztmp.ptr(), format); } bool HttpContext::set_header(const char *k, size_t klen, const std::string &v, bool format) { zend::Variable ztmp(v); return set_header(k, klen, ztmp.ptr(), format); } bool HttpContext::set_header(const char *k, size_t klen, zval *zvalue, bool format) { if (UNEXPECTED(klen > SW_HTTP_HEADER_KEY_SIZE - 1)) { php_swoole_error(E_WARNING, "header key is too long"); return false; } if (swoole_http_has_crlf(k, klen)) { return false; } zval *zheader = swoole_http_init_and_read_property( swoole_http_response_ce, response.zobject, &response.zheader, ZEND_STRL("header")); if (format) { swoole_strlcpy(sw_tg_buffer()->str, k, SW_HTTP_HEADER_KEY_SIZE); if (http2) { swoole_strtolower(sw_tg_buffer()->str, klen); } else { http_response_header_key_format(sw_tg_buffer()->str, klen); } k = sw_tg_buffer()->str; } zend::array_set(zheader, k, klen, zvalue); return true; } static PHP_METHOD(swoole_http_response, sendfile) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } if (ctx->send_chunked) { php_swoole_fatal_error(E_WARNING, "can't use sendfile when HTTP chunk is enabled"); RETURN_FALSE; } zend_string *file; zend_long offset = 0; zend_long length = 0; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(file) Z_PARAM_OPTIONAL Z_PARAM_LONG(offset) Z_PARAM_LONG(length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ZSTR_LEN(file) == 0) { php_swoole_error(E_WARNING, "file name is empty"); RETURN_FALSE; } struct stat file_stat; if (stat(file->val, &file_stat) < 0) { php_swoole_sys_error(E_WARNING, "stat(%s) failed", file->val); RETURN_FALSE; } if (!S_ISREG(file_stat.st_mode)) { php_swoole_error(E_WARNING, "parameter $file[%s] given is not a regular file", file->val); swoole_set_last_error(SW_ERROR_SERVER_IS_NOT_REGULAR_FILE); RETURN_FALSE; } if (file_stat.st_size < offset) { php_swoole_error(E_WARNING, "parameter $offset[" ZEND_LONG_FMT "] exceeds the file size", offset); RETURN_FALSE; } if (length > file_stat.st_size - offset) { php_swoole_error(E_WARNING, "parameter $length[" ZEND_LONG_FMT "] exceeds the file size", length); RETURN_FALSE; } if (length == 0) { length = file_stat.st_size - offset; } if (ctx->onAfterResponse) { ctx->onAfterResponse(ctx); } if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE)) { swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE, ctx); } RETURN_BOOL(ctx->send_file(file, offset, length)); } static bool inline http_response_create_cookie(HttpCookie *cookie, zval *zobject) { HttpContext *ctx = http_response_get_and_check_context(zobject); zend_string *cookie_str = cookie->toString(); if (!cookie_str) { cookie->reset(); return false; } add_next_index_str( swoole_http_init_and_read_property( swoole_http_response_ce, ctx->response.zobject, &ctx->response.zcookie, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE)), cookie_str); return true; } static void http_response_set_cookie(INTERNAL_FUNCTION_PARAMETERS, const bool encode) { zval *name_or_object; zend_string *value = nullptr, *path = nullptr, *domain = nullptr, *sameSite = nullptr, *priority = nullptr; zend_long expires = 0; zend_bool secure = false, httpOnly = false, partitioned = false; bool result; ZEND_PARSE_PARAMETERS_START(1, 10) Z_PARAM_ZVAL(name_or_object) Z_PARAM_OPTIONAL Z_PARAM_STR(value) Z_PARAM_LONG(expires) Z_PARAM_STR(path) Z_PARAM_STR(domain) Z_PARAM_BOOL(secure) Z_PARAM_BOOL(httpOnly) Z_PARAM_STR(sameSite) Z_PARAM_STR(priority) Z_PARAM_BOOL(partitioned) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ZVAL_IS_STRING(name_or_object)) { HttpCookie cookie(encode); (&cookie) ->withName(Z_STR_P(name_or_object)) ->withValue(value) ->withExpires(expires) ->withPath(path) ->withDomain(domain) ->withSecure(secure) ->withHttpOnly(httpOnly) ->withSameSite(sameSite) ->withPriority(priority) ->withPartitioned(partitioned); result = http_response_create_cookie(&cookie, ZEND_THIS); } else if (ZVAL_IS_OBJECT(name_or_object)) { HttpCookie *cookie = php_swoole_http_get_cooke_safety(name_or_object); result = http_response_create_cookie(cookie, ZEND_THIS); } else { php_swoole_error(E_WARNING, "The first argument must be a string or an cookie object"); result = false; } RETURN_BOOL(result); } static PHP_METHOD(swoole_http_response, cookie) { http_response_set_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_http_response, rawcookie) { http_response_set_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_http_response, status) { zend_long http_status; char *reason = nullptr; size_t reason_len = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(http_status) Z_PARAM_OPTIONAL Z_PARAM_STRING(reason, reason_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } ctx->response.status = http_status; ctx->response.reason = reason_len > 0 ? estrndup(reason, reason_len) : nullptr; RETURN_TRUE; } static PHP_METHOD(swoole_http_response, disconnect) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); RETURN_FALSE; } zend_long code = WebSocket::CLOSE_NORMAL; zend_string *reason = zend_empty_string; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(code) Z_PARAM_STR(reason) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); String *http_buffer = ctx->get_write_buffer(); zval zdata = {}; ZVAL_STR(&zdata, reason); FrameObject frame(&zdata, WebSocket::OPCODE_CLOSE, flags, code); if (sw_unlikely(!frame.pack(http_buffer))) { swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); RETURN_FALSE; } RETVAL_BOOL(ctx->send(ctx, http_buffer->str, http_buffer->length)); ctx->close(ctx); } static PHP_METHOD(swoole_http_response, header) { char *k; size_t klen; zval *zvalue; zend_bool format = true; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STRING(k, klen) Z_PARAM_ZVAL(zvalue) Z_PARAM_OPTIONAL Z_PARAM_BOOL(format) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } RETURN_BOOL(ctx->set_header(k, klen, zvalue, format)); } static PHP_METHOD(swoole_http_response, trailer) { char *k, *v; size_t klen, vlen; char key_buf[SW_HTTP_HEADER_KEY_SIZE]; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(k, klen) Z_PARAM_STRING_EX(v, vlen, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (!ctx) { RETURN_FALSE; } if (UNEXPECTED(klen > SW_HTTP_HEADER_KEY_SIZE - 1)) { php_swoole_error(E_WARNING, "trailer key is too long"); RETURN_FALSE; } zval *ztrailer = swoole_http_init_and_read_property( swoole_http_response_ce, ctx->response.zobject, &ctx->response.ztrailer, ZEND_STRL("trailer")); swoole_strlcpy(key_buf, k, sizeof(key_buf)); swoole_strtolower(key_buf, klen); if (UNEXPECTED(!v)) { add_assoc_null_ex(ztrailer, key_buf, klen); } else { add_assoc_stringl_ex(ztrailer, key_buf, klen, v, vlen); } ctx->send_trailer_ = 1; RETURN_TRUE; } static PHP_METHOD(swoole_http_response, ping) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } zend_string *zdata = zend_empty_string; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_STR(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ctx->http2) { RETURN_BOOL(swoole_http2_server_ping(ctx)); } else if (ctx->websocket) { String *buffer = ctx->get_write_buffer(); buffer->clear(); WebSocket::encode(buffer, ZSTR_VAL(zdata), ZSTR_LEN(zdata), WebSocket::OPCODE_PING, WebSocket::FLAG_FIN); RETURN_BOOL(ctx->send(ctx, buffer->str, buffer->length)); } else { php_swoole_fatal_error(E_WARNING, "only supports websocket or http2 client"); RETURN_FALSE; } } static PHP_METHOD(swoole_http_response, goaway) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } if (UNEXPECTED(!ctx->http2)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a HTTP2 conncetion", ctx->fd); RETURN_FALSE; } zend_long error_code = SW_HTTP2_ERROR_NO_ERROR; zend_string *debug_data = nullptr; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(error_code) Z_PARAM_STR_OR_NULL(debug_data) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(swoole_http2_server_goaway(ctx, error_code, debug_data)); } static PHP_METHOD(swoole_http_response, upgrade) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } if (UNEXPECTED(!ctx->is_co_socket())) { php_swoole_fatal_error(E_WARNING, "async server dose not support protocol upgrade"); RETURN_FALSE; } RETVAL_BOOL(swoole_websocket_handshake(ctx)); } static PHP_METHOD(swoole_http_response, push) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); RETURN_FALSE; } zval *zdata; zend_long opcode = WebSocket::OPCODE_TEXT; zval *zflags = nullptr; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(opcode) Z_PARAM_ZVAL_EX(zflags, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zflags != nullptr) { flags = zval_get_long(zflags); } String *http_buffer = ctx->get_write_buffer(); FrameObject frame(zdata, opcode, flags); sw_unset_bit(frame.flags, WebSocket::FLAG_MASK); if (ctx->websocket_compression) { sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); } if (sw_unlikely(!frame.pack(http_buffer))) { swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); RETURN_FALSE; } RETURN_BOOL(ctx->send(ctx, http_buffer->str, http_buffer->length)); } static PHP_METHOD(swoole_http_response, close) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } RETURN_BOOL(ctx->close(ctx)); } ssize_t WebSocket::send_frame(const swoole::WebSocketSettings &settings, SocketImpl *sock, uchar opcode, uchar flags, const char *payload, size_t payload_length) { if (settings.in_server) { sw_unset_bit(flags, WebSocket::FLAG_MASK); } else { sw_set_bit(flags, WebSocket::FLAG_MASK); } auto wbuf = sock->get_write_buffer(); wbuf->clear(); WebSocket::encode(wbuf, payload, payload_length, opcode, flags); return sock->send(wbuf->str, wbuf->length); } /** * return_value is object means success. * return_value is false means socket.read() method returning -1. * return_value is null means other error. * return_value is empry string means socket is closed. * the opcode is returned so the caller can decide when to release the continue_frame_buffer. */ void WebSocket::recv_frame(const WebSocketSettings &settings, std::shared_ptr &continue_frame_buffer, SocketImpl *sock, zval *return_value, double timeout) { zval zpayload; do { ssize_t retval = sock->recv_packet(timeout); if (retval < 0) { RETURN_FALSE; } else if (retval == 0) { RETURN_EMPTY_STRING(); } auto buffer = sock->get_read_buffer(); WebSocket::Frame frame{}; if (sw_unlikely(static_cast(buffer->length) < sizeof(frame.header))) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_NULL(); } if (sw_unlikely(!WebSocket::decode(&frame, buffer))) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_NULL(); } uchar opcode = frame.header.OPCODE; bool should_respond = false; if (opcode == WebSocket::OPCODE_PING) { if (!settings.open_ping_frame) { WebSocket::send_frame( settings, sock, WebSocket::OPCODE_PONG, WebSocket::FLAG_FIN, frame.payload, frame.payload_length); continue; } should_respond = true; } if (opcode == WebSocket::OPCODE_PONG) { if (!settings.open_pong_frame) { continue; } should_respond = true; } if (opcode == WebSocket::OPCODE_CLOSE) { if (!settings.open_close_frame) { WebSocket::send_frame( settings, sock, WebSocket::OPCODE_CLOSE, WebSocket::FLAG_FIN, frame.payload, frame.payload_length); continue; } should_respond = true; } if (should_respond) { ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); WebSocket::construct_frame(return_value, opcode, &zpayload, WebSocket::FLAG_FIN); zval_ptr_dtor(&zpayload); zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); return; } if (opcode == WebSocket::OPCODE_CONTINUATION) { if (sw_unlikely(!continue_frame_buffer)) { swoole_warning("A continuation frame cannot stand alone and MUST be preceded by an initial frame whose " "opcode indicates either text or binary data."); RETURN_NULL(); } if (sw_likely(frame.payload)) { continue_frame_buffer->append(frame.payload, frame.payload_length); } if (frame.header.FIN) { uchar complete_opcode = 0; uchar complete_flags = 0; WebSocket::parse_ext_flags(continue_frame_buffer->offset, &complete_opcode, &complete_flags); if (complete_flags & WebSocket::FLAG_RSV1) { if (sw_unlikely(!FrameObject::uncompress( &zpayload, continue_frame_buffer->str, continue_frame_buffer->length))) { continue_frame_buffer.reset(); swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_NULL(); } } else { zend::assign_zend_string_by_val( &zpayload, continue_frame_buffer->str, continue_frame_buffer->length); Z_TRY_ADDREF(zpayload); } sw_set_bit(complete_flags, WebSocket::FLAG_FIN); WebSocket::construct_frame(return_value, complete_opcode, &zpayload, complete_flags); zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); zval_ptr_dtor(&zpayload); /** * The final frame of the continuous frame sequence has been received, * and the complete message has been assembled. Memory can be released immediately. */ continue_frame_buffer.reset(); return; } } else { if (sw_unlikely(continue_frame_buffer)) { swoole_warning("All fragments of a message, except for the initial frame, must use the continuation " "frame opcode(0)."); RETURN_NULL(); } if (frame.header.FIN) { if (frame.compressed()) { if (sw_unlikely(!FrameObject::uncompress(&zpayload, frame.payload, frame.payload_length))) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_NULL(); } } else { ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); } WebSocket::construct_frame(return_value, frame.header.OPCODE, &zpayload, frame.get_flags()); zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); zval_ptr_dtor(&zpayload); return; } else { continue_frame_buffer = std::make_shared( (frame.payload_length > 0 ? frame.payload_length : SW_WEBSOCKET_DEFAULT_BUFFER), sw_zend_string_allocator()); continue_frame_buffer->offset = WebSocket::get_ext_flags(frame.header.OPCODE, frame.get_flags()); if (sw_likely(frame.payload)) { continue_frame_buffer->append(frame.payload, frame.payload_length); } } } } while (true); } static PHP_METHOD(swoole_http_response, recv) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); RETURN_FALSE; } double timeout = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); WebSocket::recv_frame( ctx->websocket_settings, ctx->continue_frame_buffer, ctx->get_co_socket(), return_value, timeout); if (ZVAL_IS_EMPTY_STRING(return_value)) { ctx->close(ctx); return; } if (sw_unlikely(ZVAL_IS_NULL(return_value))) { ZVAL_FALSE(return_value); } } static PHP_METHOD(swoole_http_response, detach) { HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (!ctx) { RETURN_FALSE; } ctx->detached = 1; RETURN_TRUE; } static PHP_METHOD(swoole_http_response, create) { zval *zobject = nullptr; zval *zrequest = nullptr; zval *zsocket = nullptr; zend_long fd = -1; Server *serv = nullptr; HttpContext *ctx = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(zobject) Z_PARAM_OPTIONAL Z_PARAM_LONG(fd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ZVAL_IS_OBJECT(zobject)) { _type_detect: if (instanceof_function(Z_OBJCE_P(zobject), swoole_server_ce)) { serv = php_swoole_server_get_and_check_server(zobject); if (serv->get_connection_verify(fd) == nullptr) { php_swoole_fatal_error(E_WARNING, "parameter $2 (%ld) must be valid connection session_id", (long) fd); RETURN_FALSE; } } else if (sw_zval_is_co_socket(zobject)) { zsocket = zobject; fd = php_swoole_get_socket(zobject)->get_fd(); } else { _bad_type: php_swoole_fatal_error(E_WARNING, "parameter $1 must be instanceof Server or Coroutine\\Socket"); RETURN_FALSE; } } else if (ZVAL_IS_ARRAY(zobject)) { zrequest = zend_hash_index_find(Z_ARR_P(zobject), 1); if (!ZVAL_IS_OBJECT(zrequest) || !instanceof_function(Z_OBJCE_P(zrequest), swoole_http_request_ce)) { php_swoole_fatal_error(E_WARNING, "parameter $1.second must be instanceof Http\\Request"); RETURN_FALSE; } zobject = zend_hash_index_find(Z_ARR_P(zobject), 0); if (!ZVAL_IS_OBJECT(zobject)) { goto _bad_type; } else { ctx = php_swoole_http_request_get_context(zrequest); goto _type_detect; } } else { fd = zval_get_long(zobject); serv = sw_server(); } if (serv && !serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (!ctx) { ctx = new HttpContext(); ctx->keepalive = 1; if (serv) { ctx->init(serv); } else if (zsocket) { ctx->init(zsocket); swoole_llhttp_parser_init(&ctx->parser, HTTP_REQUEST, (void *) ctx); } else { delete ctx; assert(0); RETURN_FALSE; } } else { if (serv) { ctx->bind(serv); } else if (zsocket) { ctx->bind(zsocket); } else { assert(0); RETURN_FALSE; } } object_init_ex(return_value, swoole_http_response_ce); php_swoole_http_response_set_context(return_value, ctx); ctx->fd = fd; ctx->response.zobject = return_value; sw_copy_to_stack(ctx->response.zobject, ctx->response._zobject); zend_update_property_long(swoole_http_response_ce, SW_Z8_OBJ_P(return_value), ZEND_STRL("fd"), fd); if (ctx->is_co_socket()) { zend_update_property_ex( swoole_http_response_ce, SW_Z8_OBJ_P(ctx->response.zobject), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), zobject); } if (zrequest) { zend_update_property_long(swoole_http_request_ce, SW_Z8_OBJ_P(ctx->request.zobject), ZEND_STRL("fd"), fd); } } static PHP_METHOD(swoole_http_response, redirect) { zval *zurl; zval *zhttp_code = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(zurl) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(zhttp_code, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } // status if (zhttp_code) { ctx->response.status = zval_get_long(zhttp_code); } else { ctx->response.status = 302; } // header ctx->set_header(ZEND_STRL("Location"), zurl, false); ctx->end(nullptr, return_value); } ================================================ FILE: ext-src/swoole_http_server.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" #include "swoole_process_pool.h" BEGIN_EXTERN_C() #include "rfc1867.h" END_EXTERN_C() using HttpRequest = swoole::http::Request; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; using swoole::Connection; using swoole::DataHead; using swoole::RecvData; using swoole::Server; using swoole::SessionId; namespace WebSocket = swoole::websocket; zend_class_entry *swoole_http_server_ce; zend_object_handlers swoole_http_server_handlers; static SW_THREAD_LOCAL std::queue queued_http_contexts; static SW_THREAD_LOCAL std::unordered_map server_ips; static SW_THREAD_LOCAL std::unordered_map client_ips; static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length); static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length); static bool http_context_disconnect(HttpContext *ctx); static void http_server_process_request(const Server *serv, zend::Callable *cb, HttpContext *ctx) { zval args[2]; args[0] = *ctx->request.zobject; args[1] = *ctx->response.zobject; if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onRequest handler error", ZSTR_VAL(swoole_http_server_ce->name)); #ifdef SW_HTTP_SERVICE_UNAVAILABLE_PACKET ctx->send(ctx, SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET)); #endif ctx->close(ctx); } } int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { auto session_id = req->info.fd; auto server_fd = req->info.server_fd; Connection *conn = serv->get_connection_verify_no_ssl(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); return SW_ERR; } auto *port = serv->get_port_by_server_fd(server_fd); // other server port if (!(port->open_http_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onRequest)) && !(port->open_websocket_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onMessage))) { return php_swoole_server_onReceive(serv, req); } // websocket client if (conn->websocket_status >= WebSocket::STATUS_HANDSHAKE) { return swoole_websocket_onMessage(serv, req); } if (conn->http2_stream) { return swoole_http2_server_onReceive(serv, conn, req); } HttpContext *ctx = swoole_http_context_new(session_id); ctx->init(serv); ctx->onBeforeRequest = swoole_http_server_onBeforeRequest; zval *zdata = &ctx->request.zdata; php_swoole_get_recv_data(serv, zdata, req); swoole_trace_log(SW_TRACE_SERVER, "http request from %ld with %d bytes: <request.zobject; zval *zresponse_object = ctx->response.zobject; llhttp_t *parser = &ctx->parser; swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); size_t parsed_n = ctx->parse(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata)); if (sw_unlikely(ctx->parser.error != HPE_OK || !ctx->completed)) { ctx->send(ctx, SW_STRL(SW_HTTP_BAD_REQUEST_PACKET)); ctx->close(ctx); if (ctx->parser.error != HPE_OK) { swoole_notice("Invalid HTTP request discarded: %ld bytes unprocessed. Reason: %s", Z_STRLEN_P(zdata) - parsed_n, llhttp_get_error_reason(&ctx->parser)); } else { swoole_notice("Incomplete HTTP request: parsed successfully but missing required components"); } goto _dtor_and_return; } do { zval *zserver = ctx->request.zserver; HashTable *ht = Z_ARR_P(zserver); swoole_http_server_populate_ip_and_port(serv, ht, conn, session_id, ctx->keepalive); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_MASTER_TIME), (zend_long) conn->last_recv_time); } while (false); if (swoole_isset_hook(static_cast(PHP_SWOOLE_HOOK_BEFORE_REQUEST))) { swoole_call_hook(static_cast(PHP_SWOOLE_HOOK_BEFORE_REQUEST), ctx); } // begin to check and call registered callback do { zend::Callable *cb = nullptr; if (conn->websocket_status == WebSocket::STATUS_CONNECTION) { cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onHandshake); if (cb == nullptr) { swoole_websocket_onHandshake(serv, port, ctx); goto _dtor_and_return; } else { conn->websocket_status = WebSocket::STATUS_HANDSHAKE; ctx->upgrade = 1; } } else { cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onRequest); if (cb == nullptr) { swoole_websocket_onRequest(ctx); goto _dtor_and_return; } } ctx->private_data_2 = cb; if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) { return SW_OK; } http_server_process_request(serv, cb, ctx); } while (false); _dtor_and_return: zval_ptr_dtor(zrequest_object); zval_ptr_dtor(zresponse_object); return SW_OK; } void php_swoole_http_server_onClose(Server *serv, DataHead *info) { server_ips.erase(info->fd); client_ips.erase(info->fd); php_swoole_server_onClose(serv, info); } void php_swoole_http_server_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX(swoole_http_server, "Swoole\\Http\\Server", nullptr, nullptr, swoole_server); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_server); SW_SET_CLASS_CLONEABLE(swoole_http_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_server, sw_zend_class_unset_property_deny); } void php_swoole_http_server_rinit() { // for is_uploaded_file and move_uploaded_file if (!SG(rfc1867_uploaded_files)) { ALLOC_HASHTABLE(SG(rfc1867_uploaded_files)); zend_hash_init(SG(rfc1867_uploaded_files), 8, nullptr, nullptr, 0); } } void php_swoole_http_server_rshutdown() { if (SG(rfc1867_uploaded_files)) { destroy_uploaded_files_hash(); SG(rfc1867_uploaded_files) = nullptr; } server_ips.clear(); client_ips.clear(); while (!queued_http_contexts.empty()) { HttpContext *ctx = queued_http_contexts.front(); queued_http_contexts.pop(); ctx->end_ = 1; ctx->onAfterResponse = nullptr; zval_ptr_dtor(ctx->request.zobject); zval_ptr_dtor(ctx->response.zobject); } } HttpContext *swoole_http_context_new(SessionId fd) { auto *ctx = new HttpContext(); zval *zrequest_object = &ctx->request._zobject; ctx->request.zobject = zrequest_object; ZVAL_OBJ(zrequest_object, swoole_http_request_ce->create_object(swoole_http_request_ce)); php_swoole_http_request_set_context(zrequest_object, ctx); zval *zresponse_object = &ctx->response._zobject; ctx->response.zobject = zresponse_object; ZVAL_OBJ(zresponse_object, swoole_http_response_ce->create_object(swoole_http_response_ce)); php_swoole_http_response_set_context(zresponse_object, ctx); http_server_set_object_fd_property(SW_Z8_OBJ_P(zrequest_object), swoole_http_request_ce, fd); http_server_set_object_fd_property(SW_Z8_OBJ_P(zresponse_object), swoole_http_response_ce, fd); swoole_http_init_and_read_property(swoole_http_request_ce, zrequest_object, &ctx->request.zserver, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER), HT_MIN_SIZE << 1); swoole_http_init_and_read_property( swoole_http_request_ce, zrequest_object, &ctx->request.zheader, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER)); ctx->fd = fd; return ctx; } void HttpContext::init(Server *serv) { parse_cookie = serv->http_parse_cookie; parse_body = serv->http_parse_post; parse_files = serv->http_parse_files; #ifdef SW_HAVE_COMPRESSION enable_compression = serv->http_compression; compression_level = serv->http_compression_level; compression_min_length = serv->compression_min_length; compression_types = serv->http_compression_types; #endif upload_tmp_dir = serv->upload_tmp_dir; bind(serv); } void HttpContext::bind(Server *serv) { private_data = serv; send = http_context_send_data; sendfile = http_context_sendfile; close = http_context_disconnect; ZVAL_NULL(&zsocket); } void HttpContext::copy(const HttpContext *ctx) { parse_cookie = ctx->parse_cookie; parse_body = ctx->parse_body; parse_files = ctx->parse_files; #ifdef SW_HAVE_COMPRESSION enable_compression = ctx->enable_compression; compression_level = ctx->compression_level; compression_min_length = ctx->compression_min_length; compression_types = ctx->compression_types; #endif zsocket = ctx->zsocket; Z_TRY_ADDREF(zsocket); private_data = ctx->private_data; upload_tmp_dir = ctx->upload_tmp_dir; send = ctx->send; sendfile = ctx->sendfile; close = ctx->close; onBeforeRequest = ctx->onBeforeRequest; onAfterResponse = ctx->onAfterResponse; } bool HttpContext::is_available() const { if (!response.zobject) { return false; } if (is_co_socket()) { return !php_swoole_socket_is_closed(&zsocket); } else { auto *serv = get_async_server(); auto *conn = serv->get_connection_by_session_id(fd); if (!conn || conn->closed || conn->peer_closed) { return false; } } return true; } void HttpContext::free() { // http context can only be freed after request and response were freed // If is an HTTP2 request, must wait for the stream to release before releasing the HTTP context if (request.zobject || response.zobject || stream_id > 0) { return; } HttpRequest *req = &request; HttpResponse *res = &response; if (req->path) { efree(req->path); } if (Z_TYPE(req->zdata) == IS_STRING) { zend_string_release(Z_STR(req->zdata)); } delete req->chunked_body; delete req->h2_data_buffer; if (res->reason) { efree(res->reason); } if (mt_parser) { multipart_parser_free(mt_parser); mt_parser = nullptr; } if (form_data_buffer) { delete form_data_buffer; form_data_buffer = nullptr; } if (is_co_socket()) { zval_ptr_dtor(&zsocket); } delete write_buffer; delete this; } bool http_context_send_data(HttpContext *ctx, const char *data, size_t length) { auto *serv = ctx->get_async_server(); bool retval = serv->send(ctx->fd, data, length); if (!retval && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { auto sdata = zend_string_init(data, length, false); auto rv = php_swoole_server_send_yield(serv, ctx->fd, sdata); zend_string_release(sdata); return rv; } return retval; } static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { auto *serv = ctx->get_async_server(); return serv->sendfile(ctx->fd, file->val, file->len, offset, length); } static bool http_context_disconnect(HttpContext *ctx) { auto *serv = ctx->get_async_server(); return serv->close(ctx->fd, false); } bool swoole_http_server_onBeforeRequest(HttpContext *ctx) { ctx->onBeforeRequest = nullptr; ctx->onAfterResponse = swoole_http_server_onAfterResponse; if (!sw_server() || !sw_worker() || sw_worker()->is_shutdown()) { return false; } auto serv = ctx->get_async_server(); auto worker = sw_worker(); swoole_trace("serv->gs->concurrency=%u, max_concurrency=%u", serv->gs->concurrency, serv->gs->max_concurrency); sw_atomic_add_fetch(&serv->gs->concurrency, 1); worker->concurrency++; if (worker->concurrency > serv->worker_max_concurrency) { swoole_trace_log(SW_TRACE_COROUTINE, "exceed worker_max_concurrency[%u] limit, request[%p] queued", serv->worker_max_concurrency, ctx); queued_http_contexts.push(ctx); return false; } return true; } void swoole_http_server_onAfterResponse(HttpContext *ctx) { ctx->onAfterResponse = nullptr; if (sw_unlikely(!sw_server() || !sw_worker())) { return; } if (sw_unlikely(sw_worker()->is_shutdown())) { while (!queued_http_contexts.empty()) { HttpContext *_ctx = queued_http_contexts.front(); queued_http_contexts.pop(); _ctx->send(_ctx, SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET)); _ctx->close(_ctx); } return; } auto serv = ctx->get_async_server(); auto worker = sw_worker(); swoole_trace("serv->gs->concurrency=%u, max_concurrency=%u", serv->gs->concurrency, serv->gs->max_concurrency); sw_atomic_sub_fetch(&serv->gs->concurrency, 1); worker->concurrency--; if (!queued_http_contexts.empty()) { HttpContext *_ctx = queued_http_contexts.front(); swoole_trace("[POP 1] concurrency=%u, ctx=%p, request=%p", worker->concurrency, _ctx, _ctx->request.zobject); queued_http_contexts.pop(); swoole_event_defer( [](void *private_data) { auto *ctx = static_cast(private_data); auto *serv = ctx->get_async_server(); auto *cb = static_cast(ctx->private_data_2); swoole_trace("[POP 2] ctx=%p, request=%p", ctx, ctx->request.zobject); http_server_process_request(serv, cb, ctx); zval_ptr_dtor(ctx->request.zobject); zval_ptr_dtor(ctx->response.zobject); }, _ctx); } } /** * When calculating the server-side IP and client-side IP, since these two calculations * share the same memory block, they cannot be performed simultaneously; otherwise, * both results would be identical. Therefore, it is necessary to first calculate the client-side IP, * write it to the cache, and then proceed to calculate the server-side IP. */ static void http_server_session_track_ip(swoole::Server *server, HashTable *ht, swoole::Connection *conn, swoole::SessionId session_id, zend_string *known_string, std::unordered_map &ips) { auto iter = ips.find(session_id); if (iter == ips.end()) { const char *address = nullptr; if (known_string == SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR)) { address = server->get_local_addr(conn); } else { address = conn->info.get_addr(); } auto rs = ips.emplace(session_id, address); iter = rs.first; } iter->second.add_ref(); http_server_add_server_array(ht, known_string, iter->second.ptr()); } void swoole_http_server_populate_ip_and_port( Server *server, HashTable *ht, Connection *conn, SessionId session_id, bool keepalive) { http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PORT), (zend_long) conn->local_port); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_PORT), (zend_long) conn->info.get_port()); if (conn->info.is_loopback_addr()) { auto key = conn->info.type == SW_SOCK_TCP6 ? SW_ZEND_STR_ADDR_LOOPBACK_V6 : SW_ZEND_STR_ADDR_LOOPBACK_V4; http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), SW_ZSTR_KNOWN(key)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), SW_ZSTR_KNOWN(key)); } else { if (keepalive && (server->is_base_mode() || (server->is_process_mode() && server->dispatch_mode == Server::DISPATCH_FDMOD))) { http_server_session_track_ip( server, ht, conn, session_id, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server_ips); http_server_session_track_ip( server, ht, conn, session_id, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), client_ips); } else { http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server->get_local_addr(conn)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), server->get_remote_addr(conn)); } } } ================================================ FILE: ext-src/swoole_http_server_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" #include "php_swoole_websocket.h" #include BEGIN_EXTERN_C() #include "stubs/php_swoole_http_server_coro_arginfo.h" END_EXTERN_C() using swoole::Coroutine; using swoole::microtime; using swoole::PHPCoroutine; using swoole::Server; using swoole::String; using swoole::coroutine::System; namespace WebSocket = swoole::websocket; using HttpRequest = swoole::http::Request; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; using Http2Stream = swoole::http2::Stream; using Http2Session = swoole::http2::Session; static zend_class_entry *swoole_http_server_coro_ce; static zend_object_handlers swoole_http_server_coro_handlers; static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length); static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length); static bool http_context_disconnect(HttpContext *ctx); static void http2_server_onRequest(const std::shared_ptr &session, const std::shared_ptr &stream); namespace swoole { namespace coroutine { class HttpServer { public: SocketImpl *socket; zend::Callable *default_handler; std::unordered_map handlers; bool running; zval zclients; /* options */ bool parse_cookie; bool parse_post; bool parse_files; #ifdef SW_HAVE_COMPRESSION bool compression; #endif WebSocketSettings websocket_settings; char *upload_tmp_dir; #ifdef SW_HAVE_COMPRESSION uint8_t compression_level; uint32_t compression_min_length; std::shared_ptr> compression_types = nullptr; #endif explicit HttpServer(SocketType type) { socket = new SocketImpl(type); default_handler = nullptr; array_init(&zclients); running = true; parse_cookie = true; parse_post = true; parse_files = false; #ifdef SW_HAVE_COMPRESSION compression = true; compression_level = SW_Z_BEST_SPEED; compression_min_length = SW_COMPRESSION_MIN_LENGTH_DEFAULT; #endif #ifdef SW_HAVE_ZLIB websocket_settings.compression = false; #endif upload_tmp_dir = sw_strdup("/tmp"); } ~HttpServer() { sw_free(upload_tmp_dir); zval_ptr_dtor(&zclients); for (const auto &handler : handlers) { sw_callable_free(handler.second); } delete socket; } bool set_handler(const std::string &pattern, zval *zfn) { auto cb = sw_callable_create(zfn); if (!cb) { return false; } if (handlers.find(pattern) != handlers.end()) { sw_callable_free(handlers[pattern]); } handlers[pattern] = cb; if (pattern == "/") { default_handler = cb; } return true; } zend::Callable *get_handler(const HttpContext *ctx) const { for (auto &handler : handlers) { if (handler.second == default_handler) { continue; } if (swoole_str_istarts_with( ctx->request.path, ctx->request.path_len, handler.first.c_str(), handler.first.length())) { return handler.second; } } return default_handler; } HttpContext *create_context(SocketImpl *conn, zval *zconn) const { HttpContext *ctx = swoole_http_context_new(conn->get_fd()); ctx->parse_body = parse_post; ctx->parse_cookie = parse_cookie; ctx->parse_files = parse_files; #ifdef SW_HAVE_COMPRESSION ctx->enable_compression = compression; ctx->compression_level = compression_level; ctx->compression_min_length = compression_min_length; ctx->compression_types = compression_types; #endif ctx->websocket_settings = websocket_settings; ctx->upload_tmp_dir = upload_tmp_dir; ctx->bind(zconn); llhttp_t *parser = &ctx->parser; swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); zend_update_property_ex( swoole_http_response_ce, SW_Z8_OBJ_P(ctx->response.zobject), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), zconn); return ctx; } void recv_http2_frame(HttpContext *ctx) { auto *sock = ctx->get_co_socket(); http2::send_setting_frame(&sock->protocol, sock->get_socket()); sock->open_length_check = true; sock->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; sock->protocol.package_length_offset = 0; sock->protocol.package_body_offset = 0; sock->protocol.get_package_length = http2::get_frame_length; auto session = swoole_http2_server_session_new(ctx->fd); session->max_body_size = sock->protocol.package_max_length; session->default_ctx = ctx; session->handle = http2_server_onRequest; session->private_data = this; session->is_coro = true; while (true) { auto buffer = sock->get_read_buffer(); ssize_t retval = sock->recv_packet(); if (sw_unlikely(retval <= 0)) { break; } if (swoole_http2_server_parse(session, buffer->str) == SW_ERR) { break; } } swoole_http2_server_session_free(ctx->fd); ctx->detached = 1; zval_dtor(ctx->request.zobject); zval_dtor(ctx->response.zobject); } }; }; // namespace coroutine }; // namespace swoole using swoole::coroutine::HttpServer; struct HttpServerObject { HttpServer *server; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_http_server_coro, __construct); static PHP_METHOD(swoole_http_server_coro, set); static PHP_METHOD(swoole_http_server_coro, handle); static PHP_METHOD(swoole_http_server_coro, start); static PHP_METHOD(swoole_http_server_coro, shutdown); static PHP_METHOD(swoole_http_server_coro, onAccept); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_http_server_coro_methods[] = { PHP_ME(swoole_http_server_coro, __construct, arginfo_class_Swoole_Coroutine_Http_Server___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, set, arginfo_class_Swoole_Coroutine_Http_Server_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, handle, arginfo_class_Swoole_Coroutine_Http_Server_handle, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, onAccept, arginfo_class_Swoole_Coroutine_Http_Server_onAccept, ZEND_ACC_PRIVATE) PHP_ME(swoole_http_server_coro, start, arginfo_class_Swoole_Coroutine_Http_Server_start, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, shutdown, arginfo_class_Swoole_Coroutine_Http_Server_shutdown, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on static zend_object *http_server_coro_create_object(zend_class_entry *ce) { auto *hsc = static_cast(zend_object_alloc(sizeof(HttpServerObject), ce)); zend_object_std_init(&hsc->std, ce); object_properties_init(&hsc->std, ce); hsc->std.handlers = &swoole_http_server_coro_handlers; return &hsc->std; } static HttpServerObject *http_server_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_http_server_coro_handlers.offset); } static HttpServer *http_server_coro_get_object(zend_object *obj) { return http_server_coro_fetch_object(obj)->server; } static void http_server_coro_set_error(const zval *zobject, const SocketImpl *sock) { zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sock->errCode); zend_update_property_string(swoole_http_server_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), sock->errMsg); } static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length) { return ctx->get_co_socket()->send_all(data, length) == (ssize_t) length; } static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { return ctx->get_co_socket()->sendfile(file->val, offset, length); } static bool http_context_disconnect(HttpContext *ctx) { return ctx->get_co_socket()->close(); } static void http_server_coro_free_object(zend_object *object) { HttpServerObject *hsc = http_server_coro_fetch_object(object); if (hsc->server) { HttpServer *hs = hsc->server; delete hs; } zend_object_std_dtor(&hsc->std); } void HttpContext::init(zval *zsock) { parse_cookie = 1; parse_body = 1; parse_files = 1; #ifdef SW_HAVE_COMPRESSION enable_compression = 1; compression_level = SW_Z_BEST_SPEED; #endif #ifdef SW_HAVE_ZLIB websocket_compression = 0; #endif upload_tmp_dir = "/tmp"; bind(zsock); } void HttpContext::bind(zval *zsock) { zsocket = *zsock; Z_TRY_ADDREF(zsocket); private_data = php_swoole_get_socket(zsock); send = http_context_send_data; sendfile = http_context_sendfile; close = http_context_disconnect; } void php_swoole_http_server_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_http_server_coro, "Swoole\\Coroutine\\Http\\Server", "Co\\Http\\Server", swoole_http_server_coro_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_server_coro); SW_SET_CLASS_CLONEABLE(swoole_http_server_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_server_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http_server_coro); SW_SET_CLASS_CUSTOM_OBJECT( swoole_http_server_coro, http_server_coro_create_object, http_server_coro_free_object, HttpServerObject, std); swoole_http_server_coro_ce->ce_flags |= ZEND_ACC_FINAL; zend_declare_property_long(swoole_http_server_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_server_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_server_coro_ce, ZEND_STRL("port"), -1, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http_server_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_server_coro_ce, ZEND_STRL("settings"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_server_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http_server_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); } static PHP_METHOD(swoole_http_server_coro, __construct) { char *host; size_t l_host; zend_long port = 0; zend_bool ssl = false; zend_bool reuse_port = false; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_STRING(host, l_host) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_BOOL(ssl) Z_PARAM_BOOL(reuse_port) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend_update_property_stringl(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, l_host); zend_update_property_bool(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("ssl"), ssl); // check host if (l_host == 0) { zend_throw_exception_ex(swoole_exception_ce, EINVAL, "host is empty"); RETURN_FALSE; } HttpServerObject *hsc = http_server_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); std::string host_str(host, l_host); hsc->server = new HttpServer(swoole::network::Socket::convert_to_type(host_str)); SocketImpl *sock = hsc->server->socket; if (reuse_port) { sock->get_socket()->set_reuse_port(); } if (!sock->bind(host_str, port)) { http_server_coro_set_error(ZEND_THIS, sock); zend_throw_exception_ex(swoole_exception_ce, sock->errCode, "bind(%s:%d) failed", host, (int) port); RETURN_FALSE; } // check ssl if (ssl) { /* we have to call ssl_check_context after user setProtocols */ zval *zsettings = sw_zend_read_and_convert_property_array(swoole_http_server_coro_ce, ZEND_THIS, ZEND_STRL("settings"), 0); add_assoc_bool(zsettings, "open_ssl", 1); sock->enable_ssl_encrypt(); } if (!sock->listen()) { http_server_coro_set_error(ZEND_THIS, sock); zend_throw_exception_ex(swoole_exception_ce, sock->errCode, "listen() failed"); RETURN_FALSE; } zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("fd"), sock->get_fd()); zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), sock->get_port()); } static PHP_METHOD(swoole_http_server_coro, handle) { char *pattern; size_t pattern_len; zval *zfn; HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(pattern, pattern_len) Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END(); std::string key(pattern, pattern_len); RETURN_BOOL(hs->set_handler(key, zfn)); } static PHP_METHOD(swoole_http_server_coro, set) { zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_array_length(zset) == 0) { RETURN_FALSE; } else { zval *zsettings = sw_zend_read_and_convert_property_array(swoole_http_server_coro_ce, ZEND_THIS, ZEND_STRL("settings"), 0); php_array_merge(Z_ARRVAL_P(zsettings), Z_ARRVAL_P(zset)); RETURN_TRUE; } } static PHP_METHOD(swoole_http_server_coro, start) { HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); SocketImpl *sock = hs->socket; /* get callback fci cache */ char *func_name = nullptr; zend_fcall_info_cache fci_cache; zend::Variable zcallback("onAccept"); if (!sw_zend_is_callable_at_frame( zcallback.ptr(), ZEND_THIS, execute_data, 0, &func_name, nullptr, &fci_cache, nullptr)) { php_swoole_fatal_error(E_CORE_ERROR, "function '%s' is not callable", func_name); return; } efree(func_name); /* check settings */ zval *zsettings = sw_zend_read_and_convert_property_array(swoole_http_server_coro_ce, ZEND_THIS, ZEND_STRL("settings"), 0); php_swoole_socket_set_protocol(hs->socket, zsettings); HashTable *vht = Z_ARRVAL_P(zsettings); zval *ztmp; // parse cookie header if (php_swoole_array_get_value(vht, "http_parse_cookie", ztmp)) { hs->parse_cookie = zval_is_true(ztmp); } // parse x-www-form-urlencoded form data if (php_swoole_array_get_value(vht, "http_parse_post", ztmp)) { hs->parse_post = zval_is_true(ztmp); } // parse multipart/form-data file uploads if (php_swoole_array_get_value(vht, "http_parse_files", ztmp)) { hs->parse_files = zval_is_true(ztmp); } #ifdef SW_HAVE_COMPRESSION // http content compression if (php_swoole_array_get_value(vht, "http_compression", ztmp)) { hs->compression = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "http_compression_level", ztmp) || php_swoole_array_get_value(vht, "compression_level", ztmp) || php_swoole_array_get_value(vht, "http_gzip_level", ztmp)) { zend_long level = zval_get_long(ztmp); if (level > UINT8_MAX) { level = UINT8_MAX; } else if (level < 0) { level = 0; } hs->compression_level = level; } if (php_swoole_array_get_value(vht, "http_compression_min_length", ztmp) || php_swoole_array_get_value(vht, "compression_min_length", ztmp)) { hs->compression_min_length = zval_get_long(ztmp); } if (php_swoole_array_get_value(vht, "http_compression_types", ztmp) || php_swoole_array_get_value(vht, "compression_types", ztmp)) { hs->compression_types = std::make_shared>(); if (ZVAL_IS_ARRAY(ztmp)) { zval *ztype; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), ztype) zend::String type(ztype); if (type.len() > 0) { hs->compression_types->emplace(type.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { php_swoole_fatal_error(E_ERROR, "http_compression_types must be array"); RETURN_FALSE; } } #endif WebSocket::apply_setting(hs->websocket_settings, vht, true); // temporary directory for HTTP uploaded file. if (php_swoole_array_get_value(vht, "upload_tmp_dir", ztmp)) { zend::String str_v(ztmp); if (php_swoole_create_dir(str_v.val(), str_v.len()) < 0) { php_swoole_fatal_error(E_ERROR, "Unable to create upload_tmp_dir[%s]", str_v.val()); return; } if (hs->upload_tmp_dir) { sw_free(hs->upload_tmp_dir); } hs->upload_tmp_dir = str_v.dup(); } hs->running = true; while (hs->running) { auto conn = sock->accept(); if (conn) { zval zsocket; php_swoole_init_socket_object(&zsocket, conn); long cid = PHPCoroutine::create(&fci_cache, 1, &zsocket, zcallback.ptr()); zval_dtor(&zsocket); if (cid < 0) { goto _wait_1s; } } else { /* * Too many connection, wait 1s */ if (sock->errCode == EMFILE || sock->errCode == ENFILE) { _wait_1s: System::sleep(SW_ACCEPT_RETRY_TIME); } else if (sock->errCode == ETIMEDOUT || sock->errCode == SW_ERROR_SSL_BAD_CLIENT) { continue; } else if (sock->errCode == ECANCELED) { http_server_coro_set_error(ZEND_THIS, sock); break; } else { http_server_coro_set_error(ZEND_THIS, sock); php_swoole_fatal_error(E_WARNING, "accept failed, Error: %s[%d]", sock->errMsg, sock->errCode); break; } } } RETURN_TRUE; } static PHP_METHOD(swoole_http_server_coro, onAccept) { HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); zval *zconn; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_OBJECT(zconn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_NULL()); Coroutine *co = Coroutine::get_current(); SocketImpl *sock = php_swoole_get_socket(zconn); sock->set_buffer_allocator(sw_zend_string_allocator()); String *buffer = sock->get_read_buffer(); HttpContext *ctx = nullptr; bool header_completed = false; off_t header_crlf_offset = 0; size_t total_length = 0; if (sock->ssl_is_enable() && !sock->ssl_handshake()) { RETURN_NULL(); } zend::array_set(&hs->zclients, co->get_cid(), zconn); auto remote_addr = zend::Variable(sock->get_addr()); int remote_port = sock->get_port(); sock->get_socket()->get_name(); auto server_addr = zend::Variable(sock->get_socket()->get_addr()); int server_port = hs->socket->get_port(); while (true) { _recv_request : { sock->get_socket()->recv_wait = 1; ssize_t retval = sock->recv(buffer->str + buffer->length, buffer->size - buffer->length); if (sw_unlikely(retval <= 0)) { break; } buffer->length += retval; } _parse_request: if (!ctx) { ctx = hs->create_context(sock, zconn); } if (!header_completed) { ssize_t pos = swoole_strnpos( buffer->str + header_crlf_offset, buffer->length - header_crlf_offset, ZEND_STRL("\r\n\r\n")); if (pos < 0) { if (buffer->length == buffer->size) { ctx->response.status = SW_HTTP_REQUEST_ENTITY_TOO_LARGE; break; } header_crlf_offset = buffer->length > 4 ? buffer->length - 4 : 0; continue; } size_t header_length = header_crlf_offset + pos; header_completed = true; header_crlf_offset = 0; // The HTTP header must be parsed first // Header contains CRLFx2 header_length += 4; size_t parsed_n = ctx->parse(buffer->str, header_length); if (parsed_n != header_length) { ctx->response.status = SW_HTTP_BAD_REQUEST; break; } buffer->offset += header_length; total_length = header_length + ctx->get_content_length(); if (ctx->get_content_length() > 0 && total_length > sock->protocol.package_max_length) { ctx->response.status = SW_HTTP_REQUEST_ENTITY_TOO_LARGE; break; } if (total_length > buffer->size) { buffer->extend(total_length); } } if (!ctx->completed) { // Make sure the complete request package is received if (ctx->recv_chunked && memcmp(buffer->str + buffer->length - (sizeof(SW_HTTP_CHUNK_EOF) - 1), SW_STRL(SW_HTTP_CHUNK_EOF)) != 0) { goto _recv_request; } if (buffer->length < total_length) { goto _recv_request; } size_t parsed_n = ctx->parse(buffer->str + buffer->offset, buffer->length - buffer->offset); buffer->offset += parsed_n; swoole_trace_log(SW_TRACE_CO_HTTP_SERVER, "parsed_n=%zu, length=%zu, offset=%jd, completed=%u", parsed_n, buffer->length, (intmax_t) buffer->offset, ctx->completed); if (ctx->parser.error != HPE_OK && ctx->parser.error != HPE_PAUSED_H2_UPGRADE) { ctx->response.status = SW_HTTP_BAD_REQUEST; break; } } zval *zserver = ctx->request.zserver; HashTable *ht = Z_ARRVAL_P(zserver); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), remote_addr.ptr()); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_PORT), (zend_long) remote_port); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server_addr.ptr()); http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PORT), (zend_long) server_port); remote_addr.add_ref(); server_addr.add_ref(); if (ctx->parser.error == HPE_PAUSED_H2_UPGRADE && buffer->length >= (sizeof(SW_HTTP2_PRI_STRING) - 1) && memcmp(buffer->str, SW_HTTP2_PRI_STRING, sizeof(SW_HTTP2_PRI_STRING) - 1) == 0) { buffer->offset = (sizeof(SW_HTTP2_PRI_STRING) - 1); hs->recv_http2_frame(ctx); /* ownership of ctx has been transferred */ ctx = nullptr; break; } http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP11)); zend::assign_zend_string_by_val(&ctx->request.zdata, buffer->pop(SW_BUFFER_SIZE_BIG), total_length); zend::Callable *cb = hs->get_handler(ctx); zval args[2] = {*ctx->request.zobject, *ctx->response.zobject}; bool keep_alive = !!llhttp_should_keep_alive(&ctx->parser) && !ctx->websocket; sock->get_socket()->recv_wait = 0; if (cb) { if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, 0))) { php_swoole_error(E_WARNING, "handler error"); } } else { ctx->response.status = SW_HTTP_NOT_FOUND; } zval_dtor(&args[0]); zval_dtor(&args[1]); ctx = nullptr; if (!hs->running || !keep_alive || php_swoole_socket_is_closed(zconn)) { break; } else { header_completed = false; if (buffer->length > 0) { goto _parse_request; } else { goto _recv_request; } } } if (ctx) { zval_dtor(ctx->request.zobject); zval_dtor(ctx->response.zobject); } zend_hash_index_del(Z_ARRVAL_P(&hs->zclients), co->get_cid()); } static PHP_METHOD(swoole_http_server_coro, shutdown) { HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); hs->running = false; hs->socket->cancel(SW_EVENT_READ); zend_ulong index; zval *zconn; ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(&hs->zclients), index, zconn) { SocketImpl *sock = php_swoole_get_socket(zconn); if (sock->get_socket()->recv_wait) { sock->cancel(SW_EVENT_READ); zend_hash_index_del(Z_ARRVAL_P(&hs->zclients), index); } } ZEND_HASH_FOREACH_END(); } static void http2_server_onRequest(const std::shared_ptr &session, const std::shared_ptr &stream) { HttpContext *ctx = stream->ctx; zval *zserver_http2 = ctx->request.zserver; zval *zserver_http1 = session->default_ctx->request.zserver; zend_string *key; zval *zvalue; ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zserver_http1), key, zvalue) { http_server_add_server_array(Z_ARRVAL_P(zserver_http2), key, zvalue); if (ZVAL_IS_STRING(zvalue)) { Z_TRY_ADDREF_P(zvalue); } } ZEND_HASH_FOREACH_END(); http_server_add_server_array(Z_ARRVAL_P(zserver_http2), SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP2)); const auto *hs = static_cast(session->private_data); zend::Callable *cb = hs->get_handler(ctx); zval args[2] = {*ctx->request.zobject, *ctx->response.zobject}; if (cb) { if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, true))) { stream->reset(SW_HTTP2_ERROR_INTERNAL_ERROR); php_swoole_error(E_WARNING, "%s->onRequest[v2] handler error", ZSTR_VAL(swoole_http_server_ce->name)); } } else { ctx->response.status = SW_HTTP_NOT_FOUND; } zval_ptr_dtor(&args[0]); zval_ptr_dtor(&args[1]); } ================================================ FILE: ext-src/swoole_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "swoole_memory.h" #include "swoole_lock.h" #include "swoole_util.h" #include "swoole_timer.h" #include BEGIN_EXTERN_C() #include "stubs/php_swoole_lock_arginfo.h" END_EXTERN_C() using swoole::Lock; using swoole::Mutex; #ifdef HAVE_SPINLOCK using swoole::SpinLock; #endif #ifdef HAVE_RWLOCK using swoole::RWLock; #endif static zend_class_entry *swoole_lock_ce; static zend_object_handlers swoole_lock_handlers; struct LockObject { Lock *lock; zend_object std; }; static sw_inline LockObject *lock_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_lock_handlers.offset); } static Lock *lock_get_ptr(const zval *zobject) { return lock_fetch_object(Z_OBJ_P(zobject))->lock; } static Lock *lock_get_and_check_ptr(const zval *zobject) { Lock *lock = lock_get_ptr(zobject); if (UNEXPECTED(!lock)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return lock; } static void lock_set_ptr(const zval *zobject, Lock *ptr) { lock_fetch_object(Z_OBJ_P(zobject))->lock = ptr; } static void lock_free_object(zend_object *object) { zend_object_std_dtor(object); } static zend_object *lock_create_object(zend_class_entry *ce) { auto *lock = static_cast(zend_object_alloc(sizeof(LockObject), ce)); zend_object_std_init(&lock->std, ce); object_properties_init(&lock->std, ce); lock->std.handlers = &swoole_lock_handlers; return &lock->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_lock, __construct); static PHP_METHOD(swoole_lock, lock); static PHP_METHOD(swoole_lock, unlock); SW_EXTERN_C_END // clang-format off static constexpr zend_function_entry swoole_lock_methods[] = { PHP_ME(swoole_lock, __construct, arginfo_class_Swoole_Lock___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_lock, lock, arginfo_class_Swoole_Lock_lock, ZEND_ACC_PUBLIC) PHP_ME(swoole_lock, unlock, arginfo_class_Swoole_Lock_unlock, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_lock_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_lock, "Swoole\\Lock", nullptr, swoole_lock_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_lock); SW_SET_CLASS_CLONEABLE(swoole_lock, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_lock, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_lock, lock_create_object, lock_free_object, LockObject, std); zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("MUTEX"), Lock::MUTEX); #ifdef HAVE_RWLOCK zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("RWLOCK"), Lock::RW_LOCK); #endif #ifdef HAVE_SPINLOCK zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("SPINLOCK"), Lock::SPIN_LOCK); #endif zend_declare_property_long(swoole_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); SW_REGISTER_LONG_CONSTANT("SWOOLE_MUTEX", Lock::MUTEX); #ifdef HAVE_RWLOCK SW_REGISTER_LONG_CONSTANT("SWOOLE_RWLOCK", Lock::RW_LOCK); #endif #ifdef HAVE_SPINLOCK SW_REGISTER_LONG_CONSTANT("SWOOLE_SPINLOCK", Lock::SPIN_LOCK); #endif } static PHP_METHOD(swoole_lock, __construct) { Lock *lock = lock_get_ptr(ZEND_THIS); if (lock != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend_long type = Lock::MUTEX; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); switch (type) { #ifdef HAVE_SPINLOCK case Lock::SPIN_LOCK: lock = new SpinLock(true); break; #endif #ifdef HAVE_RWLOCK case Lock::RW_LOCK: lock = new RWLock(true); break; #endif case Lock::MUTEX: lock = new Mutex(true); break; default: zend_throw_exception(swoole_exception_ce, "lock type[%d] is not support", type); RETURN_FALSE; break; } lock_set_ptr(ZEND_THIS, lock); RETURN_TRUE; } static PHP_METHOD(swoole_lock, lock) { zend_long operation = LOCK_EX; double timeout = -1; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(operation) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Lock *lock = lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->lock(operation, swoole::sec2msec(timeout))); } static PHP_METHOD(swoole_lock, unlock) { Lock *lock = lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->unlock()); } ================================================ FILE: ext-src/swoole_name_resolver.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_name_resolver_arginfo.h" END_EXTERN_C() using swoole::NameResolver; BEGIN_EXTERN_C() #include "ext/spl/php_spl.h" zend_class_entry *swoole_name_resolver_context_ce; zend_object_handlers swoole_name_resolver_context_handlers; struct ContextObject { NameResolver::Context *context; zend_object std; }; static zend_always_inline NameResolver::Context *swoole_name_resolver_context_get_handle(zend_object *object) { return reinterpret_cast(reinterpret_cast(object) - swoole_name_resolver_context_handlers.offset) ->context; } static zend_always_inline ContextObject *swoole_name_resolver_context_get_object(zend_object *object) { return reinterpret_cast(reinterpret_cast(object) - swoole_name_resolver_context_handlers.offset); } static zend_always_inline ContextObject *swoole_name_resolver_context_get_object_safe(zend_object *object) { NameResolver::Context *name_resolver_context = swoole_name_resolver_context_get_handle(object); if (UNEXPECTED(!name_resolver_context)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return swoole_name_resolver_context_get_object(object); } static zend_object *swoole_name_resolver_context_create_object(zend_class_entry *ce) { auto *name_resolver_context_object = static_cast(zend_object_alloc(sizeof(ContextObject), ce)); zend_object_std_init(&name_resolver_context_object->std, ce); object_properties_init(&name_resolver_context_object->std, ce); name_resolver_context_object->std.handlers = &swoole_name_resolver_context_handlers; name_resolver_context_object->context = new NameResolver::Context(); return &name_resolver_context_object->std; } static void swoole_name_resolver_context_free_object(zend_object *object) { ContextObject *name_resolver_context_object = swoole_name_resolver_context_get_object(object); delete name_resolver_context_object->context; zend_object_std_dtor(&name_resolver_context_object->std); } ZEND_METHOD(Swoole_NameResolver_Context, __construct) { zend_long family = AF_INET; zend_bool with_port = false; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(family) Z_PARAM_BOOL(with_port) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ContextObject *obj = swoole_name_resolver_context_get_object_safe(Z_OBJ_P(ZEND_THIS)); obj->context->with_port = with_port; obj->context->type = family; } void php_swoole_name_resolver_minit(int module_number) { SW_INIT_CLASS_ENTRY_STD( swoole_name_resolver_context, "Swoole\\NameResolver\\Context", class_Swoole_NameResolver_Context_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_name_resolver_context); SW_SET_CLASS_CLONEABLE(swoole_name_resolver_context, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_name_resolver_context, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_name_resolver_context, swoole_name_resolver_context_create_object, swoole_name_resolver_context_free_object, ContextObject, std); } PHP_FUNCTION(swoole_name_resolver_lookup) { char *name; size_t l_name; zval *zcontext; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(name, l_name) Z_PARAM_OBJECT(zcontext) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ContextObject *obj = swoole_name_resolver_context_get_object_safe(Z_OBJ_P(zcontext)); auto result = swoole_name_resolver_lookup(std::string(name, l_name), obj->context); RETURN_STRINGL(result.c_str(), result.length()); } PHP_FUNCTION(swoole_name_resolver_add) { zval *zresolver; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT(zresolver) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(php_swoole_name_resolver_add(zresolver)); } PHP_FUNCTION(swoole_name_resolver_remove) { zval *zresolver; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT(zresolver) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto hash_1 = sw_php_spl_object_hash(zresolver); bool found = false; swoole_name_resolver_each( [&found, hash_1, zresolver](const std::list::iterator &iter) -> swTraverseOperation { if (found) { return SW_TRAVERSE_STOP; } auto hash_2 = sw_php_spl_object_hash((zval *) iter->private_data); bool equals = zend_string_equals(hash_2, hash_1); zend_string_release(hash_2); if (iter->type == NameResolver::TYPE_PHP && iter->private_data && equals) { zval_dtor(zresolver); efree(iter->private_data); found = true; return SW_TRAVERSE_REMOVE; } else { return SW_TRAVERSE_KEEP; } }); zend_string_release(hash_1); RETURN_BOOL(found); } END_EXTERN_C() bool php_swoole_name_resolver_add(zval *zresolver) { auto ce = zend_lookup_class(SW_ZSTR_KNOWN(SW_ZEND_STR_CLASS_NAME_RESOLVER)); if (ce == nullptr) { php_swoole_fatal_error( E_WARNING, "Class \"%s\" not found", SW_ZSTR_KNOWN(SW_ZEND_STR_CLASS_NAME_RESOLVER)->val); return false; } if (!instanceof_function(Z_OBJCE_P(zresolver), ce)) { php_swoole_fatal_error(E_WARNING, "the given object is not an instance of %s", SW_ZSTR_KNOWN(SW_ZEND_STR_CLASS_NAME_RESOLVER)->val); return false; } zval_add_ref(zresolver); NameResolver resolver{php_swoole_name_resolver_lookup, sw_zval_dup(zresolver), NameResolver::TYPE_PHP}; swoole_name_resolver_add(resolver); return true; } std::string php_swoole_name_resolver_lookup(const std::string &name, NameResolver::Context *ctx, void *_resolver) { zval *zcluster_object; zval retval; zval *zresolver = (zval *) _resolver; if (!ctx->private_data) { _lookup: zval zname; ZVAL_STRINGL(&zname, name.c_str(), name.length()); zend_call_method_with_1_params(SW_Z8_OBJ_P(zresolver), nullptr, nullptr, "lookup", &retval, &zname); zval_dtor(&zname); if (Z_TYPE(retval) == IS_OBJECT) { ctx->private_data = zcluster_object = (zval *) ecalloc(1, sizeof(zval)); ctx->dtor = [](NameResolver::Context *ctx) { zval *_zcluster_object = (zval *) ctx->private_data; zval_dtor(_zcluster_object); efree(_zcluster_object); }; *zcluster_object = retval; ctx->cluster_ = true; ctx->final_ = false; } else if (Z_TYPE(retval) == IS_STRING) { ctx->final_ = true; ctx->cluster_ = false; return {Z_STRVAL(retval), Z_STRLEN(retval)}; } else { ctx->final_ = false; ctx->cluster_ = false; return ""; } } else { zcluster_object = (zval *) ctx->private_data; // no available node, resolve again sw_zend_call_method_with_0_params(zcluster_object, nullptr, nullptr, "count", &retval); if (zval_get_long(&retval) == 0) { ctx->dtor(ctx); ctx->private_data = nullptr; goto _lookup; } } sw_zend_call_method_with_0_params(zcluster_object, nullptr, nullptr, "pop", &retval); if (!ZVAL_IS_ARRAY(&retval)) { return ""; } zval *zhost = zend_hash_str_find(HASH_OF(&retval), ZEND_STRL("host")); if (zhost == nullptr || !ZVAL_IS_STRING(zhost)) { return ""; } std::string result(Z_STRVAL_P(zhost), Z_STRLEN_P(zhost)); if (ctx->with_port) { result.append(":"); zval *zport = zend_hash_str_find(HASH_OF(&retval), ZEND_STRL("port")); if (zport == nullptr) { return ""; } result.append(std::to_string(zval_get_long(zport))); } zval_ptr_dtor(&retval); return result; } NameResolver::Context *php_swoole_name_resolver_get_context(zval *zobject) { return swoole_name_resolver_context_get_handle(Z_OBJ_P(zobject)); } ================================================ FILE: ext-src/swoole_odbc.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_odbc.h" #include "php_swoole_cxx.h" #include "php_swoole_private.h" #include "php_swoole_cxx.h" #include "swoole_coroutine_system.h" #ifdef SW_USE_ODBC static bool swoole_odbc_blocking = true; #ifdef SQL_ATTR_CONNECTION_POOLING zend_ulong pdo_odbc_pool_on = SQL_CP_OFF; zend_ulong pdo_odbc_pool_mode = SQL_CP_ONE_PER_HENV; #endif void swoole_odbc_set_blocking(bool blocking) { swoole_odbc_blocking = blocking; } RETCODE swoole_odbc_SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR *ServerName, SQLSMALLINT NameLength1, SQLCHAR *UserName, SQLSMALLINT NameLength2, SQLCHAR *Authentication, SQLSMALLINT NameLength3) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLConnect(server=%s)", ServerName); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLConnect(ConnectionHandle, ServerName, NameLength1, UserName, NameLength2, Authentication, NameLength3); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLDriverConnect(SQLHDBC hdbc, SQLHWND hwnd, SQLCHAR *szConnStrIn, SQLSMALLINT cbConnStrIn, SQLCHAR *szConnStrOut, SQLSMALLINT cbConnStrOutMax, SQLSMALLINT *pcbConnStrOut, SQLUSMALLINT fDriverCompletion) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLDriverConnect"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLDriverConnect( hdbc, hwnd, szConnStrIn, cbConnStrIn, szConnStrOut, cbConnStrOutMax, pcbConnStrOut, fDriverCompletion); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLExecDirect"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLExecDirect(StatementHandle, StatementText, TextLength); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLGetInfo(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, SQLPOINTER InfoValue, SQLSMALLINT BufferLength, SQLSMALLINT *StringLength) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLGetInfo"); rc = SQLGetInfo(ConnectionHandle, InfoType, InfoValue, BufferLength, StringLength); return rc; } SQLRETURN SQL_API swoole_odbc_SQLGetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT RecNumber, SQLCHAR *Sqlstate, SQLINTEGER *NativeError, SQLCHAR *MessageText, SQLSMALLINT BufferLength, SQLSMALLINT *TextLength) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLGetInfo"); rc = SQLGetDiagRec(HandleType, Handle, RecNumber, Sqlstate, NativeError, MessageText, BufferLength, TextLength); return rc; } SQLRETURN SQL_API swoole_odbc_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPrepare(StatementText=%s)", StatementText); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLPrepare(StatementHandle, StatementText, TextLength); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLExecute(SQLHSTMT StatementHandle) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLExecute"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLExecute(StatementHandle); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLCloseCursor(SQLHSTMT StatementHandle) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLCloseCursor"); rc = SQLCloseCursor(StatementHandle); return rc; } SQLRETURN SQL_API swoole_odbc_SQLPutData(SQLHSTMT StatementHandle, SQLPOINTER Data, SQLLEN StrLen_or_Ind) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPutData"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLPutData(StatementHandle, Data, StrLen_or_Ind); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLGetData(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValue, SQLLEN BufferLength, SQLLEN *StrLen_or_Ind) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPutData"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLGetData(StatementHandle, ColumnNumber, TargetType, TargetValue, BufferLength, StrLen_or_Ind); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLMoreResults(SQLHSTMT hstmt) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLMoreResults"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLMoreResults(hstmt); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLDescribeCol(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLCHAR *ColumnName, SQLSMALLINT BufferLength, SQLSMALLINT *NameLength, SQLSMALLINT *DataType, SQLULEN *ColumnSize, SQLSMALLINT *DecimalDigits, SQLSMALLINT *Nullable) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLMoreResults"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLDescribeCol(StatementHandle, ColumnNumber, ColumnName, BufferLength, NameLength, DataType, ColumnSize, DecimalDigits, Nullable); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLRowCount(SQLHSTMT StatementHandle, SQLLEN *RowCount) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLRowCount"); rc = SQLRowCount(StatementHandle, RowCount); return rc; } SQLRETURN SQL_API swoole_odbc_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLFreeHandle"); rc = SQLFreeHandle(HandleType, Handle); return rc; } SQLRETURN SQL_API swoole_odbc_SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT CompletionType) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLEndTran(CompletionType=%d)", CompletionType); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLEndTran(HandleType, Handle, CompletionType); }); return rc; } SQLRETURN SQL_API swoole_odbc_SQLDisconnect(SQLHDBC ConnectionHandle) { RETCODE rc; swoole_trace_log(SW_TRACE_CO_ODBC, "SQLDisconnect"); php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLDisconnect(ConnectionHandle); }); return rc; } int php_swoole_odbc_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("ODBC_ATTR_USE_CURSOR_LIBRARY")) == nullptr) { #ifdef SQL_ATTR_CONNECTION_POOLING const char *pooling_val = NULL; #endif #ifdef SQL_ATTR_CONNECTION_POOLING /* ugh, we don't really like .ini stuff in PDO, but since ODBC connection * pooling is process wide, we can't set it from within the scope of a * request without affecting others, which goes against our isolated request * policy. So, we use cfg_get_string here to check it this once. * */ if (FAILURE == cfg_get_string("pdo_odbc.connection_pooling", (char **) &pooling_val) || pooling_val == NULL) { pooling_val = "strict"; } if (strcasecmp(pooling_val, "strict") == 0 || strcmp(pooling_val, "1") == 0) { pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; pdo_odbc_pool_mode = SQL_CP_STRICT_MATCH; } else if (strcasecmp(pooling_val, "relaxed") == 0) { pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; pdo_odbc_pool_mode = SQL_CP_RELAXED_MATCH; } else if (*pooling_val == '\0' || strcasecmp(pooling_val, "off") == 0) { pdo_odbc_pool_on = SQL_CP_OFF; } else { php_error_docref(NULL, E_CORE_ERROR, "Error in pdo_odbc.connection_pooling configuration. Value must be one of \"strict\", " "\"relaxed\", or \"off\""); return FAILURE; } if (pdo_odbc_pool_on != SQL_CP_OFF) { SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void *) pdo_odbc_pool_on, 0); } #endif #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); #else REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); #endif } php_pdo_unregister_driver(&swoole_pdo_odbc_driver); php_pdo_register_driver(&swoole_pdo_odbc_driver); return SUCCESS; } void php_swoole_odbc_mshutdown(void) { php_pdo_unregister_driver(&swoole_pdo_odbc_driver); } #endif ================================================ FILE: ext-src/swoole_oracle.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "php_swoole_cxx.h" #include "swoole_coroutine.h" #include "php_swoole_oracle.h" #ifdef SW_USE_ORACLE static bool swoole_oracle_blocking = true; void swoole_oracle_set_blocking(bool blocking) { swoole_oracle_blocking = blocking; } sword swoole_oci_session_begin(OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 credt, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_session_begin"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCISessionBegin(svchp, errhp, usrhp, credt, mode); }); return result; } sword swoole_oci_server_detach(OCIServer *srvhp, OCIError *errhp, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_server_detach"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIServerDetach(srvhp, errhp, mode); }); return result; } sword swoole_oci_stmt_prepare( OCIStmt *stmtp, OCIError *errhp, const OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_prepare"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIStmtPrepare(stmtp, errhp, stmt, stmt_len, language, mode); }); return result; } sword swoole_oci_stmt_execute(OCISvcCtx *svchp, OCIStmt *stmtp, OCIError *errhp, ub4 iters, ub4 rowoff, const OCISnapshot *snap_in, OCISnapshot *snap_out, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_execute"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIStmtExecute(svchp, stmtp, errhp, iters, rowoff, snap_in, snap_out, mode); }); return result; } sword swoole_oci_stmt_fetch(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_fetch"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIStmtFetch(stmtp, errhp, nrows, orientation, mode); }); return result; } sword swoole_oci_stmt_fetch2(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, sb4 scrollOffset, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_fetch2"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIStmtFetch2(stmtp, errhp, nrows, orientation, scrollOffset, mode); }); return result; } sword swoole_oci_trans_commit(OCISvcCtx *svchp, OCIError *errhp, ub4 flags) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_trans_commit"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCITransCommit(svchp, errhp, flags); }); return result; } sword swoole_oci_trans_rollback(OCISvcCtx *svchp, OCIError *errhp, ub4 flags) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_trans_rollback"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCITransRollback(svchp, errhp, flags); }); return result; } sword swoole_oci_ping(OCISvcCtx *svchp, OCIError *errhp, ub4 mode) { swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_ping"); sword result = 0; php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIPing(svchp, errhp, mode); }); return result; } const ub4 SWOOLE_PDO_OCI_INIT_MODE = OCI_DEFAULT | OCI_THREADED #ifdef OCI_OBJECT | OCI_OBJECT #endif ; OCIEnv *swoole_pdo_oci_Env = NULL; void php_swoole_oracle_rinit() { if (!swoole_pdo_oci_Env) { #ifdef HAVE_OCIENVCREATE OCIEnvCreate(&swoole_pdo_oci_Env, SWOOLE_PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL, 0, NULL); #else OCIInitialize(SWOOLE_PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL); OCIEnvInit(&swoole_pdo_oci_Env, OCI_DEFAULT, 0, NULL); #endif } } void php_swoole_oracle_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("OCI_ATTR_ACTION")) == nullptr) { #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); #else REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); #endif } php_pdo_unregister_driver(&swoole_pdo_oci_driver); php_pdo_register_driver(&swoole_pdo_oci_driver); } void php_swoole_oracle_mshutdown(void) { php_pdo_unregister_driver(&swoole_pdo_oci_driver); if (!swoole_pdo_oci_Env) { OCIHandleFree((dvoid *) swoole_pdo_oci_Env, OCI_HTYPE_ENV); } } #endif ================================================ FILE: ext-src/swoole_pgsql.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_pgsql.h" #include "php_swoole_private.h" #include "php_swoole_coroutine.h" #include "swoole_socket_impl.h" #ifdef SW_USE_PGSQL using swoole::Coroutine; using swoole::EventType; using swoole::Reactor; using swoole::translate_events_to_poll; static bool swoole_pgsql_blocking = true; /** * In macOS, under special circumstances, the poll function will not return a readable or writable signal. It is * necessary to set a relatively short poll timeout value to allow it to fail quickly. Then, re-execute PQconnectPoll, * and let the libpq underlying code determine whether the connection is ready or if an error has occurred. */ static const double swoole_pgsql_poll_timeout = 0.1; void swoole_libpq_version(char *buf, size_t len) { int version = PQlibVersion(); int major = version / 10000; if (major >= 10) { int minor = version % 10000; snprintf(buf, len, "%d.%d", major, minor); } else { int minor = version / 100 % 100; int revision = version % 100; snprintf(buf, len, "%d.%d.%d", major, minor, revision); } } static int swoole_pgsql_socket_poll(PGconn *conn, EventType event, bool check_nonblock = false) { if (swoole_pgsql_blocking) { struct pollfd fds[1]; fds[0].fd = PQsocket(conn); fds[0].events |= translate_events_to_poll(event); int result = 0; do { result = poll(fds, 1, swoole_pgsql_poll_timeout * 1000); } while (result < 0 && errno == EINTR); return result; } SocketImpl sock(PQsocket(conn), SW_SOCK_RAW); sock.get_socket()->nonblock = 1; bool retval = sock.poll(event, swoole_pgsql_poll_timeout); while (check_nonblock && event == SW_EVENT_READ) { if (PQconsumeInput(conn) == 0) { retval = false; break; } if (PQisBusy(conn) == 0) { break; } retval = sock.poll(event, swoole_pgsql_poll_timeout); } sock.move_fd(); return retval ? 1 : sock.errCode == ETIMEDOUT ? 0 : -1; } static int swoole_pgsql_flush(PGconn *conn) { int flush_ret; do { int ret = swoole_pgsql_socket_poll(conn, SW_EVENT_WRITE); if (sw_unlikely(ret < 0)) { return -1; } swoole_trace_log(SW_TRACE_CO_PGSQL, "PQflush(conn=%p)", conn); flush_ret = PQflush(conn); } while (flush_ret == 1); return flush_ret; } static PGresult *swoole_pgsql_get_result(PGconn *conn) { PGresult *result, *last_result = nullptr; // PQgetResult will block the process; it is necessary to forcibly check if the data is ready. int poll_ret = swoole_pgsql_socket_poll(conn, SW_EVENT_READ, true); if (sw_unlikely(poll_ret < 0)) { return nullptr; } swoole_trace_log(SW_TRACE_CO_PGSQL, "PQgetResult(conn=%p)", conn); while ((result = PQgetResult(conn))) { PQclear(last_result); last_result = result; } return last_result; } PGconn *swoole_pgsql_connectdb(const char *conninfo) { PGconn *conn = PQconnectStart(conninfo); if (conn == nullptr) { return nullptr; } int fd = PQsocket(conn); if (sw_unlikely(fd < 0)) { return conn; } if (!swoole_pgsql_blocking && Coroutine::get_current()) { PQsetnonblocking(conn, 1); } else { PQsetnonblocking(conn, 0); } SW_LOOP { int r = PQconnectPoll(conn); if (r == PGRES_POLLING_OK || r == PGRES_POLLING_FAILED) { break; } EventType event; switch (r) { case PGRES_POLLING_READING: event = SW_EVENT_READ; break; case PGRES_POLLING_WRITING: event = SW_EVENT_WRITE; break; default: // should not be here including PGRES_POLLING_ACTIVE abort(); break; } if (swoole_pgsql_socket_poll(conn, event) < 0) { break; } } return conn; } PGresult *swoole_pgsql_prepare( PGconn *conn, const char *stmt_name, const char *query, int n_params, const Oid *param_types) { swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendPrepare(conn=%p, stmt_name='%s')", conn, stmt_name); int ret = PQsendPrepare(conn, stmt_name, query, n_params, param_types); if (ret == 0) { return nullptr; } if (swoole_pgsql_flush(conn) == -1) { return nullptr; } return swoole_pgsql_get_result(conn); } PGresult *swoole_pgsql_exec_prepared(PGconn *conn, const char *stmt_name, int n_params, const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format) { swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQueryPrepared(conn=%p, stmt_name='%s')", conn, stmt_name); int ret = PQsendQueryPrepared(conn, stmt_name, n_params, param_values, param_lengths, param_formats, result_format); if (ret == 0) { return nullptr; } if (swoole_pgsql_flush(conn) == -1) { return nullptr; } return swoole_pgsql_get_result(conn); } PGresult *swoole_pgsql_exec(PGconn *conn, const char *query) { swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQuery(conn=%p, query='%s')", conn, query); int ret = PQsendQuery(conn, query); if (ret == 0) { return nullptr; } if (swoole_pgsql_flush(conn) == -1) { return nullptr; } return swoole_pgsql_get_result(conn); } PGresult *swoole_pgsql_exec_params(PGconn *conn, const char *command, int n_params, const Oid *param_types, const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format) { swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQueryParams(conn=%p, command='%s')", conn, command); int ret = PQsendQueryParams( conn, command, n_params, param_types, param_values, param_lengths, param_formats, result_format); if (ret == 0) { return nullptr; } if (swoole_pgsql_flush(conn) == -1) { return nullptr; } return swoole_pgsql_get_result(conn); } void swoole_pgsql_set_blocking(bool blocking) { swoole_pgsql_blocking = blocking; } void php_swoole_pgsql_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("PGSQL_ATTR_DISABLE_PREPARES")) == nullptr) { #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); #else REGISTER_PDO_CLASS_CONST_LONG("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); #endif } php_pdo_unregister_driver(&swoole_pdo_pgsql_driver); php_pdo_register_driver(&swoole_pdo_pgsql_driver); } void php_swoole_pgsql_mshutdown(void) { php_pdo_unregister_driver(&swoole_pdo_pgsql_driver); } #endif ================================================ FILE: ext-src/swoole_process.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_process.h" #include "swoole_server.h" #include "swoole_msg_queue.h" #include "swoole_signal.h" #include #include BEGIN_EXTERN_C() #include "stubs/php_swoole_process_arginfo.h" END_EXTERN_C() using namespace swoole; zend_class_entry *swoole_process_ce; static zend_object_handlers swoole_process_handlers; static uint32_t round_process_id = 0; static thread_local uint32_t server_user_worker_id = 0; static zend::Callable *signal_fci_caches[SW_SIGNO_MAX] = {}; struct ProcessObject { Worker *worker; zend_object *zsocket; PipeType pipe_type; bool enable_coroutine; bool blocking; zend_object std; }; static sw_inline ProcessObject *php_swoole_process_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_process_handlers.offset); } static sw_inline ProcessObject *php_swoole_process_fetch_object(const zval *zobj) { return php_swoole_process_fetch_object(Z_OBJ_P(zobj)); } Worker *php_swoole_process_get_worker(const zval *zobject) { return php_swoole_process_fetch_object(zobject)->worker; } Worker *php_swoole_process_get_and_check_worker(const zval *zobject) { Worker *worker = php_swoole_process_get_worker(zobject); if (UNEXPECTED(!worker)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return worker; } void php_swoole_process_set_worker(const zval *zobject, Worker *worker, bool enable_coroutine, int pipe_type) { auto po = php_swoole_process_fetch_object(zobject); po->worker = worker; po->pipe_type = static_cast(pipe_type); po->enable_coroutine = enable_coroutine; po->blocking = true; } static void php_swoole_process_free_object(zend_object *object) { ProcessObject *po = php_swoole_process_fetch_object(object); Worker *worker = po->worker; if (worker) { UnixSocket *_pipe = worker->pipe_object; if (_pipe && !worker->shared) { delete _pipe; } delete worker->queue; delete worker; } if (po->zsocket) { OBJ_RELEASE(po->zsocket); } zend_object_std_dtor(object); } static zend_object *php_swoole_process_create_object(zend_class_entry *ce) { auto *process = static_cast(zend_object_alloc(sizeof(ProcessObject), ce)); zend_object_std_init(&process->std, ce); object_properties_init(&process->std, ce); process->std.handlers = &swoole_process_handlers; return &process->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_process, __construct); static PHP_METHOD(swoole_process, __destruct); static PHP_METHOD(swoole_process, useQueue); static PHP_METHOD(swoole_process, statQueue); static PHP_METHOD(swoole_process, freeQueue); static PHP_METHOD(swoole_process, pop); static PHP_METHOD(swoole_process, push); static PHP_METHOD(swoole_process, kill); static PHP_METHOD(swoole_process, signal); static PHP_METHOD(swoole_process, alarm); static PHP_METHOD(swoole_process, wait); static PHP_METHOD(swoole_process, daemon); #ifdef HAVE_CPU_AFFINITY static PHP_METHOD(swoole_process, setAffinity); static PHP_METHOD(swoole_process, getAffinity); #endif static PHP_METHOD(swoole_process, set); static PHP_METHOD(swoole_process, setTimeout); static PHP_METHOD(swoole_process, setBlocking); static PHP_METHOD(swoole_process, setPriority); static PHP_METHOD(swoole_process, getPriority); static PHP_METHOD(swoole_process, start); static PHP_METHOD(swoole_process, write); static PHP_METHOD(swoole_process, read); static PHP_METHOD(swoole_process, close); static PHP_METHOD(swoole_process, exit); static PHP_METHOD(swoole_process, exec); static PHP_METHOD(swoole_process, exportSocket); SW_EXTERN_C_END static void php_swoole_onSignal(int signo); // clang-format off #define MSGQUEUE_NOWAIT (1 << 8) static const zend_function_entry swoole_process_methods[] = { PHP_ME(swoole_process, __construct, arginfo_class_Swoole_Process___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, __destruct, arginfo_class_Swoole_Process___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, wait, arginfo_class_Swoole_Process_wait, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_process, signal, arginfo_class_Swoole_Process_signal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_process, alarm, arginfo_class_Swoole_Process_alarm, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_process, kill, arginfo_class_Swoole_Process_kill, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_process, daemon, arginfo_class_Swoole_Process_daemon, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #ifdef HAVE_CPU_AFFINITY PHP_ME(swoole_process, setAffinity, arginfo_class_Swoole_Process_setAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_process, getAffinity, arginfo_class_Swoole_Process_getAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #endif PHP_ME(swoole_process, setPriority, arginfo_class_Swoole_Process_setPriority, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, getPriority, arginfo_class_Swoole_Process_getPriority, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, set, arginfo_class_Swoole_Process_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, setTimeout, arginfo_class_Swoole_Process_setTimeout, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, setBlocking, arginfo_class_Swoole_Process_setBlocking, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, useQueue, arginfo_class_Swoole_Process_useQueue, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, statQueue, arginfo_class_Swoole_Process_statQueue, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, freeQueue, arginfo_class_Swoole_Process_freeQueue, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, start, arginfo_class_Swoole_Process_start, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, write, arginfo_class_Swoole_Process_write, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, close, arginfo_class_Swoole_Process_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, read, arginfo_class_Swoole_Process_read, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, push, arginfo_class_Swoole_Process_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, pop, arginfo_class_Swoole_Process_pop, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, exit, arginfo_class_Swoole_Process_exit, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, exec, arginfo_class_Swoole_Process_exec, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, exportSocket, arginfo_class_Swoole_Process_exportSocket, ZEND_ACC_PUBLIC) PHP_FALIAS(name, swoole_set_process_name, arginfo_class_Swoole_Process_name) PHP_FE_END }; // clang-format on void php_swoole_process_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_process, "Swoole\\Process", nullptr, swoole_process_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_process); SW_SET_CLASS_CLONEABLE(swoole_process, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_process, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_process, php_swoole_process_create_object, php_swoole_process_free_object, ProcessObject, std); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("IPC_NOWAIT"), MSGQUEUE_NOWAIT); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_MASTER"), SW_PIPE_CLOSE_MASTER); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_WORKER"), SW_PIPE_CLOSE_WORKER); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_READ"), SW_PIPE_CLOSE_READ); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_WRITE"), SW_PIPE_CLOSE_WRITE); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_NONE"), PIPE_TYPE_NONE); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_STREAM"), PIPE_TYPE_STREAM); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_DGRAM"), PIPE_TYPE_DGRAM); zend_declare_property_null(swoole_process_ce, ZEND_STRL("pipe"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("msgQueueId"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("msgQueueKey"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("pid"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("id"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("callback"), ZEND_ACC_PRIVATE); /** * 31 signal constants */ if (!zend_hash_str_find(&module_registry, ZEND_STRL("pcntl"))) { REGISTER_LONG_CONSTANT("SIGHUP", SIGHUP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGINT", SIGINT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGQUIT", SIGQUIT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGILL", SIGILL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGTRAP", SIGTRAP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGABRT", SIGABRT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGBUS", SIGBUS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGFPE", SIGFPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGKILL", SIGKILL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGUSR1", SIGUSR1, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGSEGV", SIGSEGV, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGUSR2", SIGUSR2, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGPIPE", SIGPIPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGALRM", SIGALRM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGTERM", SIGTERM, CONST_CS | CONST_PERSISTENT); #ifdef SIGSTKFLT REGISTER_LONG_CONSTANT("SIGSTKFLT", SIGSTKFLT, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SIGCHLD", SIGCHLD, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGCONT", SIGCONT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGSTOP", SIGSTOP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGTSTP", SIGTSTP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGTTIN", SIGTTIN, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGTTOU", SIGTTOU, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGURG", SIGURG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGXCPU", SIGXCPU, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGXFSZ", SIGXFSZ, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGVTALRM", SIGVTALRM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGPROF", SIGPROF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGWINCH", SIGWINCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SIGIO", SIGIO, CONST_CS | CONST_PERSISTENT); #ifdef SIGPWR REGISTER_LONG_CONSTANT("SIGPWR", SIGPWR, CONST_CS | CONST_PERSISTENT); #endif #ifdef SIGSYS REGISTER_LONG_CONSTANT("SIGSYS", SIGSYS, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SIG_IGN", (zend_long) SIG_IGN, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PRIO_PROCESS", PRIO_PROCESS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PRIO_PGRP", PRIO_PGRP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PRIO_USER", PRIO_USER, CONST_CS | CONST_PERSISTENT); } SW_REGISTER_LONG_CONSTANT("SWOOLE_MSGQUEUE_ORIENT", SW_MSGQUEUE_ORIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_MSGQUEUE_BALANCE", SW_MSGQUEUE_BALANCE); } static PHP_METHOD(swoole_process, __construct) { auto po = php_swoole_process_fetch_object(ZEND_THIS); Server *server = sw_server(); if (po->worker) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } // only cli env if (!SWOOLE_G(cli)) { zend_throw_error(nullptr, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (server && server->is_started() && server->is_master()) { zend_throw_error(nullptr, "%s can't be used in master process", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (SwooleTG.async_threads) { zend_throw_error(nullptr, "unable to create %s with async-io threads", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend::Function func; zend_bool redirect_stdin_and_stdout = false; zend_long pipe_type = PIPE_TYPE_DGRAM; zend_bool enable_coroutine = false; po->worker = new Worker(); Worker *process = po->worker; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_FUNC(func.fci, func.fci_cache); Z_PARAM_OPTIONAL Z_PARAM_BOOL(redirect_stdin_and_stdout) Z_PARAM_LONG(pipe_type) Z_PARAM_BOOL(enable_coroutine) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (server && server->is_worker_thread()) { Worker *shared_worker; if (server->is_user_worker()) { shared_worker = server->get_worker(swoole_get_worker_id()); } else { shared_worker = server->get_worker((server_user_worker_id++) + server->get_core_worker_num()); } *process = *shared_worker; process->shared = true; if (server->is_user_worker()) { process->pipe_current = process->pipe_worker; } else { process->pipe_current = process->pipe_master; } } else { if (redirect_stdin_and_stdout) { process->redirect_stdin = true; process->redirect_stdout = true; process->redirect_stderr = true; /** * Forced to use stream pipe */ pipe_type = PIPE_TYPE_STREAM; } uint32_t base = 1; if (server && server->is_started()) { base = server->get_all_worker_num(); } if (round_process_id == 0) { round_process_id = base; } process->id = round_process_id++; process->shared = false; if (pipe_type > 0) { int socket_type = pipe_type == PIPE_TYPE_STREAM ? SOCK_STREAM : SOCK_DGRAM; auto *_pipe = new UnixSocket(true, socket_type); if (!_pipe->ready()) { zend_throw_exception(swoole_exception_ce, "failed to create unix soccket", errno); delete _pipe; efree(process); RETURN_FALSE; } process->pipe_master = _pipe->get_socket(true); process->pipe_worker = _pipe->get_socket(false); process->pipe_object = _pipe; process->pipe_current = process->pipe_master; zend_update_property_long( swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("pipe"), process->pipe_master->fd); } } zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("id"), process->id); zend_update_property( swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("callback"), ZEND_CALL_ARG(execute_data, 1)); php_swoole_process_set_worker(ZEND_THIS, process, enable_coroutine, pipe_type); } static PHP_METHOD(swoole_process, __destruct) {} static PHP_METHOD(swoole_process, wait) { zend_bool blocking = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &blocking) == FAILURE) { RETURN_FALSE; } auto exit_status = swoole::wait_process(-1, blocking ? 0 : WNOHANG); if (exit_status.get_pid() > 0) { array_init(return_value); add_assoc_long(return_value, "pid", exit_status.get_pid()); add_assoc_long(return_value, "code", exit_status.get_code()); add_assoc_long(return_value, "signal", exit_status.get_signal()); } else { RETURN_FALSE; } } static PHP_METHOD(swoole_process, useQueue) { long msgkey = 0; long mode = SW_MSGQUEUE_BALANCE; long capacity = -1; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lll", &msgkey, &mode, &capacity) == FAILURE) { RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (msgkey <= 0) { msgkey = ftok(zend_get_executed_filename(), 1); } auto *queue = new MsgQueue(msgkey); if (!queue->ready()) { delete queue; RETURN_FALSE; } if (mode & MSGQUEUE_NOWAIT) { queue->set_blocking(false); mode = mode & (~MSGQUEUE_NOWAIT); } if (capacity > 0) { queue->set_capacity(capacity); } process->queue = queue; process->msgqueue_mode = mode; zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("msgQueueId"), queue->get_id()); zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("msgQueueKey"), msgkey); RETURN_TRUE; } static PHP_METHOD(swoole_process, statQueue) { Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (!process->queue) { php_swoole_fatal_error(E_WARNING, "no queue, can't get stats of the queue"); RETURN_FALSE; } size_t queue_num = -1; size_t queue_bytes = -1; if (process->queue->stat(&queue_num, &queue_bytes)) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("queue_num"), queue_num); add_assoc_long_ex(return_value, ZEND_STRL("queue_bytes"), queue_bytes); } else { RETURN_FALSE; } } static PHP_METHOD(swoole_process, freeQueue) { Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->queue && process->queue->destroy()) { delete process->queue; process->queue = nullptr; RETURN_TRUE; } else { RETURN_FALSE; } } static PHP_METHOD(swoole_process, kill) { zend_long pid; zend_long sig = SIGTERM; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &pid, &sig) == FAILURE) { RETURN_FALSE; } int ret = swoole_kill((int) pid, (int) sig); if (ret < 0) { if (!(sig == 0 && errno == ESRCH)) { php_swoole_sys_error(E_WARNING, "kill(%d, %d) failed", (int) pid, (int) sig); } RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_process, signal) { SW_MUST_BE_MAIN_THREAD(); zend_long signo = 0; zval *zcallback = nullptr; zend::Callable *fci_cache = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(signo) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(zcallback, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (!SWOOLE_G(cli)) { php_swoole_fatal_error(E_ERROR, "%s::signal can only be used in CLI mode", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (signo < 0 || signo >= SW_SIGNO_MAX) { php_swoole_fatal_error(E_WARNING, "invalid signal number [" ZEND_LONG_FMT "]", signo); RETURN_FALSE; } swSignalHandler handler = swoole_signal_get_handler(signo); if (handler && handler != php_swoole_onSignal) { php_swoole_fatal_error( E_WARNING, "signal [" ZEND_LONG_FMT "] processor has been registered by the system", signo); RETURN_FALSE; } if (zcallback == nullptr) { fci_cache = signal_fci_caches[signo]; if (fci_cache) { #ifdef SW_USE_THREAD_CONTEXT swoole_event_defer([signo](void *) { swoole_signal_set(signo, nullptr); }, nullptr); #else swoole_signal_set(signo, nullptr); #endif signal_fci_caches[signo] = nullptr; swoole_event_defer(sw_callable_free, fci_cache); SwooleG.signal_listener_num--; RETURN_TRUE; } else { php_swoole_error(E_WARNING, "unable to find the callback of signal [" ZEND_LONG_FMT "]", signo); RETURN_FALSE; } } else if (Z_TYPE_P(zcallback) == IS_LONG && Z_LVAL_P(zcallback) == (zend_long) SIG_IGN) { handler = nullptr; } else { fci_cache = sw_callable_create(zcallback); if (!fci_cache) { RETURN_FALSE; } handler = php_swoole_onSignal; } if (sw_server() && sw_server()->is_sync_process()) { if (signal_fci_caches[signo]) { sw_callable_free(signal_fci_caches[signo]); } else { SwooleG.signal_listener_num++; } signal_fci_caches[signo] = fci_cache; #ifdef SW_USE_THREAD_CONTEXT swoole_event_defer([signo, handler](void *) { swoole_signal_set(signo, handler, true); }, nullptr); #else swoole_signal_set(signo, handler, true); #endif RETURN_TRUE; } php_swoole_check_reactor(); if (!SwooleTG.reactor->isset_exit_condition(Reactor::EXIT_CONDITION_SIGNAL_LISTENER)) { SwooleTG.reactor->set_exit_condition(Reactor::EXIT_CONDITION_SIGNAL_LISTENER, [](Reactor *reactor, size_t &event_num) -> bool { return SwooleG.signal_listener_num == 0 or !SwooleG.wait_signal; }); } if (signal_fci_caches[signo]) { // free the old fci_cache swoole_event_defer(sw_callable_free, signal_fci_caches[signo]); } else { SwooleG.signal_listener_num++; } signal_fci_caches[signo] = fci_cache; #ifdef SW_USE_THREAD_CONTEXT swoole_event_defer([signo, handler](void *) { swoole_signal_set(signo, handler, true); }, nullptr); #else swoole_signal_set(signo, handler, true); #endif RETURN_TRUE; } static PHP_METHOD(swoole_process, alarm) { zend_long usec; zend_long type = ITIMER_REAL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &usec, &type) == FAILURE) { RETURN_FALSE; } if (!SWOOLE_G(cli)) { php_swoole_fatal_error(E_ERROR, "cannot use %s::alarm here", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (SwooleTG.timer) { php_swoole_fatal_error(E_WARNING, "cannot use both 'timer' and 'alarm' at the same time"); RETURN_FALSE; } struct itimerval timer_set = {}; if (usec > 0) { long _sec = usec / 1000000; long _usec = usec - (_sec * 1000000); timer_set.it_interval.tv_sec = _sec; timer_set.it_interval.tv_usec = _usec; timer_set.it_value.tv_sec = _sec; timer_set.it_value.tv_usec = _usec; if (timer_set.it_value.tv_usec > 1e6) { timer_set.it_value.tv_usec = timer_set.it_value.tv_usec - 1e6; timer_set.it_value.tv_sec += 1; } } if (setitimer(type, &timer_set, nullptr) < 0) { php_swoole_sys_error(E_WARNING, "setitimer() failed"); RETURN_FALSE; } RETURN_TRUE; } /** * safe signal */ static void php_swoole_onSignal(int signo) { auto fci_cache = signal_fci_caches[signo]; if (fci_cache) { zval argv[1]; ZVAL_LONG(&argv[0], signo); if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error( E_WARNING, "%s: signal [%d] handler error", ZSTR_VAL(swoole_process_ce->name), signo); } } } zend_bool php_swoole_signal_isset_handler(int signo) { if (signo < 0 || signo >= SW_SIGNO_MAX) { php_swoole_fatal_error(E_WARNING, "invalid signal number [%d]", signo); return false; } return signal_fci_caches[signo] != nullptr; } void php_swoole_process_clean() { for (auto &signal_fci_cache : signal_fci_caches) { const auto fci_cache = signal_fci_cache; if (fci_cache) { sw_callable_free(fci_cache); signal_fci_cache = nullptr; } } #ifndef SW_THREAD if (swoole_get_worker_type() != SW_USER_WORKER) { swoole_set_worker_type(0); } #endif } void php_swoole_process_rshutdown() { php_swoole_process_clean(); } int php_swoole_process_start(Worker *process, zval *zobject) { zval *zcallback = sw_zend_read_property_ex(swoole_process_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_CALLBACK), 0); auto fci_cache = sw_callable_create(zcallback); if (!fci_cache) { return SW_ERR; } process->pipe_current = process->pipe_worker; process->pid = getpid(); if (process->redirect_stdin) { if (dup2(process->pipe_current->fd, STDIN_FILENO) < 0) { php_swoole_sys_error(E_WARNING, "dup2() failed"); } } if (process->redirect_stdout) { if (dup2(process->pipe_current->fd, STDOUT_FILENO) < 0) { php_swoole_sys_error(E_WARNING, "dup2() failed"); } } if (process->redirect_stderr) { if (dup2(process->pipe_current->fd, STDERR_FILENO) < 0) { php_swoole_sys_error(E_WARNING, "dup2() failed"); } } php_swoole_process_clean(); swoole_set_worker_id(process->id); swoole_set_worker_pid(getpid()); SwooleWG.worker = process; zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("pid"), process->pid); if (process->pipe_current) { zend_update_property_long( swoole_process_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("pipe"), process->pipe_current->fd); } auto po = php_swoole_process_fetch_object(zobject); // eventloop create if (po->enable_coroutine && php_swoole_reactor_init() < 0) { sw_callable_free(fci_cache); return SW_ERR; } // main function if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 1, zobject, nullptr, po->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } // eventloop start if (po->enable_coroutine) { php_swoole_event_wait(); } sw_callable_free(fci_cache); // equivalent to exit zend_bailout(); return SW_OK; } static PHP_METHOD(swoole_process, start) { Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pid && swoole_kill(process->pid, 0) == 0) { php_swoole_fatal_error(E_WARNING, "process has already been started"); RETURN_FALSE; } pid_t pid = swoole_fork(0); if (pid < 0) { php_swoole_sys_error(E_WARNING, "fork() failed"); RETURN_FALSE; } else if (pid > 0) { process->pid = pid; process->child_process = 0; zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("pid"), process->pid); RETURN_LONG(pid); } else { process->child_process = 1; SW_CHECK_RETURN(php_swoole_process_start(process, ZEND_THIS)); } RETURN_TRUE; } static PHP_METHOD(swoole_process, read) { zend_long buf_size = 8192; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(buf_size) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); const Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot read from pipe"); RETURN_FALSE; } ssize_t ret; zend_string *buf = zend_string_alloc(buf_size, false); const auto po = php_swoole_process_fetch_object(ZEND_THIS); if (po->blocking) { ret = process->pipe_current->read_sync(buf->val, buf_size); } else { ret = process->pipe_current->read(buf->val, buf_size); } if (ret < 0) { efree(buf); RETURN_FALSE; } buf->val[ret] = 0; buf->len = ret; RETURN_STR(buf); } static PHP_METHOD(swoole_process, write) { char *data = nullptr; size_t data_len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE) { RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(data, data_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (data_len < 1) { php_swoole_fatal_error(E_WARNING, "the data to send is empty"); RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot write into pipe"); RETURN_FALSE; } ssize_t ret; const auto po = php_swoole_process_fetch_object(ZEND_THIS); if (!po->blocking && swoole_event_is_available()) { ret = swoole_event_write(process->pipe_current, data, data_len); } else { ret = process->pipe_current->send_sync(data, data_len); } if (ret < 0) { php_swoole_sys_error(E_WARNING, "write() failed"); RETURN_FALSE; } ZVAL_LONG(return_value, ret); } /** * export Swoole\Coroutine\Socket object */ static PHP_METHOD(swoole_process, exportSocket) { auto po = php_swoole_process_fetch_object(ZEND_THIS); if (!po->worker || po->worker->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot export stream"); RETURN_FALSE; } if (!po->zsocket) { po->zsocket = php_swoole_dup_socket( po->worker->pipe_current->fd, po->pipe_type == PIPE_TYPE_STREAM ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM); if (!po->zsocket) { RETURN_FALSE; } } GC_ADDREF(po->zsocket); RETURN_OBJ(po->zsocket); } static PHP_METHOD(swoole_process, push) { char *data; size_t length; struct { long type; char data[SW_MSGMAX]; } message; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(data, length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (length <= 0) { php_swoole_fatal_error(E_WARNING, "the data to push is empty"); RETURN_FALSE; } else if (length >= sizeof(message.data)) { php_swoole_fatal_error(E_WARNING, "the data to push is too big"); RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (!process->queue) { php_swoole_fatal_error(E_WARNING, "no msgqueue, cannot use push()"); RETURN_FALSE; } message.type = process->id + 1; memcpy(message.data, data, length); if (!process->queue->push((QueueNode *) &message, length)) { RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_process, pop) { zend_long maxsize = SW_MSGMAX; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(maxsize) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (maxsize > SW_MSGMAX || maxsize <= 0) { maxsize = SW_MSGMAX; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (!process->queue) { php_swoole_fatal_error(E_WARNING, "no msgqueue, cannot use pop()"); RETURN_FALSE; } struct { long type; char data[SW_MSGMAX]; } message; if (process->msgqueue_mode == SW_MSGQUEUE_BALANCE) { message.type = 0; } else { message.type = process->id + 1; } ssize_t n = process->queue->pop((QueueNode *) &message, maxsize); if (n < 0) { RETURN_FALSE; } RETURN_STRINGL(message.data, n); } static PHP_METHOD(swoole_process, exec) { char *execfile = nullptr; size_t execfile_len = 0; zval *args; if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &execfile, &execfile_len, &args) == FAILURE) { RETURN_FALSE; } if (execfile_len < 1) { php_swoole_fatal_error(E_WARNING, "exec file name is empty"); RETURN_FALSE; } int exec_argc = php_swoole_array_length(args); char **exec_args = (char **) emalloc(sizeof(char *) * (exec_argc + 2)); zval *value = nullptr; exec_args[0] = sw_strdup(execfile); int i = 1; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args), value) { auto arg_str = zval_get_string(value); exec_args[i] = ZSTR_VAL(arg_str); i++; } ZEND_HASH_FOREACH_END(); exec_args[i] = nullptr; if (execv(execfile, exec_args) < 0) { php_swoole_sys_error(E_WARNING, "execv(%s) failed", execfile); RETURN_FALSE; } else { RETURN_TRUE; } } static PHP_METHOD(swoole_process, daemon) { zend_bool nochdir = true; zend_bool noclose = true; zval *zpipes = nullptr; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL Z_PARAM_BOOL(nochdir) Z_PARAM_BOOL(noclose) Z_PARAM_ARRAY(zpipes) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zpipes) { int fd = 0; zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zpipes), elem) { if (!ZVAL_IS_NULL(elem)) { int new_fd = php_swoole_convert_to_fd(elem); if (new_fd >= 0) { if (dup2(new_fd, fd) < 0) { swoole_sys_warning("dup2(%d, %d) failed", new_fd, fd); } } } if (fd++ == 2) { break; } } ZEND_HASH_FOREACH_END(); } RETURN_BOOL(swoole_daemon(nochdir, noclose) == 0); } #ifdef HAVE_CPU_AFFINITY bool php_swoole_array_to_cpu_set(const zval *array, cpu_set_t *cpu_set) { if (php_swoole_array_length(array) == 0) { return false; } if (php_swoole_array_length(array) > SW_CPU_NUM) { php_swoole_fatal_error(E_WARNING, "More than the number of CPU"); return false; } zval *value = nullptr; CPU_ZERO(cpu_set); SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(array), value) if (zval_get_long(value) >= SW_CPU_NUM) { php_swoole_fatal_error(E_WARNING, "invalid cpu id [%d]", (int) Z_LVAL_P(value)); return false; } CPU_SET(Z_LVAL_P(value), cpu_set); SW_HASHTABLE_FOREACH_END(); return true; } void php_swoole_cpu_set_to_array(zval *array, cpu_set_t *cpu_set) { array_init(array); int cpu_n = SW_CPU_NUM; SW_LOOP_N(cpu_n) { if (CPU_ISSET(i, cpu_set)) { add_next_index_long(array, i); } } } static PHP_METHOD(swoole_process, setAffinity) { zval *array; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(array) ZEND_PARSE_PARAMETERS_END(); cpu_set_t cpu_set; if (!php_swoole_array_to_cpu_set(array, &cpu_set)) { RETURN_FALSE; } if (swoole_set_cpu_affinity(&cpu_set) < 0) { php_swoole_sys_error(E_WARNING, "sched_setaffinity() failed"); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_process, getAffinity) { cpu_set_t cpu_set; if (swoole_get_cpu_affinity(&cpu_set) < 0) { php_swoole_sys_error(E_WARNING, "sched_getaffinity() failed"); RETURN_FALSE; } php_swoole_cpu_set_to_array(return_value, &cpu_set); } #endif static PHP_METHOD(swoole_process, exit) { long ret_code = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &ret_code) == FAILURE) { RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (getpid() != process->pid) { php_swoole_fatal_error(E_WARNING, "not current process"); RETURN_FALSE; } if (ret_code < 0 || ret_code > 255) { php_swoole_fatal_error(E_WARNING, "exit ret_code range is [>0 and <255] "); ret_code = 1; } if (swoole_event_is_available()) { swoole_event_free(); } exit(static_cast(ret_code)); } static PHP_METHOD(swoole_process, close) { long which = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &which) == FAILURE) { RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot close the pipe"); RETURN_FALSE; } if (process->pipe_object == nullptr) { php_swoole_fatal_error(E_WARNING, "cannot close the pipe"); RETURN_FALSE; } int ret; if (which == SW_PIPE_CLOSE_READ) { ret = process->pipe_current->shutdown(SHUT_RD); } else if (which == SW_PIPE_CLOSE_WRITE) { ret = process->pipe_current->shutdown(SHUT_WR); } else { ret = process->pipe_object->close(which); } if (ret < 0) { php_swoole_sys_error(E_WARNING, "close() failed"); RETURN_FALSE; } if (which == 0) { delete process->pipe_object; process->pipe_object = nullptr; process->pipe_current = nullptr; } RETURN_TRUE; } static PHP_METHOD(swoole_process, set) { zval *zset = nullptr; HashTable *vht = nullptr; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); auto po = php_swoole_process_fetch_object(ZEND_THIS); if (UNEXPECTED(!po->worker)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { po->enable_coroutine = zval_is_true(ztmp); } } static PHP_METHOD(swoole_process, setTimeout) { double seconds; if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &seconds) == FAILURE) { RETURN_FALSE; } Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot setTimeout the pipe"); RETURN_FALSE; } process->pipe_current->set_timeout(seconds); RETURN_BOOL(process->pipe_current->set_kernel_timeout(seconds)); } static PHP_METHOD(swoole_process, setBlocking) { zend_bool blocking; if (zend_parse_parameters(ZEND_NUM_ARGS(), "b", &blocking) == FAILURE) { RETURN_FALSE; } auto po = php_swoole_process_fetch_object(ZEND_THIS); if (po->worker == nullptr || po->worker->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot setBlocking the pipe"); RETURN_FALSE; } po->blocking = blocking; if (blocking) { RETURN_BOOL(po->worker->pipe_current->set_block()); } else { RETURN_BOOL(po->worker->pipe_current->set_nonblock()); } } #define SW_CHECK_PRIORITY_WHO() \ if (who_is_null) { \ if (which == PRIO_PROCESS) { \ Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); \ who = process->pid; \ } else { \ php_swoole_fatal_error(E_WARNING, "$who parameter must not be null"); \ swoole_set_last_error(SW_ERROR_INVALID_PARAMS); \ RETURN_FALSE; \ } \ } static PHP_METHOD(swoole_process, setPriority) { zend_long which, priority, who; bool who_is_null = true; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_LONG(which) Z_PARAM_LONG(priority) Z_PARAM_OPTIONAL Z_PARAM_LONG_OR_NULL(who, who_is_null) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_PRIORITY_WHO(); if (setpriority(which, who, priority) < 0) { swoole_set_last_error(errno); RETURN_FALSE; } else { RETURN_TRUE; } } static PHP_METHOD(swoole_process, getPriority) { zend_long which, who; bool who_is_null = true; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(which) Z_PARAM_OPTIONAL Z_PARAM_LONG_OR_NULL(who, who_is_null) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_PRIORITY_WHO(); errno = 0; int priority = getpriority(which, who); if (priority == -1 && errno != 0) { swoole_set_last_error(errno); RETURN_FALSE; } else { RETURN_LONG(priority); } } ================================================ FILE: ext-src/swoole_process_pool.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_process.h" #include "swoole_server.h" #include "swoole_signal.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_process_pool_arginfo.h" END_EXTERN_C() using namespace swoole; static zend_class_entry *swoole_process_pool_ce; static zend_object_handlers swoole_process_pool_handlers; static ProcessPool *current_pool = nullptr; static Worker *current_worker = nullptr; struct ProcessPoolObject { ProcessPool *pool; zend::Callable *onStart; zend::Callable *onShutdown; zend::Callable *onWorkerStart; zend::Callable *onWorkerStop; zend::Callable *onWorkerExit; zend::Callable *onMessage; zend_bool enable_coroutine; zend_bool enable_message_bus; zend_object std; }; static void process_pool_signal_handler(int signo); static sw_inline ProcessPoolObject *process_pool_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_process_pool_handlers.offset); } static sw_inline ProcessPoolObject *process_pool_fetch_object(const zval *zobject) { return process_pool_fetch_object(Z_OBJ_P(zobject)); } static sw_inline ProcessPool *process_pool_get_pool(const zval *zobject) { return process_pool_fetch_object(Z_OBJ_P(zobject))->pool; } static sw_inline ProcessPool *process_pool_get_and_check_pool(const zval *zobject) { ProcessPool *pool = process_pool_get_pool(zobject); if (UNEXPECTED(!pool)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return pool; } static void process_pool_free_object(zend_object *object) { ProcessPoolObject *pp = process_pool_fetch_object(object); ProcessPool *pool = pp->pool; if (pool) { efree(pool->ptr); pool->destroy(); efree(pool); } if (pp->onWorkerStart) { sw_callable_free(pp->onWorkerStart); } if (pp->onMessage) { sw_callable_free(pp->onMessage); } if (pp->onWorkerStop) { sw_callable_free(pp->onWorkerStop); } if (pp->onStart) { sw_callable_free(pp->onStart); } if (pp->onWorkerExit) { sw_callable_free(pp->onWorkerExit); } if (pp->onShutdown) { sw_callable_free(pp->onShutdown); } zend_object_std_dtor(object); } static zend_object *process_pool_create_object(zend_class_entry *ce) { auto *pp = static_cast(zend_object_alloc(sizeof(ProcessPoolObject), ce)); zend_object_std_init(&pp->std, ce); object_properties_init(&pp->std, ce); pp->std.handlers = &swoole_process_pool_handlers; return &pp->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_process_pool, __construct); static PHP_METHOD(swoole_process_pool, __destruct); static PHP_METHOD(swoole_process_pool, set); static PHP_METHOD(swoole_process_pool, on); static PHP_METHOD(swoole_process_pool, listen); static PHP_METHOD(swoole_process_pool, write); static PHP_METHOD(swoole_process_pool, sendMessage); static PHP_METHOD(swoole_process_pool, detach); static PHP_METHOD(swoole_process_pool, getProcess); static PHP_METHOD(swoole_process_pool, start); static PHP_METHOD(swoole_process_pool, stop); static PHP_METHOD(swoole_process_pool, shutdown); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_process_pool_methods[] = { PHP_ME(swoole_process_pool, __construct, arginfo_class_Swoole_Process_Pool___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, __destruct, arginfo_class_Swoole_Process_Pool___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, set, arginfo_class_Swoole_Process_Pool_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, on, arginfo_class_Swoole_Process_Pool_on, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, getProcess, arginfo_class_Swoole_Process_Pool_getProcess, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, listen, arginfo_class_Swoole_Process_Pool_listen, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, write, arginfo_class_Swoole_Process_Pool_write, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, sendMessage, arginfo_class_Swoole_Process_Pool_sendMessage, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, detach, arginfo_class_Swoole_Process_Pool_detach, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, start, arginfo_class_Swoole_Process_Pool_start, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, stop, arginfo_class_Swoole_Process_Pool_stop, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, shutdown, arginfo_class_Swoole_Process_Pool_shutdown, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_process_pool_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_process_pool, "Swoole\\Process\\Pool", nullptr, swoole_process_pool_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_process_pool); SW_SET_CLASS_CLONEABLE(swoole_process_pool, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_process_pool, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_process_pool, process_pool_create_object, process_pool_free_object, ProcessPoolObject, std); zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("master_pid"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("workerPid"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("workerId"), -1, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_pool_ce, ZEND_STRL("workers"), ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_process_pool_ce, ZEND_STRL("workerRunning"), -1, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_process_pool_ce, ZEND_STRL("running"), -1, ZEND_ACC_PUBLIC); } static void process_pool_onWorkerStart(ProcessPool *pool, Worker *worker) { auto zobject = static_cast(pool->ptr); auto pp = process_pool_fetch_object(zobject); php_swoole_process_clean(); current_pool = pool; current_worker = worker; zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), true); zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), true); zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerPid"), getpid()); zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerId"), worker->id); swoole_set_worker_type(SW_WORKER); SwooleG.enable_coroutine = pp->enable_coroutine; if (pp->onWorkerStart) { zval args[2]; args[0] = *zobject; ZVAL_LONG(&args[1], worker->id); if (UNEXPECTED(!zend::function::call(pp->onWorkerStart->ptr(), 2, args, nullptr, pp->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onWorkerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } if (!swoole_signal_isset(SIGTERM) && (pp->onMessage || pp->enable_coroutine)) { swoole_signal_set(SIGTERM, process_pool_signal_handler); } if (!swoole_signal_isset(SIGWINCH)) { swoole_signal_set(SIGWINCH, process_pool_signal_handler); } #ifdef SIGRTMIN if (!swoole_signal_isset(SIGRTMIN)) { swoole_signal_set(SIGRTMIN, process_pool_signal_handler); } #endif } static void process_pool_onMessage(ProcessPool *pool, RecvData *msg) { auto zobject = static_cast(pool->ptr); auto pp = process_pool_fetch_object(zobject); zval args[2]; args[0] = *zobject; const char *data = msg->data; uint32_t length = msg->info.len; if (length == 0) { ZVAL_EMPTY_STRING(&args[1]); } else { if (msg->info.flags & SW_EVENT_DATA_OBJ_PTR) { zend::assign_zend_string_by_val(&args[1], (char *) data, length); pool->message_bus->move_packet(); } else { ZVAL_STRINGL(&args[1], data, length); } } auto *worker = sw_worker(); worker->set_status_to_busy(); if (UNEXPECTED(!zend::function::call(pp->onMessage->ptr(), 2, args, nullptr, pp->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } worker->add_request_count(); worker->set_status_to_idle(); zval_ptr_dtor(&args[1]); } static void process_pool_onWorkerStop(ProcessPool *pool, Worker *worker) { auto zobject = static_cast(pool->ptr); ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[2]; zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); if (pp->onWorkerStop == nullptr) { return; } args[0] = *zobject; ZVAL_LONG(&args[1], worker->id); if (UNEXPECTED(!zend::function::call(pp->onWorkerStop->ptr(), 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } static void process_pool_onWorkerExit(ProcessPool *pool, Worker *worker) { zval *zobject = (zval *) pool->ptr; ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[2]; zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); if (pp->onWorkerExit == nullptr) { return; } args[0] = *zobject; ZVAL_LONG(&args[1], worker->id); if (UNEXPECTED(!zend::function::call(pp->onWorkerExit->ptr(), 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerExit handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } static void process_pool_onStart(ProcessPool *pool) { zval *zobject = static_cast(pool->ptr); ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[1]; zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("master_pid"), getpid()); zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), true); swoole_set_worker_type(SW_MASTER); SwooleG.enable_coroutine = false; if (pp->onStart == nullptr) { return; } args[0] = *zobject; if (UNEXPECTED(!zend::function::call(pp->onStart->ptr(), 1, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } static void process_pool_onShutdown(ProcessPool *pool) { zval *zobject = static_cast(pool->ptr); ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[1]; zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); if (pp->onShutdown == nullptr) { return; } args[0] = *zobject; if (UNEXPECTED(!zend::function::call(pp->onShutdown->ptr(), 1, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } static void process_pool_signal_handler(int signo) { if (!current_pool) { return; } switch (signo) { case SIGTERM: current_pool->running = false; if (current_worker) { current_pool->stop(current_worker); } break; case SIGUSR1: case SIGUSR2: current_pool->reload(); break; case SIGIO: current_pool->trigger_read_message_event(); break; case SIGWINCH: current_pool->reopen_logger(); break; default: #ifdef SIGRTMIN if (signo == SIGRTMIN) { current_pool->reopen_logger(); } #endif break; } } ProcessPool *sw_process_pool() { return current_pool; } static PHP_METHOD(swoole_process_pool, __construct) { zval *zobject = ZEND_THIS; zend_long worker_num; zend_long ipc_type = SW_IPC_NONE; zend_long msgq_key = 0; zend_bool enable_coroutine = false; // only cli env if (!SWOOLE_G(cli)) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_error(nullptr, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(zobject)); RETURN_FALSE; } if (sw_server()) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_error(nullptr, "cannot create server and process pool instances simultaneously"); RETURN_FALSE; } if (sw_process_pool()) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_error(nullptr, "A process pool instance has already been created and cannot be created again"); RETURN_FALSE; } #ifdef SW_THREAD if (!tsrm_is_main_thread()) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_exception_ex(swoole_exception_ce, -1, "This operation is only allowed in the main thread"); RETURN_FALSE; } #endif if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "l|llb", &worker_num, &ipc_type, &msgq_key, &enable_coroutine) == FAILURE) { RETURN_FALSE; } if (worker_num <= 0) { zend_throw_exception_ex(swoole_exception_ce, errno, "the parameter $worker_num must be greater than 0"); RETURN_FALSE; } if (enable_coroutine && ipc_type > 0 && ipc_type != SW_IPC_UNIXSOCK) { ipc_type = SW_IPC_UNIXSOCK; zend_throw_error(nullptr, "the parameter $ipc_type must be SWOOLE_IPC_UNIXSOCK when enable coroutine"); RETURN_FALSE; } auto *pool = static_cast(emalloc(sizeof(ProcessPool))); *pool = {}; if (pool->create(worker_num, (key_t) msgq_key, (swIPCMode) ipc_type) < 0) { zend_throw_exception_ex(swoole_exception_ce, errno, "failed to create process pool"); efree(pool); RETURN_FALSE; } pool->ptr = sw_zval_dup(zobject); pool->async = enable_coroutine; auto pp = process_pool_fetch_object(ZEND_THIS); pp->enable_coroutine = enable_coroutine; pp->pool = pool; } static PHP_METHOD(swoole_process_pool, set) { zval *zset = nullptr; HashTable *vht = nullptr; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); php_swoole_set_global_option(vht); php_swoole_set_coroutine_option(vht); php_swoole_set_aio_option(vht); if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { pool->async = pp->enable_coroutine = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "enable_message_bus", ztmp)) { pp->enable_message_bus = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "max_package_size", ztmp)) { pool->set_max_packet_size(php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "max_wait_time", ztmp)) { zend_long v = zval_get_long(ztmp); pool->max_wait_time = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } } static PHP_METHOD(swoole_process_pool, on) { char *name; size_t l_name; zval *zfn; ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is started. unable to register event callback function"); RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) Z_PARAM_STRING(name, l_name) Z_PARAM_ZVAL(zfn); ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); if (SW_STRCASEEQ(name, l_name, "WorkerStart")) { if (pp->onWorkerStart) { sw_callable_free(pp->onWorkerStart); } pp->onWorkerStart = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "Message")) { if (pool->ipc_mode == SW_IPC_NONE) { zend_throw_exception( swoole_exception_ce, "cannot set `onMessage` event with ipc_type=0", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } if (pp->onMessage) { sw_callable_free(pp->onMessage); } pp->onMessage = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "WorkerStop")) { if (pp->onWorkerStop) { sw_callable_free(pp->onWorkerStop); } pp->onWorkerStop = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "WorkerExit")) { if (pp->onWorkerExit) { sw_callable_free(pp->onWorkerExit); } pp->onWorkerExit = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "Start")) { if (pp->onStart) { sw_callable_free(pp->onStart); } pp->onStart = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "Shutdown")) { if (pp->onShutdown) { sw_callable_free(pp->onShutdown); } pp->onShutdown = sw_callable_create(zfn); } else { php_swoole_error(E_WARNING, "unknown event type[%s]", name); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_process_pool, listen) { char *host; size_t l_host; zend_long port = 0; zend_long backlog = 2048; auto pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is started. unable to listen"); RETURN_FALSE; } if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &host, &l_host, &port, &backlog) == FAILURE) { RETURN_FALSE; } if (pool->ipc_mode != SW_IPC_SOCKET) { php_swoole_fatal_error(E_WARNING, "unsupported ipc type[%d]", pool->ipc_mode); RETURN_FALSE; } int ret; // unix socket if (SW_STR_ISTARTS_WITH(host, l_host, "unix:/")) { ret = pool->listen(host + 5, backlog); } else { ret = pool->listen(host, port, backlog); } pool->stream_info_->socket->set_fd_option(0, 1); SW_CHECK_RETURN(ret); } static PHP_METHOD(swoole_process_pool, write) { char *data; size_t length; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(data, length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->ipc_mode != SW_IPC_SOCKET) { php_swoole_fatal_error(E_WARNING, "unsupported ipc type[%d]", pool->ipc_mode); RETURN_FALSE; } if (length == 0) { RETURN_FALSE; } SW_CHECK_RETURN(pool->response(data, length)); } static PHP_METHOD(swoole_process_pool, sendMessage) { ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (!pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is not started."); RETURN_FALSE; } if (pool->ipc_mode != SW_IPC_UNIXSOCK) { php_swoole_fatal_error(E_WARNING, "unsupported ipc type[%d]", pool->ipc_mode); RETURN_FALSE; } char *message; size_t l_message; zend_long worker_id; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(message, l_message) Z_PARAM_LONG(worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(pool->send_message(worker_id, message, l_message)); } static PHP_METHOD(swoole_process_pool, start) { ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is started"); RETURN_FALSE; } ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); std::unordered_map ori_handlers; // The reactor must be cleaned up before registering signal swoole_event_free(); ori_handlers[SIGTERM] = swoole_signal_set(SIGTERM, process_pool_signal_handler); ori_handlers[SIGUSR1] = swoole_signal_set(SIGUSR1, process_pool_signal_handler); ori_handlers[SIGUSR2] = swoole_signal_set(SIGUSR2, process_pool_signal_handler); ori_handlers[SIGIO] = swoole_signal_set(SIGIO, process_pool_signal_handler); ori_handlers[SIGWINCH] = swoole_signal_set(SIGWINCH, process_pool_signal_handler); #ifdef SIGRTMIN ori_handlers[SIGRTMIN] = swoole_signal_set(SIGRTMIN, process_pool_signal_handler); #endif if (pp->enable_message_bus) { if (pool->create_message_bus() != SW_OK) { RETURN_FALSE; } pool->message_bus->set_allocator(sw_zend_string_allocator()); pool->set_protocol(SW_PROTOCOL_MESSAGE); } else { pool->set_protocol(SW_PROTOCOL_STREAM); } if (pp->onWorkerStart == nullptr && pp->onMessage == nullptr) { if (pool->async) { php_swoole_fatal_error(E_ERROR, "require 'onWorkerStart' callback"); RETURN_FALSE; } else if (pool->ipc_mode != SW_IPC_NONE && pp->onMessage == nullptr) { php_swoole_fatal_error(E_ERROR, "require 'onMessage' callback"); RETURN_FALSE; } } if (pp->onWorkerExit && !pp->enable_coroutine) { zend_throw_exception( swoole_exception_ce, "cannot set `onWorkerExit` without enable_coroutine", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } if (pp->onMessage) { pool->onMessage = process_pool_onMessage; } else { pool->main_loop = nullptr; } current_pool = pool; pool->onStart = process_pool_onStart; pool->onShutdown = process_pool_onShutdown; pool->onWorkerStart = process_pool_onWorkerStart; pool->onWorkerStop = process_pool_onWorkerStop; if (pp->enable_coroutine && pp->onWorkerExit) { pool->onWorkerExit = process_pool_onWorkerExit; } if (pool->start() < 0) { RETURN_FALSE; } pool->wait(); current_pool = nullptr; for (auto &ori_handler : ori_handlers) { swoole_signal_set(ori_handler.first, ori_handler.second); } } static PHP_METHOD(swoole_process_pool, detach) { if (current_pool == nullptr) { RETURN_FALSE; } RETURN_BOOL(current_pool->detach()); } static PHP_METHOD(swoole_process_pool, getProcess) { long worker_id = -1; if (current_pool == nullptr) { RETURN_FALSE; } if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &worker_id) == FAILURE) { RETURN_FALSE; } if (worker_id >= current_pool->worker_num) { php_swoole_error(E_WARNING, "invalid worker_id[%ld]", worker_id); RETURN_FALSE; } else if (worker_id < 0) { worker_id = swoole_get_worker_id(); } zval *zworkers = sw_zend_read_and_convert_property_array(swoole_process_pool_ce, ZEND_THIS, ZEND_STRL("workers"), 0); zval *zprocess = zend_hash_index_find(Z_ARRVAL_P(zworkers), worker_id); zval zobject; if (zprocess == nullptr || ZVAL_IS_NULL(zprocess)) { zprocess = &zobject; /** * Separation from shared memory */ auto *worker = static_cast(emalloc(sizeof(Worker))); *worker = current_pool->workers[worker_id]; object_init_ex(zprocess, swoole_process_ce); zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("id"), swoole_get_worker_id()); zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("pid"), worker->pid); if (current_pool->ipc_mode == SW_IPC_UNIXSOCK) { // current process if (worker->id == swoole_get_worker_id()) { worker->pipe_current = worker->pipe_worker; } else { worker->pipe_current = worker->pipe_master; } /** * Forbidden to close pipe in the php layer */ worker->pipe_object = nullptr; zend_update_property_long( swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("pipe"), worker->pipe_current->fd); } /** * The message bus is enabled and forbid to read/write/close the pipeline in the php layer */ if (current_pool->message_bus) { worker->pipe_current = nullptr; worker->pipe_object = nullptr; } /** * The onMessage callback is not set, use getProcess()->push()/pop() to operate msgqueue */ if (current_pool->ipc_mode == SW_IPC_MSGQUEUE && current_pool->onMessage == nullptr) { worker->queue = current_pool->queue; worker->msgqueue_mode = SW_MSGQUEUE_BALANCE; } php_swoole_process_set_worker(zprocess, worker, PIPE_TYPE_STREAM, current_pool->async); (void) add_index_zval(zworkers, worker_id, zprocess); } else { auto _worker = php_swoole_process_get_worker(zprocess); if (_worker->pid != current_pool->workers[worker_id].pid) { _worker->pid = current_pool->workers[worker_id].pid; zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("pid"), _worker->pid); } } RETURN_ZVAL(zprocess, 1, 0); } static PHP_METHOD(swoole_process_pool, stop) { if (current_pool) { current_pool->running = false; if (current_worker) { current_pool->stop(current_worker); } } } static PHP_METHOD(swoole_process_pool, shutdown) { if (current_pool) { RETURN_BOOL(current_pool->shutdown()); } else { zend_throw_exception(swoole_exception_ce, "The process pool is not started", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } } static PHP_METHOD(swoole_process_pool, __destruct) {} ================================================ FILE: ext-src/swoole_redis_server.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_server.h" #include "swoole_redis.h" #include #include BEGIN_EXTERN_C() #include "ext/standard/php_string.h" #include "stubs/php_swoole_redis_server_arginfo.h" END_EXTERN_C() using swoole::Connection; using swoole::ListenPort; using swoole::RecvData; using swoole::Server; using swoole::String; namespace Redis = swoole::redis; zend_class_entry *swoole_redis_server_ce; zend_object_handlers swoole_redis_server_handlers; static SW_THREAD_LOCAL std::unordered_map redis_handlers; static bool redis_response_format(String *buf, zend_long type, zval *value); SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_redis_server, setHandler); static PHP_METHOD(swoole_redis_server, getHandler); static PHP_METHOD(swoole_redis_server, format); SW_EXTERN_C_END // clang-format off static constexpr zend_function_entry swoole_redis_server_methods[] = { PHP_ME(swoole_redis_server, setHandler, arginfo_class_Swoole_Redis_Server_setHandler, ZEND_ACC_PUBLIC) PHP_ME(swoole_redis_server, getHandler, arginfo_class_Swoole_Redis_Server_getHandler, ZEND_ACC_PUBLIC) PHP_ME(swoole_redis_server, format, arginfo_class_Swoole_Redis_Server_format, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void php_swoole_redis_server_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX( swoole_redis_server, R"(Swoole\Redis\Server)", nullptr, swoole_redis_server_methods, swoole_server); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_redis_server); SW_SET_CLASS_CLONEABLE(swoole_redis_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_redis_server, sw_zend_class_unset_property_deny); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("NIL"), Redis::REPLY_NIL); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("ERROR"), Redis::REPLY_ERROR); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("STATUS"), Redis::REPLY_STATUS); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("INT"), Redis::REPLY_INT); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("STRING"), Redis::REPLY_STRING); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("SET"), Redis::REPLY_SET); zend_declare_class_constant_long(swoole_redis_server_ce, ZEND_STRL("MAP"), Redis::REPLY_MAP); } void php_swoole_redis_server_rshutdown() { for (const auto &redis_handler : redis_handlers) { sw_callable_free(redis_handler.second); } redis_handlers.clear(); } int php_swoole_redis_server_onReceive(Server *serv, RecvData *req) { auto fd = req->info.fd; Connection *conn = serv->get_connection_by_session_id(fd); if (!conn) { swoole_warning("connection[%ld] is closed", fd); return SW_ERR; } ListenPort *port = serv->get_port_by_fd(conn->fd); // other server port if (!port->open_redis_protocol) { return php_swoole_server_onReceive(serv, req); } zval zdata; php_swoole_get_recv_data(serv, &zdata, req); const char *p = Z_STRVAL(zdata); const char *pe = p + Z_STRLEN(zdata); int ret; int length = 0; zval zparams{}; array_init(&zparams); int state = Redis::STATE_RECEIVE_TOTAL_LINE; int add_param = 0; const char *command = nullptr; int command_len = 0; do { switch (state) { case Redis::STATE_RECEIVE_TOTAL_LINE: if (*p == '*' && (p = Redis::get_number(p, &ret))) { state = Redis::STATE_RECEIVE_LENGTH; break; } /* no break */ case Redis::STATE_RECEIVE_LENGTH: if (*p == '$' && (p = Redis::get_number(p, &ret))) { if (ret == -1) { add_next_index_null(&zparams); break; } length = ret; state = Redis::STATE_RECEIVE_STRING; break; } // integer else if (*p == ':' && (p = Redis::get_number(p, &ret))) { add_next_index_long(&zparams, ret); break; } /* no break */ case Redis::STATE_RECEIVE_STRING: if (add_param == 0) { command = p; command_len = length; add_param = 1; } else { add_next_index_stringl(&zparams, p, length); } p += length + SW_CRLF_LEN; state = Redis::STATE_RECEIVE_LENGTH; break; default: break; } } while (p < pe); if (command_len >= SW_REDIS_MAX_COMMAND_SIZE) { php_swoole_error(E_WARNING, "command [%.8s...](length=%d) is too long", command, command_len); serv->close(fd, false); return SW_OK; } char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%.*s", command_len, command); #if PHP_VERSION_ID >= 80400 zend_str_tolower(_command, _command_len); #else php_strtolower(_command, _command_len); #endif auto i = redis_handlers.find(std::string(_command, _command_len)); if (i == redis_handlers.end()) { char err_msg[256]; length = sw_snprintf(err_msg, sizeof(err_msg), "-ERR unknown command '%.*s'\r\n", command_len, command); return serv->send(fd, err_msg, length) ? SW_OK : SW_ERR; } auto fci_cache = i->second; zval args[2]; zval retval; ZVAL_LONG(&args[0], fd); args[1] = zparams; if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 2, args, &retval, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onRequest with command '%.*s' handler error", ZSTR_VAL(swoole_redis_server_ce->name), command_len, command); } if (Z_TYPE_P(&retval) == IS_STRING) { serv->send(fd, Z_STRVAL_P(&retval), Z_STRLEN_P(&retval)); } zval_ptr_dtor(&retval); zval_ptr_dtor(&zdata); zval_ptr_dtor(&zparams); return SW_OK; } static PHP_METHOD(swoole_redis_server, setHandler) { char *command; size_t command_len; zval *zcallback; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(command, command_len) Z_PARAM_ZVAL(zcallback) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (command_len == 0 || command_len >= SW_REDIS_MAX_COMMAND_SIZE) { php_swoole_fatal_error(E_ERROR, "invalid command"); RETURN_FALSE; } auto fci_cache = sw_callable_create(zcallback); if (!fci_cache) { return; } char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%s", command); #if PHP_VERSION_ID >= 80400 zend_str_tolower(_command, _command_len); #else php_strtolower(_command, _command_len); #endif zend_update_property(swoole_redis_server_ce, SW_Z8_OBJ_P(ZEND_THIS), _command, _command_len, zcallback); std::string key(_command, _command_len); auto i = redis_handlers.find(key); if (i != redis_handlers.end()) { sw_callable_free(i->second); } redis_handlers[key] = fci_cache; RETURN_TRUE; } static PHP_METHOD(swoole_redis_server, getHandler) { char *command; size_t command_len; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(command, command_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%s", command); #if PHP_VERSION_ID >= 80400 zend_str_tolower(_command, _command_len); #else php_strtolower(_command, _command_len); #endif zval rv; zval *handler = zend_read_property(swoole_redis_server_ce, SW_Z8_OBJ_P(ZEND_THIS), _command, _command_len, 1, &rv); RETURN_ZVAL(handler, 1, 0); } static void redis_response_format_array_item(String *buf, zval *item) { switch (Z_TYPE_P(item)) { case IS_LONG: case IS_FALSE: case IS_TRUE: redis_response_format(buf, Redis::REPLY_INT, item); break; case IS_ARRAY: if (zend_array_is_list(Z_ARRVAL_P(item))) { redis_response_format(buf, Redis::REPLY_SET, item); } else { redis_response_format(buf, Redis::REPLY_MAP, item); } break; case IS_NULL: redis_response_format(buf, Redis::REPLY_NIL, item); break; default: redis_response_format(buf, Redis::REPLY_STRING, item); break; } } static bool redis_response_format(String *buf, zend_long type, zval *value) { if (type == Redis::REPLY_NIL) { buf->append(SW_STRL(SW_REDIS_RETURN_NIL)); } else if (type == Redis::REPLY_ERROR || type == Redis::REPLY_STATUS) { char flag = type == Redis::REPLY_ERROR ? '-' : '+'; const char *default_message = type == Redis::REPLY_ERROR ? "ERR" : "OK"; if (value) { zend::String str_value(value); buf->append_format("%c%.*s\r\n", flag, (int) str_value.len(), str_value.val()); } else { buf->append_format("%c%s\r\n", flag, default_message); } } else if (type == Redis::REPLY_INT) { if (!value) { goto _no_value; } buf->append_format(":" ZEND_LONG_FMT "\r\n", zval_get_long(value)); } else if (type == Redis::REPLY_STRING) { if (!value) { _no_value: zend_throw_exception(swoole_exception_ce, "require more parameters", SW_ERROR_INVALID_PARAMS); return false; } zend::String str_value(value); if (sw_unlikely(str_value.len() > SW_REDIS_MAX_STRING_SIZE)) { zend_throw_exception(swoole_exception_ce, "the length of given string exceeds the maximum allowed value", SW_ERROR_INVALID_PARAMS); return false; } else if (sw_unlikely(str_value.len() == 0)) { buf->append("$0\r\n\r\n"); } else { buf->append_format("$%zu\r\n", str_value.len()); buf->append(str_value.val(), str_value.len()); buf->append(SW_CRLF, SW_CRLF_LEN); } } else if (type == Redis::REPLY_SET) { if (!value) { goto _no_value; } if (!ZVAL_IS_ARRAY(value)) { zend_throw_exception( swoole_exception_ce, "the second parameter should be an array", SW_ERROR_INVALID_PARAMS); } buf->append_format("*%d\r\n", zend_hash_num_elements(Z_ARRVAL_P(value))); zval *item; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), item) { redis_response_format_array_item(buf, item); } ZEND_HASH_FOREACH_END(); } else if (type == Redis::REPLY_MAP) { if (!value) { goto _no_value; } if (!ZVAL_IS_ARRAY(value)) { zend_throw_exception( swoole_exception_ce, "the second parameter should be an array", SW_ERROR_INVALID_PARAMS); } buf->append_format("*%d\r\n", 2 * zend_hash_num_elements(Z_ARRVAL_P(value))); zend_string *key; zend_ulong num_key; zval *item; ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(value), num_key, key, item) { if (key) { buf->append_format("$%zu\r\n%.*s\r\n", ZSTR_LEN(key), (int) ZSTR_LEN(key), ZSTR_VAL(key)); } else { std::string _key = std::to_string(num_key); buf->append_format("$%zu\r\n%.*s\r\n", _key.length(), (int) _key.length(), _key.c_str()); } redis_response_format_array_item(buf, item); } ZEND_HASH_FOREACH_END(); } else { zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "Unknown type[%d]", (int) type); return false; } return true; } static PHP_METHOD(swoole_redis_server, format) { zend_long type; zval *value = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(type) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto buf = std::shared_ptr(swoole::make_string(1024, sw_zend_string_allocator())); if (!redis_response_format(buf.get(), type, value)) { RETURN_FALSE; } auto str = zend::fetch_zend_string_by_val(buf->str); buf->set_null_terminated(); str->len = buf->length; buf->release(); RETURN_STR(str); } ================================================ FILE: ext-src/swoole_runtime.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_api.h" #include "php_swoole_coroutine.h" #include "swoole_util.h" #include "thirdparty/php/standard/proc_open.h" #ifdef SW_USE_CURL #include "swoole_curl_interface.h" #endif #include BEGIN_EXTERN_C() #include "stubs/php_swoole_runtime_arginfo.h" #ifdef SW_USE_PGSQL extern void swoole_pgsql_set_blocking(bool blocking); #endif #ifdef SW_USE_ODBC extern void swoole_odbc_set_blocking(bool blocking); #endif #ifdef SW_USE_ORACLE extern void swoole_oracle_set_blocking(bool blocking); #endif #ifdef SW_USE_SQLITE extern void swoole_sqlite_set_blocking(bool blocking); #endif #ifdef SW_USE_FIREBIRD extern void swoole_firebird_set_blocking(bool blocking); #endif END_EXTERN_C() /* openssl */ #ifndef OPENSSL_NO_ECDH #define HAVE_ECDH 1 #endif #ifndef OPENSSL_NO_TLSEXT #define HAVE_TLS_SNI 1 #if OPENSSL_VERSION_NUMBER >= 0x10002000L #define HAVE_TLS_ALPN 1 #endif #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) #define HAVE_SEC_LEVEL 1 #endif using swoole::Coroutine; using swoole::PHPCoroutine; using swoole::coroutine::PollSocket; using swoole::coroutine::System; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_runtime, enableCoroutine); static PHP_METHOD(swoole_runtime, getHookFlags); static PHP_METHOD(swoole_runtime, setHookFlags); static PHP_FUNCTION(swoole_sleep); static PHP_FUNCTION(swoole_usleep); static PHP_FUNCTION(swoole_time_nanosleep); static PHP_FUNCTION(swoole_time_sleep_until); static PHP_FUNCTION(swoole_stream_select); static PHP_FUNCTION(swoole_stream_socket_pair); static PHP_FUNCTION(swoole_user_func_handler); #if defined(HAVE_PUTENV) && defined(SW_THREAD) static PHP_FUNCTION(swoole_putenv); #endif #if PHP_VERSION_ID >= 80400 extern PHP_FUNCTION(swoole_exit); #endif SW_EXTERN_C_END static void inherit_class(const char *child_name, size_t child_length, const char *parent_name, size_t parent_length); static void detach_parent_class(const char *child_name); static void clear_class_entries(); static int socket_set_option(php_stream *stream, int option, int value, void *ptrparam); static php_stream_size_t socket_read(php_stream *stream, char *buf, size_t count); static php_stream_size_t socket_write(php_stream *stream, const char *buf, size_t count); static int socket_flush(php_stream *stream); static int socket_close(php_stream *stream, int close_handle); static int socket_stat(php_stream *stream, php_stream_statbuf *ssb); static int socket_cast(php_stream *stream, int castas, void **ret); static bool socket_ssl_set_options(SocketImpl *sock, php_stream_context *context); static php_stream *socket_create(const char *proto, size_t protolen, const char *resourcename, size_t resourcenamelen, const char *persistent_id, int options, int flags, struct timeval *timeout, php_stream_context *context STREAMS_DC); // clang-format off static zend_class_entry *swoole_runtime_ce; static php_stream_ops socket_ops { socket_write, socket_read, socket_close, socket_flush, "socket/coroutine", nullptr, /* seek */ socket_cast, socket_stat, socket_set_option, }; struct NetStream { php_netstream_data_t stream; std::shared_ptr socket; bool blocking; }; static struct { php_stream_transport_factory tcp; php_stream_transport_factory udp; php_stream_transport_factory _unix; php_stream_transport_factory udg; php_stream_transport_factory ssl; php_stream_transport_factory tls; } ori_factory = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }; static std::vector unsafe_functions { "pcntl_fork", "pcntl_rfork", "pcntl_wait", "pcntl_waitpid", "pcntl_sigtimedwait", "pcntl_sigwaitinfo", }; #if defined(HAVE_PUTENV) && defined(SW_THREAD) static std::unordered_map swoole_runtime_environ; #endif static constexpr zend_function_entry swoole_runtime_methods[] = { PHP_ME(swoole_runtime, enableCoroutine, arginfo_class_Swoole_Runtime_enableCoroutine, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_runtime, getHookFlags, arginfo_class_Swoole_Runtime_getHookFlags, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_runtime, setHookFlags, arginfo_class_Swoole_Runtime_setHookFlags, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on static php_stream_wrapper ori_php_plain_files_wrapper; static php_stream_ops ori_php_stream_stdio_ops; static void hook_func(const char *name, size_t l_name, zif_handler handler = nullptr, zend_internal_arg_info *arg_info = nullptr); static void unhook_func(const char *name, size_t l_name); static bool extension_loaded(const char *name) { zend_string *extension_name = zend_string_init(name, strlen(name), false); zend_string *lcname = zend_string_tolower(extension_name); bool rv = zend_hash_exists(&module_registry, lcname); zend_string_release(lcname); zend_string_release(extension_name); return rv; } static bool class_exists(const char *name) { zend_string *class_name = zend_string_init(name, strlen(name), false); zend_string *lcname = zend_string_tolower(class_name); auto *ce = (zend_class_entry *) zend_hash_find_ptr(EG(class_table), lcname); zend_string_release_ex(lcname, 0); zend_string_release_ex(class_name, 0); return !!ce; } static zend_internal_arg_info *get_arginfo(const char *name, size_t l_name) { auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { return nullptr; } return zf->internal_function.arg_info; } static zend_internal_arg_info *copy_arginfo(const zend_internal_function *function, zend_internal_arg_info *_arg_info) { uint32_t num_args = function->num_args + 1; zend_internal_arg_info *arg_info = _arg_info - 1; auto new_arg_info = static_cast(pemalloc(sizeof(zend_internal_arg_info) * num_args, 1)); memcpy(new_arg_info, arg_info, sizeof(zend_internal_arg_info) * num_args); if (function->fn_flags & ZEND_ACC_VARIADIC) { num_args++; } for (uint32_t i = 0; i < num_args; i++) { if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { auto old_list = ZEND_TYPE_LIST(arg_info[i].type); auto *new_list = static_cast(pemalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types), 1)); memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list); #if PHP_VERSION_ID >= 80500 // For PHP 8.5+, ZEND_TYPE_LIST_FOREACH gives a const pointer, which we can't modify. // We must use a manual loop over the list we allocated ourselves. for (uint32_t j = 0; j < new_list->num_types; j++) { zend_type *list_type = &new_list->types[j]; if (ZEND_TYPE_HAS_NAME(*list_type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(*list_type), 1); ZEND_TYPE_SET_PTR(*list_type, name); } } #else zend_type *list_type; ZEND_TYPE_LIST_FOREACH(new_list, list_type) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(*list_type), true); ZEND_TYPE_SET_PTR(*list_type, name); } ZEND_TYPE_LIST_FOREACH_END(); #endif } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), true); ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); } } return new_arg_info + 1; } static void free_arg_info(const zend_internal_function *function, zend_internal_arg_info *arg_info_copy) { zend_internal_arg_info *arg_info = arg_info_copy - 1; uint32_t num_args = function->num_args + 1; if (function->fn_flags & ZEND_ACC_VARIADIC) { num_args++; } for (uint32_t i = 0; i < num_args; i++) { zend_type_release(arg_info[i].type, true); } pefree(arg_info, 1); } #define SW_HOOK_FUNC(f) hook_func(ZEND_STRL(#f), PHP_FN(swoole_##f)) #define SW_UNHOOK_FUNC(f) unhook_func(ZEND_STRL(#f)) #define SW_HOOK_WITH_NATIVE_FUNC(f) \ hook_func(ZEND_STRL(#f), PHP_FN(swoole_native_##f), get_arginfo(ZEND_STRL("swoole_native_" #f))) #define SW_HOOK_WITH_PHP_FUNC(f) hook_func(ZEND_STRL(#f)) #define SW_HOOK_LIBRARY_FE(name, arg_info) \ ZEND_RAW_FENTRY("swoole_hook_" #name, PHP_FN(swoole_user_func_handler), arg_info, 0) static int runtime_hook_flags = 0; static zend_array *hook_function_table = nullptr; static std::unordered_map child_class_entries; static zend::ConcurrencyHashMap ori_func_handlers(nullptr); static zend::ConcurrencyHashMap ori_func_arg_infos(nullptr); SW_EXTERN_C_BEGIN #include "ext/standard/file.h" #include "thirdparty/php/streams/plain_wrapper.c" #undef close SW_EXTERN_C_END void php_swoole_runtime_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE(swoole_runtime, "Swoole\\Runtime", nullptr, swoole_runtime_methods, nullptr); SW_SET_CLASS_CREATE(swoole_runtime, sw_zend_create_object_deny); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_TCP", PHPCoroutine::HOOK_TCP); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_UDP", PHPCoroutine::HOOK_UDP); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_UNIX", PHPCoroutine::HOOK_UNIX); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_UDG", PHPCoroutine::HOOK_UDG); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_SSL", PHPCoroutine::HOOK_SSL); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_TLS", PHPCoroutine::HOOK_TLS); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_STREAM_FUNCTION", PHPCoroutine::HOOK_STREAM_FUNCTION); // backward compatibility SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_STREAM_SELECT", PHPCoroutine::HOOK_STREAM_FUNCTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_FILE", PHPCoroutine::HOOK_FILE); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_STDIO", PHPCoroutine::HOOK_STDIO); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_SLEEP", PHPCoroutine::HOOK_SLEEP); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PROC", PHPCoroutine::HOOK_PROC); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_CURL", PHPCoroutine::HOOK_CURL); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_NATIVE_CURL", PHPCoroutine::HOOK_NATIVE_CURL); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_SOCKETS", PHPCoroutine::HOOK_SOCKETS); #ifdef SW_USE_PGSQL SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_PGSQL", PHPCoroutine::HOOK_PDO_PGSQL); #endif #ifdef SW_USE_ODBC SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_ODBC", PHPCoroutine::HOOK_PDO_ODBC); #endif #ifdef SW_USE_ORACLE SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_ORACLE", PHPCoroutine::HOOK_PDO_ORACLE); #endif #ifdef SW_USE_SQLITE SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_SQLITE", PHPCoroutine::HOOK_PDO_SQLITE); #endif #ifdef SW_USE_FIREBIRD SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_FIREBIRD", PHPCoroutine::HOOK_PDO_FIREBIRD); #endif SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_NET_FUNCTION", PHPCoroutine::HOOK_NET_FUNCTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_MONGODB", PHPCoroutine::HOOK_MONGODB); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_ALL", PHPCoroutine::HOOK_ALL); #ifdef SW_USE_CURL swoole_native_curl_minit(module_number); #endif swoole_proc_open_init(module_number); php_stream_xport_register("async.tcp", socket_create); php_stream_xport_register("async.udp", socket_create); php_stream_xport_register("async.unix", socket_create); php_stream_xport_register("async.udg", socket_create); php_stream_xport_register("async.ssl", socket_create); php_stream_xport_register("async.tls", socket_create); php_register_url_stream_wrapper(SW_ASYNC_FILE_PROTOCOL, &sw_php_plain_files_wrapper); } struct PhpFunc { zend_function *function; zend_internal_arg_info *ori_arg_info; zif_handler ori_handler; zend_internal_arg_info *arg_info_copy; uint32_t ori_fn_flags; zend::Callable *fci_cache; zval name; }; void php_swoole_runtime_rinit() { if (!sw_is_main_thread()) { #if PHP_VERSION_ID < 80300 // After creating a thread, the main thread will not modify the runtime hook, // so this `hook_function_table` is read-only and safe for multi-threaded reading zend_string *key = NULL; void *ptr; ZEND_HASH_REVERSE_FOREACH_STR_KEY_PTR(hook_function_table, key, ptr) { PhpFunc *rf = (PhpFunc *) ptr; // The PHP 8.3 and later removed `function_copy_ctor`. The `zend_internal_function` pointer in // `EG(function_table)` and `CG(function_table)` are in shared memory across all threads, so updating the // handler and arginfo once in the main thread takes effect for every thread. In PHP 8.2 and earlier, each // thread called `function_copy_ctor` to copy the `zend_internal_function` memory. As a result, // `EG(function_table)` and `CG(function_table)` were thread-local, and must set the `handler` and // `arginfo` again in the thread `RINIT` function. if (rf->function && rf->function->internal_function.handler != rf->ori_handler) { auto zf = zend::get_function(key); auto ifn = &rf->function->internal_function; zf->internal_function.handler = ifn->handler; if (ifn->arg_info != rf->ori_arg_info && ifn->arg_info) { zf->internal_function.arg_info = copy_arginfo(ifn, ifn->arg_info); } } } ZEND_HASH_FOREACH_END(); #endif return; } hook_function_table = static_cast(emalloc(sizeof(zend_array))); zend_hash_init(hook_function_table, 8, nullptr, nullptr, 0); #if defined(HAVE_PUTENV) && defined(SW_THREAD) /** * There are issues with the implementation of putenv in PHP, * which can lead to memory invalid read in multi-thread environment. */ SW_HOOK_FUNC(putenv); #endif #if PHP_VERSION_ID >= 80400 SW_HOOK_FUNC(exit); #endif HashTable *xport_hash = php_stream_xport_get_hash(); ori_factory.tcp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tcp")); ori_factory.udp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udp")); ori_factory._unix = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("unix")); ori_factory.udg = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udg")); ori_factory.ssl = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("ssl")); ori_factory.tls = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tls")); memcpy(&ori_php_plain_files_wrapper, &php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); memcpy(&ori_php_stream_stdio_ops, &php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); } void php_swoole_runtime_rshutdown() { if (!sw_is_main_thread()) { return; } PHPCoroutine::disable_hook(); ori_func_handlers.clear(); ori_func_arg_infos.clear(); void *ptr; ZEND_HASH_FOREACH_PTR(hook_function_table, ptr) { auto *rf = static_cast(ptr); if (rf->fci_cache) { sw_callable_free(rf->fci_cache); } if (Z_TYPE(rf->name) == IS_STRING) { zval_dtor(&rf->name); } if (rf->arg_info_copy) { free_arg_info(&rf->function->internal_function, rf->arg_info_copy); rf->arg_info_copy = nullptr; } rf->function->internal_function.handler = rf->ori_handler; rf->function->internal_function.arg_info = rf->ori_arg_info; efree(rf); } ZEND_HASH_FOREACH_END(); zend_hash_destroy(hook_function_table); efree(hook_function_table); hook_function_table = nullptr; clear_class_entries(); } void php_swoole_runtime_mshutdown() { #ifdef SW_USE_CURL swoole_native_curl_mshutdown(); #endif } static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) { char *colon; char *host = nullptr; if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ char *p = (char *) memchr(str + 1, ']', str_len - 2); if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); } return nullptr; } *portno = sw_atoi(p + 2); return estrndup(str + 1, p - str - 1); } if (str_len) { colon = (char *) memchr(str, ':', str_len - 1); } else { colon = nullptr; } if (colon) { *portno = sw_atoi(colon + 1); host = estrndup(str, colon - str); } else { if (get_err) { *err = strpprintf(0, "Failed to parse address \"%s\"", str); } return nullptr; } return host; } static php_stream_size_t socket_write(php_stream *stream, const char *buf, size_t count) { ssize_t didwrite = -1; std::shared_ptr sock; auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract || !abstract->socket)) { goto _exit; } sock = abstract->socket; if (abstract->blocking) { didwrite = sock->send_all(buf, count); } else { didwrite = sock->get_socket()->send(buf, count, 0); sock->set_err(errno); } if (didwrite < 0 || (size_t) didwrite != count) { /* we do not expect the outer layer to continue to call the `send` syscall in a loop * and `didwrite` is meaningless if it failed */ didwrite = -1; abstract->stream.timeout_event = (sock->errCode == ETIMEDOUT); php_error_docref(nullptr, E_NOTICE, "Send of " ZEND_LONG_FMT " bytes failed with errno=%d %s", (zend_long) count, sock->errCode, sock->errMsg); } else { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); } if (didwrite < 0) { if (sock->errCode == ETIMEDOUT || sock->get_socket()->catch_write_error(sock->errCode) == SW_WAIT) { didwrite = 0; } else { stream->eof = 1; } } else if (didwrite == 0) { stream->eof = 1; } _exit: return didwrite; } static php_stream_size_t socket_read(php_stream *stream, char *buf, size_t count) { std::shared_ptr sock; ssize_t nr_bytes = -1; auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract || !abstract->socket)) { goto _exit; } sock = abstract->socket; if (abstract->blocking) { nr_bytes = sock->recv(buf, count); } else { nr_bytes = sock->get_socket()->recv(buf, count, 0); sock->set_err(errno); } if (nr_bytes > 0) { php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); } if (nr_bytes < 0) { if (sock->errCode == ETIMEDOUT || sock->get_socket()->catch_read_error(sock->errCode) == SW_WAIT) { nr_bytes = 0; } else { stream->eof = 1; } } else if (nr_bytes == 0) { stream->eof = 1; } _exit: return nr_bytes; } static int socket_flush(php_stream *stream) { return 0; } static int socket_close(php_stream *stream, int close_handle) { const auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract)) { return FAILURE; } /** set it null immediately */ stream->abstract = nullptr; /** * it's always successful (even if the destructor rule is violated) * every calls passes through the hook function in PHP * so there is unnecessary to worry about the null pointer. */ if (abstract->socket) { abstract->socket->close(); } delete abstract; return SUCCESS; } enum { STREAM_XPORT_OP_BIND, STREAM_XPORT_OP_CONNECT, STREAM_XPORT_OP_LISTEN, STREAM_XPORT_OP_ACCEPT, STREAM_XPORT_OP_CONNECT_ASYNC, STREAM_XPORT_OP_GET_NAME, STREAM_XPORT_OP_GET_PEER_NAME, STREAM_XPORT_OP_RECV, STREAM_XPORT_OP_SEND, STREAM_XPORT_OP_SHUTDOWN, }; enum { STREAM_XPORT_CRYPTO_OP_SETUP, STREAM_XPORT_CRYPTO_OP_ENABLE }; static int socket_cast(php_stream *stream, int castas, void **ret) { const auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract || !abstract->socket)) { return FAILURE; } const std::shared_ptr sock = abstract->socket; switch (castas) { case PHP_STREAM_AS_STDIO: if (ret) { *reinterpret_cast(ret) = fdopen(sock->get_fd(), stream->mode); if (*ret) { return SUCCESS; } return FAILURE; } return SUCCESS; case PHP_STREAM_AS_FD_FOR_SELECT: case PHP_STREAM_AS_FD: case PHP_STREAM_AS_SOCKETD: if (ret) *reinterpret_cast(ret) = sock->get_fd(); return SUCCESS; default: return FAILURE; } } static int socket_stat(php_stream *stream, php_stream_statbuf *ssb) { const auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract)) { return FAILURE; } if (UNEXPECTED(!abstract->socket)) { return FAILURE; } return zend_fstat(abstract->socket->get_fd(), &ssb->sb); } static inline int socket_connect(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam) { char *host = nullptr, *bindto = nullptr; int portno = 0, bindport = 0; int ret = 0; zval *tmpzval = nullptr; char *ip_address = nullptr; if (UNEXPECTED(sock->get_fd() < 0)) { return FAILURE; } if (sock->get_socket()->is_inet()) { ip_address = parse_ip_address_ex( xparam->inputs.name, xparam->inputs.namelen, &portno, xparam->want_errortext, &xparam->outputs.error_text); host = ip_address; if (sock->get_sock_type() == SOCK_STREAM) { sock->get_socket()->set_tcp_nodelay(); } } else { host = xparam->inputs.name; } if (host == nullptr) { return FAILURE; } ON_SCOPE_EXIT { if (ip_address) { efree(ip_address); } }; if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != nullptr) { if (Z_TYPE_P(tmpzval) != IS_STRING) { if (xparam->want_errortext) { xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); } return FAILURE; } bindto = parse_ip_address_ex( Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); if (bindto == nullptr) { return FAILURE; } if (strcmp(bindto, "0") == 0 || bindto[0] == '\0') { efree(bindto); bindto = nullptr; } ON_SCOPE_EXIT { if (bindto) { efree(bindto); } }; if (!sock->bind(bindto ? bindto : "0.0.0.0", bindport)) { return FAILURE; } } if (xparam->inputs.timeout) { sock->set_timeout(xparam->inputs.timeout, SW_TIMEOUT_CONNECT); } if (sock->connect(host, portno) == false) { xparam->outputs.error_code = sock->errCode; if (sock->errMsg) { xparam->outputs.error_text = zend_string_init(sock->errMsg, strlen(sock->errMsg), false); } ret = -1; } return ret; } static inline int socket_bind(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { char *host = nullptr; int portno = 0; char *ip_address = nullptr; if (sock->get_socket()->is_inet()) { ip_address = parse_ip_address_ex( xparam->inputs.name, xparam->inputs.namelen, &portno, xparam->want_errortext, &xparam->outputs.error_text); host = ip_address; } else { host = xparam->inputs.name; } if (host == nullptr) { sock->set_err(EINVAL); return -1; } int ret = sock->bind(host, portno) ? 0 : -1; if (ip_address) { efree(ip_address); } return ret; } static inline int socket_accept(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { int tcp_nodelay = 0; zval *tmpzval = nullptr; xparam->outputs.client = nullptr; if ((nullptr != PHP_STREAM_CONTEXT(stream)) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != nullptr && zval_is_true(tmpzval)) { tcp_nodelay = 1; } zend_string **textaddr = xparam->want_textaddr ? &xparam->outputs.textaddr : nullptr; sockaddr **addr = xparam->want_addr ? &xparam->outputs.addr : nullptr; socklen_t *addrlen = xparam->want_addr ? &xparam->outputs.addrlen : nullptr; timeval *timeout = xparam->inputs.timeout; zend_string **error_string = xparam->want_errortext ? &xparam->outputs.error_text : nullptr; int *error_code = &xparam->outputs.error_code; int error = 0; php_sockaddr_storage sa; socklen_t sl = sizeof(sa); if (timeout) { sock->set_timeout(timeout, SW_TIMEOUT_READ); } std::shared_ptr clisock(sock->accept()); if (clisock != nullptr && clisock->ssl_is_enable()) { if (!clisock->ssl_handshake()) { sock->errCode = clisock->errCode; clisock.reset(); } } if (clisock == nullptr) { error = sock->errCode; if (error_code) { *error_code = error; } if (error_string) { *error_string = php_socket_error_str(error); } return FAILURE; } else { php_network_populate_name_from_sockaddr((sockaddr *) &sa, sl, textaddr, addr, addrlen); #ifdef TCP_NODELAY if (tcp_nodelay) { clisock->get_socket()->set_tcp_nodelay(tcp_nodelay); } #endif auto abstract = new NetStream(); abstract->socket = clisock; abstract->blocking = true; xparam->outputs.client = php_stream_alloc_rel(stream->ops, abstract, nullptr, "r+"); if (xparam->outputs.client) { xparam->outputs.client->ctx = stream->ctx; if (stream->ctx) { GC_ADDREF(stream->ctx); } } return SUCCESS; } } static inline int socket_recvfrom( SocketImpl *sock, char *buf, size_t buflen, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen) { int ret; int want_addr = textaddr || addr; if (want_addr) { php_sockaddr_storage sa; socklen_t sl = sizeof(sa); ret = sock->recvfrom(buf, buflen, (struct sockaddr *) &sa, &sl); if (sl) { php_network_populate_name_from_sockaddr((struct sockaddr *) &sa, sl, textaddr, addr, addrlen); } else { if (textaddr) { *textaddr = ZSTR_EMPTY_ALLOC(); } if (addr) { *addr = nullptr; *addrlen = 0; } } } else { ret = sock->recv(buf, buflen); } return ret; } static inline int socket_sendto( SocketImpl *sock, const char *buf, size_t buflen, struct sockaddr *addr, socklen_t addrlen) { if (addr) { return sendto(sock->get_fd(), buf, buflen, 0, addr, addrlen); } else { return sock->send(buf, buflen); } } static int socket_setup_crypto(php_stream *stream, SocketImpl *sock, php_stream_xport_crypto_param *cparam STREAMS_DC) { return 0; } static int socket_xport_crypto_setup(php_stream *stream) { php_stream_xport_crypto_param param = {}; param.op = (decltype(param.op)) STREAM_XPORT_CRYPTO_OP_SETUP; param.inputs.method = (php_stream_xport_crypt_method_t) 0; param.inputs.session = nullptr; int ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); if (ret == PHP_STREAM_OPTION_RETURN_OK) { return param.outputs.returncode; } php_error_docref("streams.crypto", E_WARNING, "this stream does not support SSL/crypto"); return ret; } static int socket_xport_crypto_enable(php_stream *stream, int activate) { php_stream_xport_crypto_param param = {}; param.op = (decltype(param.op)) STREAM_XPORT_CRYPTO_OP_ENABLE; param.inputs.activate = activate; int ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); if (ret == PHP_STREAM_OPTION_RETURN_OK) { return param.outputs.returncode; } php_error_docref("streams.crypto", E_WARNING, "this stream does not support SSL/crypto"); return ret; } static bool php_openssl_capture_peer_certs(php_stream *stream, SocketImpl *sslsock) { zval *val; std::string peer_cert = sslsock->ssl_get_peer_cert(); if (peer_cert.empty()) { return false; } zval argv[1]; ZVAL_STRINGL(&argv[0], peer_cert.c_str(), peer_cert.length()); auto retval = zend::function::call("openssl_x509_read", 1, argv); php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_certificate", &retval.value); zval_dtor(&argv[0]); if (nullptr != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "capture_peer_cert_chain")) && zend_is_true(val)) { zval arr; auto chain = sslsock->get_socket()->ssl_get_peer_cert_chain(INT_MAX); if (!chain.empty()) { array_init(&arr); for (auto &cert : chain) { zval _argv[1]; ZVAL_STRINGL(&_argv[0], cert.c_str(), cert.length()); auto _retval = zend::function::call("openssl_x509_read", 1, _argv); zval_add_ref(&_retval.value); add_next_index_zval(&arr, &_retval.value); zval_dtor(&_argv[0]); } } else { ZVAL_NULL(&arr); } php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_certificate_chain", &arr); zval_ptr_dtor(&arr); } return true; } static int socket_enable_crypto(php_stream *stream, SocketImpl *sock, php_stream_xport_crypto_param *cparam STREAMS_DC) { php_stream_context *context = PHP_STREAM_CONTEXT(stream); if (cparam->inputs.activate && !sock->ssl_is_available()) { sock->enable_ssl_encrypt(); if (!socket_ssl_set_options(sock, context)) { return -1; } if (!sock->ssl_handshake()) { return -1; } } else if (!cparam->inputs.activate && sock->ssl_is_available() && sock->get_ssl()) { sock->ssl_close(); return -1; } if (context && sock->ssl_is_available()) { zval *val = php_stream_context_get_option(context, "ssl", "capture_peer_cert"); if (val && zend_is_true(val) && !php_openssl_capture_peer_certs(stream, sock)) { return -1; } } return 1; } static inline int socket_xport_api(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; switch (xparam->op) { case STREAM_XPORT_OP_LISTEN: { xparam->outputs.returncode = sock->listen(xparam->inputs.backlog) ? 0 : -1; break; } case STREAM_XPORT_OP_CONNECT: case STREAM_XPORT_OP_CONNECT_ASYNC: xparam->outputs.returncode = socket_connect(stream, sock, xparam); if (sock->ssl_is_enable() && (socket_xport_crypto_setup(stream) < 0 || socket_xport_crypto_enable(stream, 1) < 0)) { xparam->outputs.returncode = -1; } break; case STREAM_XPORT_OP_BIND: { if (sock->get_sock_domain() != AF_UNIX) { zval *tmpzval = nullptr; php_stream_context *ctx = PHP_STREAM_CONTEXT(stream); if (!ctx) { break; } #ifdef SO_REUSEADDR sock->get_socket()->set_reuse_addr(); #endif #ifdef IPV6_V6ONLY if ((tmpzval = php_stream_context_get_option(ctx, "socket", "ipv6_v6only")) != nullptr && zval_is_true(tmpzval)) { sock->get_socket()->set_option(IPPROTO_IPV6, IPV6_V6ONLY, 1); } #endif #ifdef SO_REUSEPORT if ((tmpzval = php_stream_context_get_option(ctx, "socket", "so_reuseport")) != nullptr && zval_is_true(tmpzval)) { sock->get_socket()->set_reuse_port(); } #endif #ifdef SO_BROADCAST if ((tmpzval = php_stream_context_get_option(ctx, "socket", "so_broadcast")) != nullptr && zval_is_true(tmpzval)) { sock->set_option(SOL_SOCKET, SO_BROADCAST, 1); } #endif } xparam->outputs.returncode = socket_bind(stream, sock, xparam STREAMS_CC); break; } case STREAM_XPORT_OP_ACCEPT: xparam->outputs.returncode = socket_accept(stream, sock, xparam STREAMS_CC); break; case STREAM_XPORT_OP_GET_NAME: xparam->outputs.returncode = php_network_get_sock_name(sock->get_fd(), xparam->want_textaddr ? &xparam->outputs.textaddr : nullptr, xparam->want_addr ? &xparam->outputs.addr : nullptr, xparam->want_addr ? &xparam->outputs.addrlen : nullptr); break; case STREAM_XPORT_OP_GET_PEER_NAME: xparam->outputs.returncode = php_network_get_peer_name(sock->get_fd(), xparam->want_textaddr ? &xparam->outputs.textaddr : nullptr, xparam->want_addr ? &xparam->outputs.addr : nullptr, xparam->want_addr ? &xparam->outputs.addrlen : nullptr); break; case STREAM_XPORT_OP_SEND: if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { php_swoole_error(E_WARNING, "STREAM_OOB flags is not supports"); xparam->outputs.returncode = -1; break; } xparam->outputs.returncode = socket_sendto(sock, xparam->inputs.buf, xparam->inputs.buflen, xparam->inputs.addr, xparam->inputs.addrlen); if (xparam->outputs.returncode == -1) { char *err = php_socket_strerror(php_socket_errno(), nullptr, 0); php_error_docref(nullptr, E_WARNING, "%s\n", err); efree(err); } break; case STREAM_XPORT_OP_RECV: if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { php_swoole_error(E_WARNING, "STREAM_OOB flags is not supports"); xparam->outputs.returncode = -1; break; } if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) { xparam->outputs.returncode = sock->peek(xparam->inputs.buf, xparam->inputs.buflen); } else { xparam->outputs.returncode = socket_recvfrom(sock, xparam->inputs.buf, xparam->inputs.buflen, xparam->want_textaddr ? &xparam->outputs.textaddr : nullptr, xparam->want_addr ? &xparam->outputs.addr : nullptr, xparam->want_addr ? &xparam->outputs.addrlen : nullptr); } break; case STREAM_XPORT_OP_SHUTDOWN: xparam->outputs.returncode = sock->shutdown(shutdown_how[xparam->how]); break; default: #ifdef SW_DEBUG php_swoole_fatal_error(E_WARNING, "socket_xport_api: unsupported option %d", xparam->op); #endif break; } return PHP_STREAM_OPTION_RETURN_OK; } static int socket_set_option(php_stream *stream, int option, int value, void *ptrparam) { auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract || !abstract->socket)) { return PHP_STREAM_OPTION_RETURN_ERR; } std::shared_ptr sock_wrapped = abstract->socket; auto sock = sock_wrapped.get(); switch (option) { case PHP_STREAM_OPTION_BLOCKING: if (abstract->blocking == (bool) value) { break; } abstract->blocking = (bool) value; break; case PHP_STREAM_OPTION_XPORT_API: { return socket_xport_api(stream, sock, (php_stream_xport_param *) ptrparam STREAMS_CC); } case PHP_STREAM_OPTION_META_DATA_API: { SSL *ssl = sock->get_socket() ? sock->get_socket()->ssl : nullptr; if (ssl) { zval tmp; const char *proto_str; array_init(&tmp); switch (SSL_version(ssl)) { #ifdef TLS1_3_VERSION case TLS1_3_VERSION: proto_str = "TLSv1.3"; break; #endif #ifdef TLS1_2_VERSION case TLS1_2_VERSION: proto_str = "TLSv1.2"; break; #endif #ifdef TLS1_1_VERSION case TLS1_1_VERSION: proto_str = "TLSv1.1"; break; #endif case TLS1_VERSION: proto_str = "TLSv1"; break; #ifdef SSL3_VERSION case SSL3_VERSION: proto_str = "SSLv3"; break; #endif default: proto_str = "UNKNOWN"; break; } const auto cipher = SSL_get_current_cipher(ssl); add_assoc_string(&tmp, "protocol", proto_str); add_assoc_string(&tmp, "cipher_name", SSL_CIPHER_get_name(cipher)); add_assoc_long(&tmp, "cipher_bits", SSL_CIPHER_get_bits(cipher, nullptr)); add_assoc_string(&tmp, "cipher_version", SSL_CIPHER_get_version(cipher)); add_assoc_zval((zval *) ptrparam, "crypto", &tmp); } add_assoc_bool((zval *) ptrparam, "timed_out", sock->errCode == ETIMEDOUT); add_assoc_bool((zval *) ptrparam, "eof", stream->eof); add_assoc_bool((zval *) ptrparam, "blocked", true); break; } case PHP_STREAM_OPTION_READ_TIMEOUT: { abstract->socket->set_timeout(static_cast(ptrparam), SW_TIMEOUT_READ); break; } case PHP_STREAM_OPTION_CRYPTO_API: { auto *cparam = static_cast(ptrparam); switch (cparam->op) { case STREAM_XPORT_CRYPTO_OP_SETUP: cparam->outputs.returncode = socket_setup_crypto(stream, sock, cparam STREAMS_CC); return PHP_STREAM_OPTION_RETURN_OK; case STREAM_XPORT_CRYPTO_OP_ENABLE: cparam->outputs.returncode = socket_enable_crypto(stream, sock, cparam STREAMS_CC); return PHP_STREAM_OPTION_RETURN_OK; default: /* never here */ SW_ASSERT(0); break; } break; } case PHP_STREAM_OPTION_CHECK_LIVENESS: { return sock->check_liveness() ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; } case PHP_STREAM_OPTION_READ_BUFFER: case PHP_STREAM_OPTION_WRITE_BUFFER: { // TODO: read/write buffer break; } default: #ifdef SW_DEBUG php_swoole_fatal_error(E_WARNING, "socket_set_option: unsupported option %d with value %d", option, value); #endif break; } return PHP_STREAM_OPTION_RETURN_OK; } static bool socket_ssl_set_options(SocketImpl *sock, php_stream_context *context) { if (context && ZVAL_IS_ARRAY(&context->options)) { zval *ztmp; if (sock->ssl_is_enable() && php_swoole_array_get_value(Z_ARRVAL_P(&context->options), "ssl", ztmp) && ZVAL_IS_ARRAY(ztmp)) { zval zalias; array_init(&zalias); zend_array *options = Z_ARRVAL_P(ztmp); auto add_alias = [&zalias, options](const char *name, const char *alias) { zval *ztmp; if (php_swoole_array_get_value_ex(options, name, ztmp)) { zend::array_set(&zalias, alias, strlen(alias), ztmp); } }; add_alias("peer_name", "ssl_host_name"); add_alias("verify_peer", "ssl_verify_peer"); add_alias("allow_self_signed", "ssl_allow_self_signed"); add_alias("cafile", "ssl_cafile"); add_alias("capath", "ssl_capath"); add_alias("local_cert", "ssl_cert_file"); add_alias("local_pk", "ssl_key_file"); add_alias("passphrase", "ssl_passphrase"); add_alias("verify_depth", "ssl_verify_depth"); add_alias("disable_compression", "ssl_disable_compression"); bool ret = php_swoole_socket_set_ssl(sock, &zalias); zval_dtor(&zalias); return ret; } } return true; } static php_stream *socket_create_original(const char *proto, size_t protolen, const char *resourcename, size_t resourcenamelen, const char *persistent_id, int options, int flags, struct timeval *timeout, php_stream_context *context STREAMS_DC) { php_stream_transport_factory factory = nullptr; if (SW_STREQ(proto, protolen, "tcp")) { factory = ori_factory.tcp; } else if (SW_STREQ(proto, protolen, "ssl")) { factory = ori_factory.ssl; } else if (SW_STREQ(proto, protolen, "tls")) { factory = ori_factory.tls; } else if (SW_STREQ(proto, protolen, "unix")) { factory = ori_factory._unix; } else if (SW_STREQ(proto, protolen, "udp")) { factory = ori_factory.udp; } else if (SW_STREQ(proto, protolen, "udg")) { factory = ori_factory.udg; } if (factory) { return factory( proto, protolen, resourcename, resourcenamelen, persistent_id, options, flags, timeout, context STREAMS_CC); } else { php_swoole_fatal_error(E_WARNING, "unknown protocol '%s'", proto); return nullptr; } } static php_stream *socket_create(const char *proto, size_t protolen, const char *resourcename, size_t resourcenamelen, const char *persistent_id, int options, int flags, struct timeval *timeout, php_stream_context *context STREAMS_DC) { php_stream *stream = nullptr; SocketImpl *sock = nullptr; auto co = Coroutine::get_current(); if (sw_unlikely(co == nullptr)) { return socket_create_original( proto, protolen, resourcename, resourcenamelen, persistent_id, options, flags, timeout, context STREAMS_CC); } if (SW_STR_STARTS_WITH(proto, protolen, "async.")) { proto += sizeof("async.") - 1; protolen -= sizeof("async.") - 1; } if (SW_STREQ(proto, protolen, "tcp")) { sock = new SocketImpl(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); } else if (SW_STREQ(proto, protolen, "ssl") || SW_STREQ(proto, protolen, "tls")) { sock = new SocketImpl(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); sock->enable_ssl_encrypt(); } else if (SW_STREQ(proto, protolen, "unix")) { sock = new SocketImpl(SW_SOCK_UNIX_STREAM); } else if (SW_STREQ(proto, protolen, "udp")) { sock = new SocketImpl(SW_SOCK_UDP); } else if (SW_STREQ(proto, protolen, "udg")) { sock = new SocketImpl(SW_SOCK_UNIX_DGRAM); } else { php_swoole_fatal_error(E_WARNING, "unknown protocol '%s'", proto); return nullptr; } if (UNEXPECTED(sock->get_fd() < 0)) { _failed: if (!stream) { delete sock; } else { php_stream_close(stream); } return nullptr; } sock->set_zero_copy(true); auto abstract = new NetStream(); abstract->socket.reset(sock); abstract->stream.socket = sock->get_fd(); abstract->blocking = true; stream = php_stream_alloc_rel(&socket_ops, abstract, persistent_id, "r+"); if (stream == nullptr) { delete abstract; goto _failed; } if (!socket_ssl_set_options(sock, context)) { goto _failed; } return stream; } static ZEND_FUNCTION(swoole_display_disabled_function) { zend_error(E_WARNING, "%s() has been disabled for security reasons", get_active_function_name()); } static bool disable_func(const char *name, size_t l_name) { auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (rf) { rf->function->internal_function.handler = ZEND_FN(swoole_display_disabled_function); return true; } auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { return false; } rf = static_cast(emalloc(sizeof(PhpFunc))); sw_memset_zero(rf, sizeof(*rf)); rf->function = zf; rf->ori_handler = zf->internal_function.handler; rf->ori_arg_info = zf->internal_function.arg_info; rf->ori_fn_flags = zf->internal_function.fn_flags; zf->internal_function.handler = ZEND_FN(swoole_display_disabled_function); zf->internal_function.fn_flags = ZEND_ACC_FAKE_CLOSURE; zend_hash_add_ptr(hook_function_table, zf->common.function_name, rf); return true; } static bool enable_func(const char *name, size_t l_name) { auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (!rf) { return false; } rf->function->internal_function.handler = rf->ori_handler; rf->function->internal_function.fn_flags = rf->ori_fn_flags; return true; } bool php_swoole_call_original_handler(const char *name, size_t len, INTERNAL_FUNCTION_PARAMETERS) { auto ori_handler = php_swoole_get_original_handler(name, len); if (!ori_handler) { return false; } ori_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return true; } void PHPCoroutine::disable_unsafe_function() { for (auto &f : unsafe_functions) { disable_func(f.c_str(), f.length()); } } void PHPCoroutine::enable_unsafe_function() { for (auto &f : unsafe_functions) { enable_func(f.c_str(), f.length()); } } static void hook_stream_throw_exception(const char *type) { zend_throw_exception_ex( swoole_exception_ce, SW_ERROR_PHP_FATAL_ERROR, "failed to register `%s` stream transport factory", type); } static void hook_stream_factory(uint32_t *flags_ptr) { uint32_t flags = *flags_ptr; if (flags & PHPCoroutine::HOOK_TCP) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_TCP)) { if (php_stream_xport_register("tcp", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_TCP; hook_stream_throw_exception("tcp"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_TCP) { php_stream_xport_register("tcp", ori_factory.tcp); } } if (flags & PHPCoroutine::HOOK_UDP) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UDP)) { if (php_stream_xport_register("udp", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UDP; hook_stream_throw_exception("udp"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_UDP) { php_stream_xport_register("udp", ori_factory.udp); } } if (flags & PHPCoroutine::HOOK_UNIX) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UNIX)) { if (php_stream_xport_register("unix", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UNIX; hook_stream_throw_exception("unix"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_UNIX) { php_stream_xport_register("unix", ori_factory._unix); } } if (flags & PHPCoroutine::HOOK_UDG) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UDG)) { if (php_stream_xport_register("udg", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UDG; hook_stream_throw_exception("udg"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_UDG) { php_stream_xport_register("udg", ori_factory.udg); } } if (flags & PHPCoroutine::HOOK_SSL) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_SSL)) { if (php_stream_xport_register("ssl", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_SSL; hook_stream_throw_exception("ssl"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_SSL) { if (ori_factory.ssl != nullptr) { php_stream_xport_register("ssl", ori_factory.ssl); } else { php_stream_xport_unregister("ssl"); } } } if (flags & PHPCoroutine::HOOK_TLS) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_TLS)) { if (php_stream_xport_register("tls", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_TLS; hook_stream_throw_exception("tls"); } } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_TLS) { if (ori_factory.tls != nullptr) { php_stream_xport_register("tls", ori_factory.tls); } else { php_stream_xport_unregister("tls"); } } } *flags_ptr = flags; } static void hook_stream_ops(uint32_t flags) { // file if (flags & PHPCoroutine::HOOK_FILE) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_FILE)) { memcpy(&php_plain_files_wrapper, &sw_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_FILE) { memcpy(&php_plain_files_wrapper, &ori_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); } } // stdio if (flags & PHPCoroutine::HOOK_STDIO) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_STDIO)) { memcpy(&php_stream_stdio_ops, &sw_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_STDIO) { memcpy(&php_stream_stdio_ops, &ori_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); } } } static void hook_pdo_driver(uint32_t flags) { #ifdef SW_USE_PGSQL if (flags & PHPCoroutine::HOOK_PDO_PGSQL) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_PGSQL)) { swoole_pgsql_set_blocking(false); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_PGSQL) { swoole_pgsql_set_blocking(true); } } #endif #ifdef SW_USE_ODBC if (flags & PHPCoroutine::HOOK_PDO_ODBC) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_ODBC)) { swoole_odbc_set_blocking(false); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_ODBC) { swoole_odbc_set_blocking(true); } } #endif #ifdef SW_USE_ORACLE if (flags & PHPCoroutine::HOOK_PDO_ORACLE) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_ORACLE)) { swoole_oracle_set_blocking(0); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_ORACLE) { swoole_oracle_set_blocking(1); } } #endif #ifdef SW_USE_SQLITE if (flags & PHPCoroutine::HOOK_PDO_SQLITE) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_SQLITE)) { swoole_sqlite_set_blocking(false); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_SQLITE) { swoole_sqlite_set_blocking(true); } } #endif #ifdef SW_USE_FIREBIRD if (flags & PHPCoroutine::HOOK_PDO_FIREBIRD) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_FIREBIRD)) { swoole_firebird_set_blocking(false); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_FIREBIRD) { swoole_firebird_set_blocking(true); } } #endif } static void hook_all_func(uint32_t flags) { // stream func if (flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION)) { SW_HOOK_FUNC(stream_select); SW_HOOK_FUNC(stream_socket_pair); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { SW_UNHOOK_FUNC(stream_select); SW_UNHOOK_FUNC(stream_socket_pair); } } // sleep if (flags & PHPCoroutine::HOOK_SLEEP) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_SLEEP)) { SW_HOOK_FUNC(sleep); SW_HOOK_FUNC(usleep); SW_HOOK_FUNC(time_nanosleep); SW_HOOK_FUNC(time_sleep_until); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_SLEEP) { SW_UNHOOK_FUNC(sleep); SW_UNHOOK_FUNC(usleep); SW_UNHOOK_FUNC(time_nanosleep); SW_UNHOOK_FUNC(time_sleep_until); } } // proc_open if (flags & PHPCoroutine::HOOK_PROC) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_PROC)) { SW_HOOK_FUNC(proc_open); SW_HOOK_FUNC(proc_close); SW_HOOK_FUNC(proc_get_status); SW_HOOK_FUNC(proc_terminate); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_PROC) { SW_UNHOOK_FUNC(proc_open); SW_UNHOOK_FUNC(proc_close); SW_UNHOOK_FUNC(proc_get_status); SW_UNHOOK_FUNC(proc_terminate); } } // ext-sockets if (flags & PHPCoroutine::HOOK_SOCKETS) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_SOCKETS)) { SW_HOOK_WITH_PHP_FUNC(socket_create); SW_HOOK_WITH_PHP_FUNC(socket_create_listen); SW_HOOK_WITH_PHP_FUNC(socket_create_pair); SW_HOOK_WITH_PHP_FUNC(socket_connect); SW_HOOK_WITH_PHP_FUNC(socket_write); SW_HOOK_WITH_PHP_FUNC(socket_read); SW_HOOK_WITH_PHP_FUNC(socket_send); SW_HOOK_WITH_PHP_FUNC(socket_recv); SW_HOOK_WITH_PHP_FUNC(socket_sendto); SW_HOOK_WITH_PHP_FUNC(socket_recvfrom); SW_HOOK_WITH_PHP_FUNC(socket_bind); SW_HOOK_WITH_PHP_FUNC(socket_listen); SW_HOOK_WITH_PHP_FUNC(socket_accept); SW_HOOK_WITH_PHP_FUNC(socket_getpeername); SW_HOOK_WITH_PHP_FUNC(socket_getsockname); SW_HOOK_WITH_PHP_FUNC(socket_getopt); SW_HOOK_WITH_PHP_FUNC(socket_get_option); SW_HOOK_WITH_PHP_FUNC(socket_setopt); SW_HOOK_WITH_PHP_FUNC(socket_set_option); SW_HOOK_WITH_PHP_FUNC(socket_set_block); SW_HOOK_WITH_PHP_FUNC(socket_set_nonblock); SW_HOOK_WITH_PHP_FUNC(socket_shutdown); SW_HOOK_WITH_PHP_FUNC(socket_close); SW_HOOK_WITH_PHP_FUNC(socket_clear_error); SW_HOOK_WITH_PHP_FUNC(socket_last_error); SW_HOOK_WITH_PHP_FUNC(socket_import_stream); inherit_class(ZEND_STRL("Swoole\\Coroutine\\Socket"), ZEND_STRL("Socket")); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_SOCKETS) { SW_UNHOOK_FUNC(socket_create); SW_UNHOOK_FUNC(socket_create_listen); SW_UNHOOK_FUNC(socket_create_pair); SW_UNHOOK_FUNC(socket_connect); SW_UNHOOK_FUNC(socket_write); SW_UNHOOK_FUNC(socket_read); SW_UNHOOK_FUNC(socket_send); SW_UNHOOK_FUNC(socket_recv); SW_UNHOOK_FUNC(socket_sendto); SW_UNHOOK_FUNC(socket_recvfrom); SW_UNHOOK_FUNC(socket_bind); SW_UNHOOK_FUNC(socket_listen); SW_UNHOOK_FUNC(socket_accept); SW_UNHOOK_FUNC(socket_getpeername); SW_UNHOOK_FUNC(socket_getsockname); SW_UNHOOK_FUNC(socket_getopt); SW_UNHOOK_FUNC(socket_get_option); SW_UNHOOK_FUNC(socket_setopt); SW_UNHOOK_FUNC(socket_set_option); SW_UNHOOK_FUNC(socket_set_block); SW_UNHOOK_FUNC(socket_set_nonblock); SW_UNHOOK_FUNC(socket_shutdown); SW_UNHOOK_FUNC(socket_close); SW_UNHOOK_FUNC(socket_clear_error); SW_UNHOOK_FUNC(socket_last_error); SW_UNHOOK_FUNC(socket_import_stream); detach_parent_class("Swoole\\Coroutine\\Socket"); } } #ifdef SW_USE_CURL // curl native if (flags & PHPCoroutine::HOOK_NATIVE_CURL) { if (flags & PHPCoroutine::HOOK_CURL) { php_swoole_fatal_error(E_WARNING, "cannot enable both hooks HOOK_NATIVE_CURL and HOOK_CURL at same time"); flags ^= PHPCoroutine::HOOK_CURL; } if (!(runtime_hook_flags & PHPCoroutine::HOOK_NATIVE_CURL)) { SW_HOOK_WITH_NATIVE_FUNC(curl_close); SW_HOOK_WITH_NATIVE_FUNC(curl_copy_handle); SW_HOOK_WITH_NATIVE_FUNC(curl_errno); SW_HOOK_WITH_NATIVE_FUNC(curl_error); SW_HOOK_WITH_NATIVE_FUNC(curl_exec); SW_HOOK_WITH_NATIVE_FUNC(curl_getinfo); SW_HOOK_WITH_NATIVE_FUNC(curl_init); SW_HOOK_WITH_NATIVE_FUNC(curl_setopt); SW_HOOK_WITH_NATIVE_FUNC(curl_setopt_array); SW_HOOK_WITH_NATIVE_FUNC(curl_reset); SW_HOOK_WITH_NATIVE_FUNC(curl_pause); SW_HOOK_WITH_NATIVE_FUNC(curl_escape); SW_HOOK_WITH_NATIVE_FUNC(curl_unescape); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_init); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_add_handle); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_exec); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_errno); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_select); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_setopt); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_getcontent); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_info_read); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_remove_handle); SW_HOOK_WITH_NATIVE_FUNC(curl_multi_close); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_NATIVE_CURL) { SW_UNHOOK_FUNC(curl_close); SW_UNHOOK_FUNC(curl_copy_handle); SW_UNHOOK_FUNC(curl_errno); SW_UNHOOK_FUNC(curl_error); SW_UNHOOK_FUNC(curl_exec); SW_UNHOOK_FUNC(curl_getinfo); SW_UNHOOK_FUNC(curl_init); SW_UNHOOK_FUNC(curl_setopt); SW_UNHOOK_FUNC(curl_setopt_array); SW_UNHOOK_FUNC(curl_reset); SW_UNHOOK_FUNC(curl_pause); SW_UNHOOK_FUNC(curl_escape); SW_UNHOOK_FUNC(curl_unescape); SW_UNHOOK_FUNC(curl_multi_init); SW_UNHOOK_FUNC(curl_multi_add_handle); SW_UNHOOK_FUNC(curl_multi_exec); SW_UNHOOK_FUNC(curl_multi_errno); SW_UNHOOK_FUNC(curl_multi_select); SW_UNHOOK_FUNC(curl_multi_setopt); SW_UNHOOK_FUNC(curl_multi_getcontent); SW_UNHOOK_FUNC(curl_multi_info_read); SW_UNHOOK_FUNC(curl_multi_remove_handle); SW_UNHOOK_FUNC(curl_multi_close); } } #endif // curl if (flags & PHPCoroutine::HOOK_CURL) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_CURL)) { SW_HOOK_WITH_PHP_FUNC(curl_init); SW_HOOK_WITH_PHP_FUNC(curl_setopt); SW_HOOK_WITH_PHP_FUNC(curl_setopt_array); SW_HOOK_WITH_PHP_FUNC(curl_exec); SW_HOOK_WITH_PHP_FUNC(curl_getinfo); SW_HOOK_WITH_PHP_FUNC(curl_errno); SW_HOOK_WITH_PHP_FUNC(curl_error); SW_HOOK_WITH_PHP_FUNC(curl_reset); SW_HOOK_WITH_PHP_FUNC(curl_close); SW_HOOK_WITH_PHP_FUNC(curl_multi_getcontent); inherit_class(ZEND_STRL("Swoole\\Curl\\Handler"), ZEND_STRL("CurlHandle")); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_CURL) { SW_UNHOOK_FUNC(curl_init); SW_UNHOOK_FUNC(curl_setopt); SW_UNHOOK_FUNC(curl_setopt_array); SW_UNHOOK_FUNC(curl_exec); SW_UNHOOK_FUNC(curl_getinfo); SW_UNHOOK_FUNC(curl_errno); SW_UNHOOK_FUNC(curl_error); SW_UNHOOK_FUNC(curl_reset); SW_UNHOOK_FUNC(curl_close); SW_UNHOOK_FUNC(curl_multi_getcontent); detach_parent_class("Swoole\\Curl\\Handler"); } } // network functions if (flags & PHPCoroutine::HOOK_NET_FUNCTION) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_NET_FUNCTION)) { hook_func(ZEND_STRL("gethostbyname"), PHP_FN(swoole_coroutine_gethostbyname)); SW_HOOK_WITH_PHP_FUNC(gethostbynamel); SW_HOOK_WITH_PHP_FUNC(mail); SW_HOOK_WITH_PHP_FUNC(dns_check_record); SW_HOOK_WITH_PHP_FUNC(checkdnsrr); SW_HOOK_WITH_PHP_FUNC(dns_get_mx); SW_HOOK_WITH_PHP_FUNC(getmxrr); SW_HOOK_WITH_PHP_FUNC(dns_get_record); SW_HOOK_WITH_PHP_FUNC(gethostbyaddr); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_NET_FUNCTION) { SW_UNHOOK_FUNC(gethostbyname); SW_UNHOOK_FUNC(gethostbynamel); SW_UNHOOK_FUNC(mail); SW_UNHOOK_FUNC(dns_check_record); SW_UNHOOK_FUNC(checkdnsrr); SW_UNHOOK_FUNC(dns_get_mx); SW_UNHOOK_FUNC(getmxrr); SW_UNHOOK_FUNC(dns_get_record); SW_UNHOOK_FUNC(gethostbyaddr); } } } static void hook_mongodb(uint32_t flags) { if (flags & PHPCoroutine::HOOK_MONGODB) { auto alias_name = R"(MongoDB\Client)"; if (!(runtime_hook_flags & PHPCoroutine::HOOK_MONGODB) && !class_exists(alias_name)) { zend::String class_name(R"(Swoole\MongoDB\Client)"); auto ce = zend_lookup_class_ex(class_name.get(), NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (ce) { if (zend_register_class_alias(alias_name, ce) != SUCCESS) { zend_error(E_WARNING, "Cannot declare class %s, because the name is already in use", alias_name); } } } } } bool PHPCoroutine::enable_hook(uint32_t flags) { if (!sw_is_main_thread() || sw_active_thread_count() > 1) { swoole_warning("The runtime hook can only set on the main thread and no child threads have been created"); return false; } if (swoole_isset_hook((swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK)) { swoole_call_hook((swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK, &flags); } if (!extension_loaded("sockets")) { sw_unset_bit(flags, HOOK_SOCKETS); } if (!SWOOLE_G(enable_library)) { sw_unset_bit(flags, HOOK_NET_FUNCTION); sw_unset_bit(flags, HOOK_MONGODB); } hook_stream_factory(&flags); hook_stream_ops(flags); hook_pdo_driver(flags); hook_all_func(flags); hook_mongodb(flags); if (swoole_isset_hook((swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK)) { swoole_call_hook((swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK, &flags); } runtime_hook_flags = flags; return true; } bool PHPCoroutine::disable_hook() { return enable_hook(0); } static PHP_METHOD(swoole_runtime, enableCoroutine) { if (!SWOOLE_G(cli)) { php_swoole_fatal_error(E_ERROR, "must be used in PHP CLI mode"); RETURN_FALSE; } zend_long flags = PHPCoroutine::HOOK_ALL; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); PHPCoroutine::set_hook_flags(flags); RETURN_BOOL(PHPCoroutine::enable_hook(flags)); } static PHP_METHOD(swoole_runtime, getHookFlags) { RETURN_LONG(PHPCoroutine::get_hook_flags()); } static PHP_METHOD(swoole_runtime, setHookFlags) { if (!SWOOLE_G(cli)) { php_swoole_fatal_error(E_ERROR, "must be used in PHP CLI mode"); RETURN_FALSE; } zend_long flags = PHPCoroutine::HOOK_ALL; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); PHPCoroutine::set_hook_flags(flags); RETURN_BOOL(PHPCoroutine::enable_hook(flags)); } static PHP_FUNCTION(swoole_sleep) { zend_long num; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(num) ZEND_PARSE_PARAMETERS_END(); if (num < 0) { zend_argument_value_error(1, "must be greater than or equal to 0"); RETURN_THROWS(); } if (Coroutine::get_current()) { RETURN_LONG(System::sleep((double) num) < 0 ? num : 0); } else { RETURN_LONG(php_sleep((unsigned int) num)); } } static PHP_FUNCTION(swoole_usleep) { zend_long num; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(num) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (num < 0) { zend_argument_value_error(1, "must be greater than or equal to 0"); RETURN_THROWS(); } double sec = (double) num / 1000000; if (Coroutine::get_current()) { System::sleep(sec); } else { usleep((unsigned int) num); } } static PHP_FUNCTION(swoole_time_nanosleep) { zend_long tv_sec, tv_nsec; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(tv_sec) Z_PARAM_LONG(tv_nsec) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (tv_sec < 0) { zend_argument_value_error(1, "must be greater than or equal to 0"); RETURN_THROWS(); } if (tv_nsec < 0) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); } double _time = (double) tv_sec + (double) tv_nsec / 1000000000.00; if (Coroutine::get_current()) { System::sleep(_time); } else { struct timespec php_req, php_rem; php_req.tv_sec = (time_t) tv_sec; php_req.tv_nsec = (long) tv_nsec; if (nanosleep(&php_req, &php_rem) == 0) { RETURN_TRUE; } else if (errno == EINTR) { array_init(return_value); add_assoc_long_ex(return_value, "seconds", sizeof("seconds") - 1, php_rem.tv_sec); add_assoc_long_ex(return_value, "nanoseconds", sizeof("nanoseconds") - 1, php_rem.tv_nsec); } else if (errno == EINVAL) { php_swoole_error(E_WARNING, "nanoseconds was not in the range 0 to 999 999 999 or seconds was negative"); } } RETURN_TRUE; } static PHP_FUNCTION(swoole_time_sleep_until) { double target_secs; const uint64_t ns_per_sec = 1000000000; const double top_target_sec = (double) (UINT64_MAX / ns_per_sec); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(target_secs) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(!(target_secs >= 0 && target_secs <= top_target_sec))) { zend_argument_value_error(1, "must be between 0 and %" PRIu64, (uint64_t) top_target_sec); RETURN_THROWS(); } using namespace std::chrono; using dseconds = duration; const auto now = system_clock::now(); const auto target_duration = duration_cast(dseconds(target_secs)); const auto target_time = system_clock::time_point(target_duration); if (target_time <= now) { php_error_docref(NULL, E_WARNING, "Argument #1 ($timestamp) must be greater than or equal to the current time"); RETURN_FALSE; } const auto sleep_duration = target_time - now; if (Coroutine::get_current()) { const double sleep_seconds = duration_cast(sleep_duration).count(); System::sleep(sleep_seconds); } else { std::this_thread::sleep_until(target_time); } RETURN_TRUE; } static void stream_array_to_fd_set(zval *stream_array, std::unordered_map &fds, int event) { zval *elem; zend_ulong index; zend_string *key; if (!ZVAL_IS_ARRAY(stream_array)) { return; } ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), index, key, elem) { ZVAL_DEREF(elem); php_socket_t sock = php_swoole_convert_to_fd(elem); if (sock < 0) { continue; } auto i = fds.find(sock); if (i == fds.end()) { fds.emplace(sock, PollSocket(event, new zend::KeyValue(index, key, elem))); } else { i->second.events |= event; } } ZEND_HASH_FOREACH_END(); } static int stream_array_emulate_read_fd_set(zval *stream_array) { zval *elem, new_array; php_stream *stream; int ret = 0; zend_ulong num_ind; zend_string *key; if (!ZVAL_IS_ARRAY(stream_array)) { return 0; } array_init_size(&new_array, zend_hash_num_elements(Z_ARRVAL_P(stream_array))); HashTable *ht = Z_ARRVAL(new_array); ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) { ZVAL_DEREF(elem); php_stream_from_zval_no_verify(stream, elem); if (stream == nullptr) { continue; } if ((stream->writepos - stream->readpos) > 0) { /* allow readable non-descriptor based streams to participate in stream_select. * Non-descriptor streams will only "work" if they have previously buffered the * data. Not ideal, but better than nothing. * This branch of code also allows blocking streams with buffered data to * operate correctly in stream_select. * */ zval *dest_elem = !key ? zend_hash_index_update(ht, num_ind, elem) : zend_hash_update(ht, key, elem); zval_add_ref(dest_elem); ret++; continue; } } ZEND_HASH_FOREACH_END(); if (ret > 0) { /* destroy old array and add new one */ zend_array_destroy(Z_ARR_P(stream_array)); ZVAL_ARR(stream_array, ht); } else { zend_array_destroy(ht); } return ret; } static PHP_FUNCTION(swoole_stream_select) { Coroutine::get_current_safe(); zval *r_array, *w_array, *e_array; zend_long sec, usec = 0; zend_bool secnull; bool usecnull = true; int retval = 0; ZEND_PARSE_PARAMETERS_START(4, 5) Z_PARAM_ARRAY_EX2(r_array, 1, 1, 1) Z_PARAM_ARRAY_EX2(w_array, 1, 1, 1) Z_PARAM_ARRAY_EX2(e_array, 1, 1, 1) Z_PARAM_LONG_OR_NULL(sec, secnull) Z_PARAM_OPTIONAL Z_PARAM_LONG_OR_NULL(usec, usecnull) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (secnull && !usecnull) { if (usec != 0) { zend_argument_value_error(5, "must be null when argument #4 ($seconds) is null"); RETURN_THROWS(); } } double timeout = -1; if (!secnull) { if (sec < 0) { php_error_docref(nullptr, E_WARNING, "The seconds parameter must be greater than 0"); RETURN_FALSE; } else if (usec < 0) { php_error_docref(nullptr, E_WARNING, "The microseconds parameter must be greater than 0"); RETURN_FALSE; } timeout = (double) sec + ((double) usec / 1000000); } std::unordered_map fds; if (r_array != nullptr) { stream_array_to_fd_set(r_array, fds, SW_EVENT_READ); } if (w_array != nullptr) { stream_array_to_fd_set(w_array, fds, SW_EVENT_WRITE); } if (e_array != nullptr) { stream_array_to_fd_set(e_array, fds, SW_EVENT_ERROR); } if (fds.empty()) { php_error_docref(nullptr, E_WARNING, "No stream arrays were passed"); RETURN_FALSE; } ON_SCOPE_EXIT { for (auto &i : fds) { const auto *kv = static_cast(i.second.ptr); delete kv; } }; /* slight hack to support buffered data; if there is data sitting in the * read buffer of the streams in the read array, let's pretend * that we selected, but return only the readable sockets */ if (r_array != nullptr) { retval = stream_array_emulate_read_fd_set(r_array); if (retval > 0) { if (w_array != nullptr) { zend_hash_clean(Z_ARRVAL_P(w_array)); } if (e_array != nullptr) { zend_hash_clean(Z_ARRVAL_P(e_array)); } RETURN_LONG(retval); } } if (r_array != nullptr) { zend_hash_clean(Z_ARRVAL_P(r_array)); } if (w_array != nullptr) { zend_hash_clean(Z_ARRVAL_P(w_array)); } if (e_array != nullptr) { zend_hash_clean(Z_ARRVAL_P(e_array)); } /** * timeout or add failed */ if (!System::socket_poll(fds, timeout)) { RETURN_LONG(0); } for (auto &i : fds) { auto *kv = static_cast(i.second.ptr); int revents = i.second.revents; SW_ASSERT((revents & (~(SW_EVENT_READ | SW_EVENT_WRITE | SW_EVENT_ERROR))) == 0); if (revents > 0) { if ((revents & SW_EVENT_READ) && r_array) { kv->add_to(r_array); } if ((revents & SW_EVENT_WRITE) && w_array) { kv->add_to(w_array); } if ((revents & SW_EVENT_ERROR) && e_array) { kv->add_to(e_array); } retval++; } } RETURN_LONG(retval); } static void hook_func(const char *name, size_t l_name, zif_handler handler, zend_internal_arg_info *arg_info) { auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); bool use_php_func = false; /** * use php library function */ if (handler == nullptr) { handler = PHP_FN(swoole_user_func_handler); use_php_func = true; } if (rf) { rf->function->internal_function.handler = handler; if (arg_info) { rf->function->internal_function.arg_info = arg_info; } return; } auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { swoole_warning("The function named `%s` is not found", name); return; } auto fn_str = zf->common.function_name; rf = static_cast(emalloc(sizeof(PhpFunc))); sw_memset_zero(rf, sizeof(*rf)); rf->function = zf; auto fn_name = std::string(fn_str->val, fn_str->len); if (!ori_func_arg_infos.exists(fn_name)) { ori_func_handlers.set(fn_name, zf->internal_function.handler); ori_func_arg_infos.set(fn_name, zf->internal_function.arg_info); } rf->ori_handler = ori_func_handlers.get(fn_name); rf->ori_arg_info = ori_func_arg_infos.get(fn_name); zf->internal_function.handler = handler; if (arg_info) { zf->internal_function.arg_info = copy_arginfo(&zf->internal_function, arg_info); rf->arg_info_copy = zf->internal_function.arg_info; } if (use_php_func) { char func[128]; memcpy(func, ZEND_STRL("swoole_")); memcpy(func + 7, fn_str->val, fn_str->len); ZVAL_STRINGL(&rf->name, func, fn_str->len + 7); rf->fci_cache = sw_callable_create(&rf->name); } zend_hash_add_ptr(hook_function_table, fn_str, rf); } static void unhook_func(const char *name, size_t l_name) { auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (rf == nullptr) { return; } rf->function->internal_function.handler = rf->ori_handler; rf->function->internal_function.arg_info = rf->ori_arg_info; } php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, int type, int protocol STREAMS_DC) { auto *abstract = new NetStream(); abstract->socket = std::make_shared(_fd, domain, type, protocol); if (FG(default_socket_timeout) > 0) { abstract->socket->set_timeout((double) FG(default_socket_timeout)); } abstract->stream.timeout.tv_sec = FG(default_socket_timeout); abstract->stream.socket = abstract->socket->get_fd(); abstract->blocking = true; php_stream *stream = php_stream_alloc_rel(&socket_ops, abstract, nullptr, "r+"); if (stream == nullptr) { delete abstract; } else { stream->flags |= PHP_STREAM_FLAG_AVOID_BLOCKING; } return stream; } zend_string *php_async_socket_error_str(long err) { const char *errstr = swoole_strerror(err); return zend_string_init(errstr, strlen(errstr), 0); } int php_async_socket_connect_to_host(const char *host, unsigned short port, int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string, int *error_code, const char *bindto, unsigned short bindport, long sockopts) { if (!swoole_coroutine_is_in()) { php_swoole_fatal_error(E_WARNING, "This API must be called in coroutine"); return -1; } auto domain = swoole::network::Address::verify_ip(AF_INET6, host) ? AF_INET6 : AF_INET; auto sock = swoole_coroutine_socket(domain, SOCK_STREAM, 0); if (sock < 0) { return -1; } auto sockobj = swoole_coroutine_get_socket_object(sock); if (bindto) { if (!sockobj->bind(bindto, bindport)) { php_swoole_fatal_error(E_WARNING, "Failed to bind to '%s:%d', system said: %s", bindto, bindport, swoole_strerror(sockobj->errCode)); } } #ifdef SO_BROADCAST if (sockopts & STREAM_SOCKOP_SO_BROADCAST) { sockobj->set_option(SOL_SOCKET, SO_BROADCAST, 1); } #endif #ifdef TCP_NODELAY if (sockopts & STREAM_SOCKOP_TCP_NODELAY) { sockobj->set_option(IPPROTO_TCP, TCP_NODELAY, 1); } #endif if (timeout) { sockobj->set_timeout(timeout, SW_TIMEOUT_ALL); } if (!sockobj->connect(host, port)) { if (error_code) { *error_code = sockobj->errCode; } if (error_string) { *error_string = php_async_socket_error_str(sockobj->errCode); } if (sockobj->errCode == SW_ERROR_DNSLOOKUP_RESOLVE_FAILED) { php_swoole_fatal_error(E_WARNING, "getaddrinfo for '%s' failed, error: %s", host, sockobj->get_err()); } swoole_coroutine_close(sock); return -1; } return sock; } int php_async_socket_poll(php_socket_t fd, int events, int timeout) { auto sockobj = swoole_coroutine_get_socket_object(fd); if (!sockobj) { return -1; } return sockobj->poll(events & POLLOUT ? SW_EVENT_WRITE : SW_EVENT_READ, timeout / 1000.0); } php_stream *php_swoole_create_stream_from_pipe(int fd, const char *mode, const char *persistent_id STREAMS_DC) { return _sw_php_stream_fopen_from_fd(fd, mode, persistent_id, false STREAMS_CC); } php_stream_ops *php_swoole_get_ori_php_stream_stdio_ops() { return &ori_php_stream_stdio_ops; } zif_handler php_swoole_get_original_handler(const char *name, size_t len) { zif_handler handler = ori_func_handlers.get(std::string(name, len)); if (handler) { return handler; } auto *zf = zend::get_function(name, len); if (zf && zf->type == ZEND_INTERNAL_FUNCTION && zf->internal_function.handler) { return zf->internal_function.handler; } return nullptr; } static PHP_FUNCTION(swoole_stream_socket_pair) { zend_long domain, type, protocol; php_socket_t pair[2]; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_LONG(domain) Z_PARAM_LONG(type) Z_PARAM_LONG(protocol) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (0 != socketpair((int) domain, (int) type, (int) protocol, pair)) { php_swoole_error(E_WARNING, "failed to create sockets: [%d]: %s", errno, strerror(errno)); RETURN_FALSE; } array_init(return_value); php_swoole_check_reactor(); php_stream *s1 = php_swoole_create_stream_from_socket(pair[0], domain, type, protocol STREAMS_CC); php_stream *s2 = php_swoole_create_stream_from_socket(pair[1], domain, type, protocol STREAMS_CC); /* set the __exposed flag. * php_stream_to_zval() does, add_next_index_resource() does not */ php_stream_auto_cleanup(s1); php_stream_auto_cleanup(s2); add_next_index_resource(return_value, s1->res); add_next_index_resource(return_value, s2->res); } static PHP_FUNCTION(swoole_user_func_handler) { auto fn_str = execute_data->func->common.function_name; if (!swoole_coroutine_is_in()) { auto ori_handler = ori_func_handlers.get(std::string(fn_str->val, fn_str->len)); ori_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return; } auto *rf = static_cast(zend_hash_find_ptr(hook_function_table, fn_str)); if (!rf || !rf->fci_cache) { zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_UNDEFINED_BEHAVIOR, "%s func not exists", fn_str->val); return; } zend_fcall_info fci; fci.size = sizeof(fci); fci.object = nullptr; fci.retval = return_value; fci.param_count = ZEND_NUM_ARGS(); fci.params = ZEND_CALL_ARG(execute_data, 1); fci.named_params = nullptr; ZVAL_UNDEF(&fci.function_name); zend_call_function(&fci, rf->fci_cache->ptr()); } zend_class_entry *find_class_entry(const char *name, size_t length) { zend_string *search_key = zend_string_init(name, length, false); zend_class_entry *class_ce = zend_lookup_class(search_key); zend_string_release(search_key); return class_ce ? class_ce : nullptr; } static void inherit_class(const char *child_name, size_t child_length, const char *parent_name, size_t parent_length) { zend_class_entry *temp_ce = nullptr; zend_class_entry *child_ce = find_class_entry(child_name, child_length); zend_class_entry *parent_ce = find_class_entry(parent_name, parent_length); if (!child_ce || !parent_ce || instanceof_function(child_ce, parent_ce)) { return; } temp_ce = child_ce; while (temp_ce->parent) { temp_ce = temp_ce->parent; } temp_ce->parent = parent_ce; std::string key(ZSTR_VAL(child_ce->name)); child_class_entries.insert({key, child_ce}); } void start_detach_parent_class(zend_class_entry *class_ce) { zend_class_entry *p1 = nullptr; zend_class_entry *p2 = nullptr; p1 = class_ce; p2 = class_ce->parent; while (p2->parent) { p1 = p1->parent; p2 = p2->parent; } p1->parent = nullptr; } static void detach_parent_class(const char *child_name) { std::string search_key(child_name); auto iter = child_class_entries.find(search_key); if (iter == child_class_entries.end()) { return; } start_detach_parent_class(iter->second); child_class_entries.erase(search_key); } static void clear_class_entries() { for (const auto &child_class_entry : child_class_entries) { start_detach_parent_class(child_class_entry.second); } child_class_entries.clear(); } #if defined(HAVE_PUTENV) && defined(SW_THREAD) /* {{{ Set the value of an environment variable */ static PHP_FUNCTION(swoole_putenv) { char *setting; size_t setting_len; char *p; bool result; std::string key; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(setting, setting_len) ZEND_PARSE_PARAMETERS_END(); if (setting_len == 0 || setting[0] == '=') { zend_argument_value_error(1, "must have a valid syntax"); RETURN_THROWS(); } if ((p = strchr(setting, '='))) { key = std::string(setting, p - setting); } else { key = std::string(setting, setting_len); } tsrm_env_lock(); swoole_runtime_environ[key] = std::string(setting, setting_len); auto iter = swoole_runtime_environ.find(key); #ifdef HAVE_UNSETENV if (!p) { /* no '=' means we want to unset it */ unsetenv(iter->second.c_str()); } if (!p || putenv((char *) iter->second.c_str()) == 0) { /* success */ #else if (putenv((char *) iter->second.c_str()) == 0) { /* success */ #endif #ifdef HAVE_TZSET if (zend_binary_strcasecmp(key.c_str(), key.length(), ZEND_STRL("TZ")) == 0) { tzset(); } #endif result = true; } else { result = false; } tsrm_env_unlock(); RETURN_BOOL(result); } /* }}} */ #endif ================================================ FILE: ext-src/swoole_server.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_server.h" #include "php_swoole_http_server.h" #include "php_swoole_process.h" #include "php_swoole_thread.h" #include "php_swoole_call_stack.h" #include "swoole_msg_queue.h" #include "swoole_coroutine_system.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" BEGIN_EXTERN_C() #include "ext/json/php_json.h" #include "stubs/php_swoole_server_arginfo.h" END_EXTERN_C() using namespace swoole; struct ConnectionIterator { int current_fd; SessionId session_id; Server *serv; ListenPort *port; int index; }; struct ServerEvent { enum php_swoole_server_callback_type type; std::string name; ServerEvent(enum php_swoole_server_callback_type type, std::string &&name) : type(type), name(name) {} }; // clang-format off static std::unordered_map server_event_map({ { "start", ServerEvent(SW_SERVER_CB_onStart, "Start") }, { "beforeshutdown", ServerEvent(SW_SERVER_CB_onBeforeShutdown, "BeforeShutdown") }, { "shutdown", ServerEvent(SW_SERVER_CB_onShutdown, "Shutdown") }, { "workerstart", ServerEvent(SW_SERVER_CB_onWorkerStart, "WorkerStart") }, { "workerstop", ServerEvent(SW_SERVER_CB_onWorkerStop, "WorkerStop") }, { "beforereload", ServerEvent(SW_SERVER_CB_onBeforeReload, "BeforeReload") }, { "afterreload", ServerEvent(SW_SERVER_CB_onAfterReload, "AfterReload") }, { "task", ServerEvent(SW_SERVER_CB_onTask, "Task") }, { "finish", ServerEvent(SW_SERVER_CB_onFinish, "Finish") }, { "workerexit", ServerEvent(SW_SERVER_CB_onWorkerExit, "WorkerExit") }, { "workererror", ServerEvent(SW_SERVER_CB_onWorkerError, "WorkerError") }, { "managerstart", ServerEvent(SW_SERVER_CB_onManagerStart, "ManagerStart") }, { "managerstop", ServerEvent(SW_SERVER_CB_onManagerStop, "ManagerStop") }, { "pipemessage", ServerEvent(SW_SERVER_CB_onPipeMessage, "PipeMessage") }, }); // clang-format on // server event callback static void php_swoole_server_onStart(Server *); static void php_swoole_server_onBeforeShutdown(Server *serv); static void php_swoole_server_onShutdown(Server *); static void php_swoole_server_onWorkerStart(Server *, Worker *worker); static void php_swoole_server_onBeforeReload(Server *serv); static void php_swoole_server_onAfterReload(Server *serv); static void php_swoole_server_onWorkerStop(Server *, Worker *worker); static void php_swoole_server_onWorkerExit(Server *serv, Worker *worker); static void php_swoole_server_onUserWorkerStart(Server *serv, Worker *worker); static int php_swoole_server_onTask(Server *, EventData *req); static int php_swoole_server_onFinish(Server *, EventData *req); static void php_swoole_server_onPipeMessage(Server *serv, EventData *req); static void php_swoole_server_onWorkerError(Server *serv, Worker *worker, const ExitStatus &exit_status); static void php_swoole_server_onManagerStart(Server *serv); static void php_swoole_server_onManagerStop(Server *serv); static bool php_swoole_server_task_finish(Server *serv, zval *zdata, EventData *current_task); static TaskId php_swoole_server_task_pack(zval *data, EventData *task); static bool php_swoole_server_task_unpack(zval *zresult, EventData *task_result); static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendData *data); static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort *port); void php_swoole_server_rshutdown() { if (!sw_server() || !sw_worker()) { return; } Server *serv = sw_server(); Worker *worker = sw_worker(); serv->drain_worker_pipe(); if (serv->is_started() && worker->is_running() && !serv->is_user_worker()) { worker->shutdown(); if (serv->is_event_worker()) { serv->clean_worker_connections(worker); } if (php_swoole_is_fatal_error()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_PHP_FATAL_ERROR, "Fatal error: %s in %s on line %d", php_swoole_get_last_error_message(), php_swoole_get_last_error_file(), PG(last_error_lineno)); } else { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SERVER_WORKER_TERMINATED, "worker process is terminated by exit()/die()"); } } } zend_class_entry *swoole_server_ce; zend_object_handlers swoole_server_handlers; zend_class_entry *swoole_connection_iterator_ce; static zend_object_handlers swoole_connection_iterator_handlers; static zend_class_entry *swoole_server_task_ce; static zend_object_handlers swoole_server_task_handlers; static zend_class_entry *swoole_server_event_ce; static zend_object_handlers swoole_server_event_handlers; static zend_class_entry *swoole_server_packet_ce; static zend_object_handlers swoole_server_packet_handlers; static zend_class_entry *swoole_server_pipe_message_ce; static zend_object_handlers swoole_server_pipe_message_handlers; static zend_class_entry *swoole_server_status_info_ce; static zend_object_handlers swoole_server_status_info_handlers; static zend_class_entry *swoole_server_task_result_ce; static zend_object_handlers swoole_server_task_result_handlers; static SW_THREAD_LOCAL zval swoole_server_instance; #ifdef SW_THREAD static SW_THREAD_LOCAL WorkerFn worker_thread_fn; static SW_THREAD_LOCAL std::vector swoole_server_port_properties; #endif static sw_inline ServerObject *server_fetch_object(zend_object *obj) { return (ServerObject *) ((char *) obj - swoole_server_handlers.offset); } static sw_inline Server *server_get_ptr(zval *zobject) { return server_fetch_object(Z_OBJ_P(zobject))->serv; } Server *php_swoole_server_get_and_check_server(zval *zobject) { Server *serv = server_get_ptr(zobject); if (UNEXPECTED(!serv)) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return serv; } zval *php_swoole_server_zval_ptr(Server *serv) { return &swoole_server_instance; } ServerPortProperty *php_swoole_server_get_port_property(ListenPort *port) { #ifdef SW_THREAD return swoole_server_port_properties.at(port->object_id); #else return (ServerPortProperty *) port->ptr; #endif } void php_swoole_server_set_port_property(ListenPort *port, ServerPortProperty *property) { #ifdef SW_THREAD if (swoole_server_port_properties.size() < (size_t) port->object_id + 1) { swoole_server_port_properties.resize((size_t) port->object_id + 1); } swoole_server_port_properties[port->object_id] = property; #else port->ptr = property; #endif } ServerObject *php_swoole_server_get_zend_object(Server *serv) { return server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); } bool php_swoole_server_isset_callback(Server *serv, ListenPort *port, int event_type) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); return server_object->isset_callback(port, event_type); } static sw_inline void server_set_ptr(zval *zobject, Server *serv) { server_fetch_object(Z_OBJ_P(zobject))->serv = serv; } static void server_free_object(zend_object *object) { ServerObject *server_object = server_fetch_object(object); ServerProperty *property = server_object->property; Server *serv = server_object->serv; if (serv) { if (serv->private_data_3) { sw_callable_free(serv->private_data_3); } for (auto &callback : property->callbacks) { auto fci_cache = callback; if (fci_cache) { sw_callable_free(fci_cache); callback = nullptr; } } for (auto &user_processe : property->user_processes) { sw_zval_free(user_processe); } for (auto zport : property->ports) { php_swoole_server_port_deref(Z_OBJ_P(zport)); efree(zport); } server_object->serv = nullptr; } for (auto fci_cache : property->command_callbacks) { sw_callable_free(fci_cache); } delete property; zend_object_std_dtor(object); if (serv && serv->is_master()) { #ifdef SW_THREAD if (serv->is_thread_mode()) { zend_string_release((zend_string *) serv->private_data_4); } #endif delete serv; } } static zend_object *server_create_object(zend_class_entry *ce) { auto *server_object = static_cast(zend_object_alloc(sizeof(ServerObject), ce)); zend_object_std_init(&server_object->std, ce); object_properties_init(&server_object->std, ce); server_object->std.handlers = &swoole_server_handlers; server_object->property = new ServerProperty(); return &server_object->std; } struct ConnectionIteratorObject { ConnectionIterator iterator; zend_object std; }; static sw_inline ConnectionIteratorObject *php_swoole_connection_iterator_fetch_object(zend_object *obj) { return (ConnectionIteratorObject *) ((char *) obj - swoole_connection_iterator_handlers.offset); } static sw_inline ConnectionIterator *php_swoole_connection_iterator_get_ptr(zval *zobject) { return &php_swoole_connection_iterator_fetch_object(Z_OBJ_P(zobject))->iterator; } ConnectionIterator *php_swoole_connection_iterator_get_and_check_ptr(zval *zobject) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_ptr(zobject); if (UNEXPECTED(!iterator->serv)) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return iterator; } static void php_swoole_connection_iterator_free_object(zend_object *object) { zend_object_std_dtor(object); } static zend_object *php_swoole_connection_iterator_create_object(zend_class_entry *ce) { auto *connection = static_cast(zend_object_alloc(sizeof(ConnectionIteratorObject), ce)); zend_object_std_init(&connection->std, ce); object_properties_init(&connection->std, ce); connection->std.handlers = &swoole_connection_iterator_handlers; return &connection->std; } struct ServerTaskObject { Server *serv; DataHead info; zend_object std; }; static sw_inline ServerTaskObject *php_swoole_server_task_fetch_object(zend_object *obj) { return (ServerTaskObject *) ((char *) obj - swoole_server_task_handlers.offset); } static sw_inline Server *php_swoole_server_task_get_server(zval *zobject) { Server *serv = php_swoole_server_task_fetch_object(Z_OBJ_P(zobject))->serv; if (!serv) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return serv; } static sw_inline void php_swoole_server_task_set_server(zval *zobject, Server *serv) { php_swoole_server_task_fetch_object(Z_OBJ_P(zobject))->serv = serv; } static sw_inline DataHead *php_swoole_server_task_get_info(zval *zobject) { ServerTaskObject *task = php_swoole_server_task_fetch_object(Z_OBJ_P(zobject)); if (!task->serv) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return &task->info; } static sw_inline void php_swoole_server_task_set_info(zval *zobject, DataHead *info) { php_swoole_server_task_fetch_object(Z_OBJ_P(zobject))->info = *info; } static void php_swoole_server_task_free_object(zend_object *object) { zend_object_std_dtor(object); } static zend_object *php_swoole_server_task_create_object(zend_class_entry *ce) { auto *server_task = static_cast(zend_object_alloc(sizeof(ServerTaskObject), ce)); zend_object_std_init(&server_task->std, ce); object_properties_init(&server_task->std, ce); server_task->std.handlers = &swoole_server_task_handlers; return &server_task->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_server, __construct); static PHP_METHOD(swoole_server, __destruct); static PHP_METHOD(swoole_server, set); static PHP_METHOD(swoole_server, on); static PHP_METHOD(swoole_server, getCallback); static PHP_METHOD(swoole_server, listen); static PHP_METHOD(swoole_server, sendMessage); static PHP_METHOD(swoole_server, addProcess); static PHP_METHOD(swoole_server, addCommand); static PHP_METHOD(swoole_server, start); static PHP_METHOD(swoole_server, stop); static PHP_METHOD(swoole_server, send); static PHP_METHOD(swoole_server, sendfile); static PHP_METHOD(swoole_server, stats); static PHP_METHOD(swoole_server, bind); static PHP_METHOD(swoole_server, sendto); static PHP_METHOD(swoole_server, sendwait); static PHP_METHOD(swoole_server, exists); static PHP_METHOD(swoole_server, protect); static PHP_METHOD(swoole_server, close); static PHP_METHOD(swoole_server, pause); static PHP_METHOD(swoole_server, resume); static PHP_METHOD(swoole_server, task); static PHP_METHOD(swoole_server, taskwait); static PHP_METHOD(swoole_server, taskWaitMulti); static PHP_METHOD(swoole_server, taskCo); static PHP_METHOD(swoole_server, finish); static PHP_METHOD(swoole_server, reload); static PHP_METHOD(swoole_server, shutdown); static PHP_METHOD(swoole_server, heartbeat); static PHP_METHOD(swoole_server, command); static PHP_METHOD(swoole_server, getClientList); static PHP_METHOD(swoole_server, getClientInfo); static PHP_METHOD(swoole_server, getWorkerId); static PHP_METHOD(swoole_server, getWorkerPid); static PHP_METHOD(swoole_server, getWorkerStatus); static PHP_METHOD(swoole_server, getManagerPid); static PHP_METHOD(swoole_server, getMasterPid); #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_server, getSocket); #endif /** * Server\Connection */ static PHP_METHOD(swoole_connection_iterator, count); static PHP_METHOD(swoole_connection_iterator, rewind); static PHP_METHOD(swoole_connection_iterator, next); static PHP_METHOD(swoole_connection_iterator, current); static PHP_METHOD(swoole_connection_iterator, key); static PHP_METHOD(swoole_connection_iterator, valid); static PHP_METHOD(swoole_connection_iterator, offsetExists); static PHP_METHOD(swoole_connection_iterator, offsetGet); static PHP_METHOD(swoole_connection_iterator, offsetSet); static PHP_METHOD(swoole_connection_iterator, offsetUnset); static PHP_METHOD(swoole_connection_iterator, __construct); static PHP_METHOD(swoole_connection_iterator, __destruct); /** * Server\Task */ static PHP_METHOD(swoole_server_task, finish); static PHP_METHOD(swoole_server_task, pack); static PHP_METHOD(swoole_server_task, unpack); SW_EXTERN_C_END // clang-format off static zend_function_entry swoole_server_methods[] = { PHP_ME(swoole_server, __construct, arginfo_class_Swoole_Server___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, __destruct, arginfo_class_Swoole_Server___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, listen, arginfo_class_Swoole_Server_listen, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_server, addlistener, listen, arginfo_class_Swoole_Server_listen, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, on, arginfo_class_Swoole_Server_on, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getCallback, arginfo_class_Swoole_Server_getCallback, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, set, arginfo_class_Swoole_Server_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, start, arginfo_class_Swoole_Server_start, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, send, arginfo_class_Swoole_Server_send, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, sendto, arginfo_class_Swoole_Server_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, sendwait, arginfo_class_Swoole_Server_sendwait, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, exists, arginfo_class_Swoole_Server_exists, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_server, exist, exists, arginfo_class_Swoole_Server_exists, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, protect, arginfo_class_Swoole_Server_protect, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, sendfile, arginfo_class_Swoole_Server_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, close, arginfo_class_Swoole_Server_close, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_server, confirm, resume, arginfo_class_Swoole_Server_resume, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, pause, arginfo_class_Swoole_Server_pause, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, resume, arginfo_class_Swoole_Server_resume, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, task, arginfo_class_Swoole_Server_task, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, taskwait, arginfo_class_Swoole_Server_taskwait, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, taskWaitMulti, arginfo_class_Swoole_Server_taskWaitMulti, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, taskCo, arginfo_class_Swoole_Server_taskCo, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, finish, arginfo_class_Swoole_Server_finish, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, reload, arginfo_class_Swoole_Server_reload, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, shutdown, arginfo_class_Swoole_Server_shutdown, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, stop, arginfo_class_Swoole_Server_stop, ZEND_ACC_PUBLIC) PHP_FALIAS(getLastError, swoole_last_error, arginfo_class_Swoole_Server_getLastError) PHP_ME(swoole_server, heartbeat, arginfo_class_Swoole_Server_heartbeat, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getClientInfo, arginfo_class_Swoole_Server_getClientInfo, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getClientList, arginfo_class_Swoole_Server_getClientList, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getWorkerId, arginfo_class_Swoole_Server_getWorkerId, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getWorkerPid, arginfo_class_Swoole_Server_getWorkerPid, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getWorkerStatus, arginfo_class_Swoole_Server_getWorkerStatus, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getManagerPid, arginfo_class_Swoole_Server_getManagerPid, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, getMasterPid, arginfo_class_Swoole_Server_getMasterPid, ZEND_ACC_PUBLIC) //psr-0 style PHP_MALIAS(swoole_server, connection_info, getClientInfo, arginfo_class_Swoole_Server_getClientInfo, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_server, connection_list, getClientList, arginfo_class_Swoole_Server_getClientList, ZEND_ACC_PUBLIC) //process PHP_ME(swoole_server, sendMessage, arginfo_class_Swoole_Server_sendMessage, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, command, arginfo_class_Swoole_Server_command, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, addCommand, arginfo_class_Swoole_Server_addCommand, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, addProcess, arginfo_class_Swoole_Server_addProcess, ZEND_ACC_PUBLIC) PHP_ME(swoole_server, stats, arginfo_class_Swoole_Server_stats, ZEND_ACC_PUBLIC) #ifdef SWOOLE_SOCKETS_SUPPORT PHP_ME(swoole_server, getSocket, arginfo_class_Swoole_Server_getSocket, ZEND_ACC_PUBLIC) #endif PHP_ME(swoole_server, bind, arginfo_class_Swoole_Server_bind, ZEND_ACC_PUBLIC) PHP_FE_END }; static const zend_function_entry swoole_connection_iterator_methods[] = { PHP_ME(swoole_connection_iterator, __construct, arginfo_class_Swoole_Connection_Iterator___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, __destruct, arginfo_class_Swoole_Connection_Iterator___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, rewind, arginfo_class_Swoole_Connection_Iterator_rewind, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, next, arginfo_class_Swoole_Connection_Iterator_next, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, current, arginfo_class_Swoole_Connection_Iterator_current, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, key, arginfo_class_Swoole_Connection_Iterator_key, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, valid, arginfo_class_Swoole_Connection_Iterator_valid, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, count, arginfo_class_Swoole_Connection_Iterator_count, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, offsetExists, arginfo_class_Swoole_Connection_Iterator_offsetExists, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, offsetGet, arginfo_class_Swoole_Connection_Iterator_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, offsetSet, arginfo_class_Swoole_Connection_Iterator_offsetSet, ZEND_ACC_PUBLIC) PHP_ME(swoole_connection_iterator, offsetUnset, arginfo_class_Swoole_Connection_Iterator_offsetUnset, ZEND_ACC_PUBLIC) PHP_FE_END }; static constexpr zend_function_entry swoole_server_task_methods[] = { PHP_ME(swoole_server_task, finish, arginfo_class_Swoole_Server_Task_finish, ZEND_ACC_PUBLIC) PHP_ME(swoole_server_task, pack, arginfo_class_Swoole_Server_Task_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_server_task, unpack, arginfo_class_Swoole_Server_Task_unpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on #define SWOOLE_DISPATCH_STREAM 7 // Deprecated, kept for forward compatibility void php_swoole_server_minit(int module_number) { // ---------------------------------------Server------------------------------------- SW_INIT_CLASS_ENTRY(swoole_server, "Swoole\\Server", nullptr, swoole_server_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_server); SW_SET_CLASS_CLONEABLE(swoole_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_server, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_server, server_create_object, server_free_object, ServerObject, std); // ---------------------------------------Task------------------------------------- SW_INIT_CLASS_ENTRY(swoole_server_task, "Swoole\\Server\\Task", nullptr, swoole_server_task_methods); swoole_server_task_ce->ce_flags |= ZEND_ACC_FINAL; SW_SET_CLASS_NOT_SERIALIZABLE(swoole_server_task); SW_SET_CLASS_CLONEABLE(swoole_server_task, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_server_task, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_server_task, php_swoole_server_task_create_object, php_swoole_server_task_free_object, ServerTaskObject, std); zend_declare_property_null(swoole_server_task_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_task_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_task_ce, ZEND_STRL("id"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_task_ce, ZEND_STRL("worker_id"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_task_ce, ZEND_STRL("flags"), 0, ZEND_ACC_PUBLIC); // ---------------------------------------Event------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_event, "Swoole\\Server\\Event"); zend_declare_property_long(swoole_server_event_ce, ZEND_STRL("reactor_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_event_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_event_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_event_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); // ---------------------------------------Packet------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_packet, "Swoole\\Server\\Packet"); zend_declare_property_long(swoole_server_packet_ce, ZEND_STRL("server_socket"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_packet_ce, ZEND_STRL("server_port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_packet_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_packet_ce, ZEND_STRL("address"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_packet_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); // ---------------------------------------PipeMessage------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_pipe_message, "Swoole\\Server\\PipeMessage"); zend_declare_property_long(swoole_server_pipe_message_ce, ZEND_STRL("source_worker_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_pipe_message_ce, ZEND_STRL("worker_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_pipe_message_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_pipe_message_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); // ---------------------------------------StatusInfo------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_status_info, "Swoole\\Server\\StatusInfo"); zend_declare_property_long(swoole_server_status_info_ce, ZEND_STRL("worker_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_status_info_ce, ZEND_STRL("worker_pid"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_status_info_ce, ZEND_STRL("status"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_status_info_ce, ZEND_STRL("exit_code"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_status_info_ce, ZEND_STRL("signal"), 0, ZEND_ACC_PUBLIC); // ---------------------------------------TaskResult------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_task_result, "Swoole\\Server\\TaskResult"); zend_declare_property_long(swoole_server_task_result_ce, ZEND_STRL("task_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_task_result_ce, ZEND_STRL("task_worker_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_task_result_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_task_result_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); // ---------------------------------------Connection Iterator------------------------------------- SW_INIT_CLASS_ENTRY( swoole_connection_iterator, "Swoole\\Connection\\Iterator", nullptr, swoole_connection_iterator_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_connection_iterator); SW_SET_CLASS_CLONEABLE(swoole_connection_iterator, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_connection_iterator, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_connection_iterator, php_swoole_connection_iterator_create_object, php_swoole_connection_iterator_free_object, ConnectionIteratorObject, std); zend_class_implements(swoole_connection_iterator_ce, 2, zend_ce_iterator, zend_ce_arrayaccess); zend_class_implements(swoole_connection_iterator_ce, 1, zend_ce_countable); // ---------------------------------------Server Property------------------------------------- zend_declare_property_null(swoole_server_ce, ZEND_STRL("onStart"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onBeforeShutdown"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onShutdown"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onWorkerStart"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onWorkerStop"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onBeforeReload"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onAfterReload"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onWorkerExit"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onWorkerError"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onTask"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onFinish"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onManagerStart"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onManagerStop"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onPipeMessage"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("connections"), ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_server_ce, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_server_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("mode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("ports"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("master_pid"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("manager_pid"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("worker_id"), -1, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_server_ce, ZEND_STRL("taskworker"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_ce, ZEND_STRL("worker_pid"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("stats_timer"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("admin_server"), ZEND_ACC_PUBLIC); #ifdef SW_THREAD zend_declare_property_string(swoole_server_ce, ZEND_STRL("bootstrap"), "", ZEND_ACC_PUBLIC); #endif /** * mode type */ SW_REGISTER_LONG_CONSTANT("SWOOLE_BASE", swoole::Server::MODE_BASE); SW_REGISTER_LONG_CONSTANT("SWOOLE_PROCESS", swoole::Server::MODE_PROCESS); #ifdef SW_THREAD SW_REGISTER_LONG_CONSTANT("SWOOLE_THREAD", swoole::Server::MODE_THREAD); #endif /** * task ipc mode */ SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_UNSOCK", Server::TASK_IPC_UNIXSOCK); SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_MSGQUEUE", Server::TASK_IPC_MSGQUEUE); SW_REGISTER_LONG_CONSTANT("SWOOLE_IPC_PREEMPTIVE", Server::TASK_IPC_PREEMPTIVE); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_MASTER", Server::Command::MASTER); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_MANAGER", Server::Command::MANAGER); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_REACTOR_THREAD", Server::Command::REACTOR_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_EVENT_WORKER", Server::Command::EVENT_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_WORKER", Server::Command::EVENT_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_SERVER_COMMAND_TASK_WORKER", Server::Command::TASK_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_ROUND", Server::DISPATCH_ROUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_FDMOD", Server::DISPATCH_FDMOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_IDLE_WORKER", Server::DISPATCH_IDLE_WORKER); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_IPMOD", Server::DISPATCH_IPMOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_UIDMOD", Server::DISPATCH_UIDMOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_USERFUNC", Server::DISPATCH_USERFUNC); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_STREAM", SWOOLE_DISPATCH_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CO_CONN_LB", Server::DISPATCH_CO_CONN_LB); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CO_REQ_LB", Server::DISPATCH_CO_REQ_LB); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CONCURRENT_LB", Server::DISPATCH_CONCURRENT_LB); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_RESULT_DISCARD_PACKET", Server::DISPATCH_RESULT_DISCARD_PACKET); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_RESULT_CLOSE_CONNECTION", Server::DISPATCH_RESULT_CLOSE_CONNECTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_RESULT_USERFUNC_FALLBACK", Server::DISPATCH_RESULT_USERFUNC_FALLBACK); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_TMPFILE", SW_TASK_TMPFILE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_SERIALIZE", SW_TASK_SERIALIZE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_NONBLOCK", SW_TASK_NONBLOCK); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_CALLBACK", SW_TASK_CALLBACK); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_WAITALL", SW_TASK_WAITALL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_COROUTINE", SW_TASK_COROUTINE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_PEEK", SW_TASK_PEEK); SW_REGISTER_LONG_CONSTANT("SWOOLE_TASK_NOREPLY", SW_TASK_NOREPLY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WORKER_BUSY", SW_WORKER_BUSY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WORKER_IDLE", SW_WORKER_IDLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_WORKER_EXIT", SW_WORKER_EXIT); } zend::Callable *php_swoole_server_get_callback(Server *serv, int server_fd, int event_type) { ListenPort *port = serv->get_port_by_server_fd(server_fd); ServerPortProperty *property = php_swoole_server_get_port_property(port); zend::Callable *cb; if (sw_unlikely(!port)) { return nullptr; } if (property && ((cb = property->callbacks[event_type]))) { return cb; } else { return php_swoole_server_get_port_property(serv->get_primary_port())->callbacks[event_type]; } } int php_swoole_create_dir(const char *path, size_t length) { if (access(path, F_OK) == 0) { return 0; } return php_stream_mkdir(path, 0777, PHP_STREAM_MKDIR_RECURSIVE | REPORT_ERRORS, nullptr) ? 0 : -1; } static TaskId php_swoole_server_task_pack(zval *zdata, EventData *task) { smart_str serialized_data = {}; php_serialize_data_t var_hash; bool serialized = false; char *task_data_str; size_t task_data_len = 0; // need serialize if (Z_TYPE_P(zdata) != IS_STRING) { // serialize serialized = true; PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&serialized_data, zdata, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); if (!serialized_data.s) { return -1; } task_data_str = ZSTR_VAL(serialized_data.s); task_data_len = ZSTR_LEN(serialized_data.s); } else { task_data_str = Z_STRVAL_P(zdata); task_data_len = Z_STRLEN_P(zdata); } if (!Server::task_pack(task, task_data_str, task_data_len)) { php_swoole_fatal_error(E_WARNING, "large task pack failed"); task->info.fd = SW_ERR; task->info.len = 0; } if (serialized) { task->info.ext_flags |= SW_TASK_SERIALIZE; } smart_str_free(&serialized_data); return task->info.fd; } void php_swoole_get_recv_data(Server *serv, zval *zdata, RecvData *req) { const char *data = req->data; uint32_t length = req->info.len; if (length == 0) { ZVAL_EMPTY_STRING(zdata); } else { if (req->info.flags & SW_EVENT_DATA_OBJ_PTR) { zend::assign_zend_string_by_val(zdata, (char *) data, length); serv->get_worker_message_bus()->move_packet(); } else if (req->info.flags & SW_EVENT_DATA_POP_PTR) { String *recv_buffer = serv->get_recv_buffer(serv->get_connection_by_session_id(req->info.fd)->socket); zend::assign_zend_string_by_val(zdata, recv_buffer->pop(serv->recv_buffer_size), length); } else { ZVAL_STRINGL(zdata, data, length); } } } static inline int php_swoole_server_task_check_param(Server *serv, zend_long dst_worker_id) { if (UNEXPECTED(serv->task_worker_num == 0)) { php_swoole_fatal_error(E_WARNING, "task method can't be executed without task worker"); return SW_ERR; } if (UNEXPECTED(dst_worker_id > 0 && dst_worker_id >= serv->task_worker_num)) { php_swoole_fatal_error(E_WARNING, "worker_id must be less than task_worker_num[%u]", serv->task_worker_num); return SW_ERR; } if (UNEXPECTED(serv->is_task_worker())) { php_swoole_fatal_error(E_WARNING, "Server->task() cannot use in the task-worker"); return SW_ERR; } return SW_OK; } static bool php_swoole_server_task_unpack(zval *zresult, EventData *task_result) { php_unserialize_data_t var_hash; PacketPtr packet; if (!Server::task_unpack(task_result, sw_tg_buffer(), &packet)) { return false; } if (task_result->info.ext_flags & SW_TASK_SERIALIZE) { char *p = packet.data; size_t l = packet.length; PHP_VAR_UNSERIALIZE_INIT(var_hash); zend_bool unserialized = php_var_unserialize(zresult, (const uchar **) &p, (const uchar *) (p + l), &var_hash); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (!unserialized) { swoole_warning("unserialize() failed, Error at offset " ZEND_LONG_FMT " of %zd bytes", (zend_long) ((char *) p - packet.data), l); return false; } } else { ZVAL_STRINGL(zresult, packet.data, packet.length); } return true; } extern ListenPort *php_swoole_server_port_get_and_check_ptr(zval *zobject); extern void php_swoole_server_port_set_ptr(zval *zobject, ListenPort *port); extern ServerPortProperty *php_swoole_server_port_get_property(zval *zobject); static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort *port) { Server *serv = server_object->serv; zval *zport = sw_malloc_zval(); object_init_ex(zport, swoole_server_port_ce); server_object->property->ports.push_back(zport); /* port ptr */ php_swoole_server_port_set_ptr(zport, port); /* port property */ ServerPortProperty *property = php_swoole_server_port_get_property(zport); property->serv = serv; property->port = port; /* linked */ php_swoole_server_set_port_property(port, property); zend_update_property_string(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("host"), port->get_host()); zend_update_property_long(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("port"), port->get_port()); zend_update_property_long(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("type"), port->get_type()); zend_update_property_long(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("sock"), port->get_fd()); zend_update_property_bool(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("ssl"), port->ssl); do { zval *zserv = php_swoole_server_zval_ptr(serv); zval *zports = sw_zend_read_and_convert_property_array(Z_OBJCE_P(zserv), zserv, ZEND_STRL("ports"), 0); (void) add_next_index_zval(zports, zport); } while (false); /* iterator */ do { zval connection_iterator; object_init_ex(&connection_iterator, swoole_connection_iterator_ce); ConnectionIterator *iterator = php_swoole_connection_iterator_get_ptr(&connection_iterator); iterator->serv = serv; iterator->port = port; zend_update_property(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("connections"), &connection_iterator); zval_ptr_dtor(&connection_iterator); } while (false); return zport; } void ServerObject::copy_setting(zval *zsetting) const { zend_array *new_array = zend_array_dup(Z_ARRVAL_P(zsetting)); zend_hash_apply(new_array, [](zval *el) -> int { return sw_zval_is_serializable(el) ? ZEND_HASH_APPLY_KEEP : ZEND_HASH_APPLY_REMOVE; }); zval znew_array; ZVAL_ARR(&znew_array, new_array); serv->private_data_4 = php_swoole_serialize(&znew_array); zval_ptr_dtor(&znew_array); } void ServerObject::on_before_start() { /** * create swoole server */ if (serv->create() < 0) { php_swoole_fatal_error(E_ERROR, "failed to create the server. Error: %s", sw_error); return; } zval *zobject = php_swoole_server_zval_ptr(serv); auto primary_port = serv->get_primary_port(); #ifdef SW_LOG_TRACE_OPEN swoole_trace_log(SW_TRACE_SERVER, "Create Server: host=%s, port=%d, mode=%d, type=%d", primary_port->host.c_str(), (int) primary_port->port, serv->is_base_mode() ? Server::MODE_BASE : Server::MODE_PROCESS, (int) primary_port->type); #endif if (serv->enable_coroutine) { serv->reload_async = true; } if (serv->send_yield) { if (serv->onClose == nullptr && serv->is_support_unsafe_events()) { serv->onClose = php_swoole_server_onClose; } } serv->message_bus.set_allocator(sw_zend_string_allocator()); if (serv->is_base_mode() || serv->is_thread_mode()) { serv->recv_buffer_allocator = sw_zend_string_allocator(); } /** * Master Process ID */ zend_update_property_long(get_ce(), SW_Z8_OBJ_P(zobject), ZEND_STRL("master_pid"), getpid()); zval *zsetting = sw_zend_read_and_convert_property_array(get_ce(), zobject, ZEND_STRL("setting"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("worker_num"))) { add_assoc_long(zsetting, "worker_num", serv->worker_num); } if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("task_worker_num"))) { add_assoc_long(zsetting, "task_worker_num", serv->task_worker_num); } if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("output_buffer_size"))) { add_assoc_long(zsetting, "output_buffer_size", serv->output_buffer_size); } if (!zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("max_connection"))) { add_assoc_long(zsetting, "max_connection", serv->get_max_connection()); } // for admin_server if (zend_hash_str_exists(Z_ARRVAL_P(zsetting), ZEND_STRL("admin_server"))) { swoole::register_admin_server_commands(serv); } bool find_http_port = false; if (is_redis_server()) { add_assoc_bool(zsetting, "open_redis_protocol", 1); add_assoc_bool(zsetting, "open_http_protocol", 0); add_assoc_bool(zsetting, "open_mqtt_protocol", 0); add_assoc_bool(zsetting, "open_eof_check", 0); add_assoc_bool(zsetting, "open_length_check", 0); primary_port->clear_protocol(); primary_port->open_redis_protocol = true; serv->onReceive = php_swoole_redis_server_onReceive; } else if (is_http_server()) { if (is_websocket_server()) { if (!isset_callback(primary_port, SW_SERVER_CB_onMessage)) { php_swoole_fatal_error(E_ERROR, "require onMessage callback"); return; } } else if (!isset_callback(primary_port, SW_SERVER_CB_onRequest)) { php_swoole_fatal_error(E_ERROR, "require onRequest callback"); return; } add_assoc_bool(zsetting, "open_http_protocol", 1); add_assoc_bool(zsetting, "open_mqtt_protocol", 0); add_assoc_bool(zsetting, "open_eof_check", 0); add_assoc_bool(zsetting, "open_length_check", 0); enum protocol_flags { SW_HTTP2_PROTOCOL = 1u << 1, SW_WEBSOCKET_PROTOCOL = 1u << 2 }; uint8_t protocol_flag = 0; if (primary_port->open_http2_protocol) { add_assoc_bool(zsetting, "open_http2_protocol", 1); protocol_flag |= SW_HTTP2_PROTOCOL; } if (primary_port->open_websocket_protocol || is_websocket_server()) { add_assoc_bool(zsetting, "open_websocket_protocol", 1); protocol_flag |= SW_WEBSOCKET_PROTOCOL; } primary_port->clear_protocol(); primary_port->open_http_protocol = true; primary_port->open_http2_protocol = !!(protocol_flag & SW_HTTP2_PROTOCOL); primary_port->open_websocket_protocol = !!(protocol_flag & SW_WEBSOCKET_PROTOCOL); find_http_port = true; serv->onReceive = php_swoole_http_server_onReceive; } else { if (serv->if_require_packet_callback(primary_port, isset_callback(primary_port, SW_SERVER_CB_onPacket))) { php_swoole_fatal_error(E_ERROR, "require onPacket callback"); return; } if (serv->if_require_receive_callback(primary_port, isset_callback(primary_port, SW_SERVER_CB_onReceive))) { php_swoole_fatal_error(E_ERROR, "require onReceive callback"); return; } serv->onReceive = php_swoole_server_onReceive; } for (size_t i = 1; i < property->ports.size(); i++) { zval *zport = property->ports.at(i); zval *zport_setting = sw_zend_read_property_ex(swoole_server_port_ce, zport, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); // use swoole_server->setting if (zport_setting == nullptr || ZVAL_IS_NULL(zport_setting)) { Z_TRY_ADDREF_P(zport); sw_zend_call_method_with_1_params(zport, swoole_server_port_ce, nullptr, "set", nullptr, zsetting); } } for (auto zport : property->ports) { ListenPort *port = php_swoole_server_port_get_and_check_ptr(zport); if (serv->if_require_packet_callback(port, isset_callback(port, SW_SERVER_CB_onPacket))) { php_swoole_fatal_error(E_ERROR, "require onPacket callback"); return; } if (port->ssl_is_enable() && port->get_ssl_verify_peer() && port->get_ssl_client_cert_file().empty() && port->get_ssl_cafile().empty() && port->get_ssl_capath().empty()) { php_swoole_fatal_error( E_ERROR, "server open verify peer require `ssl_client_cert_file` or `ssl_capath` or `ssl_cafile` config"); return; } if (port->open_http2_protocol && !serv->is_hash_dispatch_mode()) { php_swoole_fatal_error( E_ERROR, "server dispatch mode should be FDMOD(%d) or IPMOD(%d) if open_http2_protocol is true", Server::DISPATCH_FDMOD, Server::DISPATCH_IPMOD); return; } if (!port->open_http_protocol) { port->open_http_protocol = port->open_websocket_protocol || port->open_http2_protocol; } if (port->open_http_protocol) { find_http_port = true; if (port->open_websocket_protocol) { if (!isset_callback(port, SW_SERVER_CB_onMessage) && !isset_callback(port, SW_SERVER_CB_onReceive)) { php_swoole_fatal_error(E_ERROR, "require onMessage callback"); return; } } else if (port->open_http_protocol && !isset_callback(port, SW_SERVER_CB_onRequest) && !isset_callback(port, SW_SERVER_CB_onReceive)) { php_swoole_fatal_error(E_ERROR, "require onRequest callback"); return; } if (!is_http_server() && isset_callback(port, SW_SERVER_CB_onRequest)) { php_swoole_error( E_WARNING, "use %s class and open http related protocols may lead to some errors (inconsistent class type)", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } else if (!port->open_redis_protocol) { // redis server does not require to set receive callback if (port->is_stream() && !isset_callback(port, SW_SERVER_CB_onReceive)) { php_swoole_fatal_error(E_ERROR, "require onReceive callback"); return; } } } if (find_http_port) { serv->onReceive = php_swoole_http_server_onReceive; if (serv->is_base_mode()) { serv->onClose = php_swoole_http_server_onClose; } } #ifdef SW_THREAD if (serv->is_thread_mode()) { copy_setting(zsetting); } #endif if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onBeforeStart)", 1, zobject); } } void ServerObject::register_callback() const { // control plane serv->onStart = php_swoole_server_onStart; serv->onBeforeShutdown = php_swoole_server_onBeforeShutdown; serv->onShutdown = php_swoole_server_onShutdown; serv->onWorkerStart = php_swoole_server_onWorkerStart; serv->onWorkerStop = php_swoole_server_onWorkerStop; serv->onWorkerExit = php_swoole_server_onWorkerExit; serv->onBeforeReload = php_swoole_server_onBeforeReload; serv->onAfterReload = php_swoole_server_onAfterReload; serv->onManagerStart = php_swoole_server_onManagerStart; serv->onManagerStop = php_swoole_server_onManagerStop; serv->onWorkerError = php_swoole_server_onWorkerError; // data plane if (property->callbacks[SW_SERVER_CB_onTask] != nullptr) { serv->onTask = php_swoole_server_onTask; serv->onFinish = php_swoole_server_onFinish; } if (property->callbacks[SW_SERVER_CB_onPipeMessage] != nullptr) { serv->onPipeMessage = php_swoole_server_onPipeMessage; } if (serv->send_yield && serv->is_support_unsafe_events()) { serv->onBufferEmpty = php_swoole_server_onBufferEmpty; } } static bool php_swoole_server_task_finish(Server *serv, zval *zdata, EventData *current_task) { int flags = 0; smart_str serialized_data = {}; php_serialize_data_t var_hash; char *data_str; size_t data_len = 0; // need serialize if (Z_TYPE_P(zdata) != IS_STRING) { // serialize flags |= SW_TASK_SERIALIZE; PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&serialized_data, zdata, &var_hash); PHP_VAR_SERIALIZE_DESTROY(var_hash); data_str = ZSTR_VAL(serialized_data.s); data_len = ZSTR_LEN(serialized_data.s); } else { data_str = Z_STRVAL_P(zdata); data_len = Z_STRLEN_P(zdata); } bool success = serv->finish(data_str, data_len, flags, current_task); smart_str_free(&serialized_data); return success; } static void php_swoole_server_onPipeMessage(Server *serv, EventData *req) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); auto cb = server_object->get_callback(SW_SERVER_CB_onPipeMessage); zval *zserv = php_swoole_server_zval_ptr(serv); zend::Variable zresult; if (UNEXPECTED(!php_swoole_server_task_unpack(zresult.ptr(), req))) { return; } swoole_trace_log(SW_TRACE_SERVER, "PipeMessage: fd=%ld|len=%d|src_worker_id=%d|data=%.*s\n", req->info.fd, req->info.len, req->info.reactor_id, req->info.len, req->data); zval args[3]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_pipe_message_ce); zend_update_property_long(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_id"), (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_long(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("source_worker_id"), (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_double( swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time); zend_update_property(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zresult.ptr()); argc = 2; } else { ZVAL_LONG(&args[1], (zend_long) serv->get_task_src_worker_id(req)); args[2] = zresult.value; argc = 3; } if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onPipeMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } } int php_swoole_server_onReceive(Server *serv, RecvData *req) { auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onReceive); if (cb) { zval *zserv = php_swoole_server_zval_ptr(serv); zval args[4]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; zval data; object_init_ex(object, swoole_server_event_ce); zend_update_property_long( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) req->info.fd); zend_update_property_long( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) req->info.reactor_id); zend_update_property_double( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time); php_swoole_get_recv_data(serv, &data, req); zend_update_property(swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), &data); zval_ptr_dtor(&data); argc = 2; } else { ZVAL_LONG(&args[1], (zend_long) req->info.fd); ZVAL_LONG(&args[2], (zend_long) req->info.reactor_id); php_swoole_get_recv_data(serv, &args[3], req); argc = 4; } if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onReceive handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); serv->close(req->info.fd, false); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } else { zval_ptr_dtor(&args[3]); } } return SW_OK; } int php_swoole_server_onPacket(Server *serv, RecvData *req) { zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; auto *packet = (DgramPacket *) req->data; if (serv->event_object) { zval zobject; object_init_ex(&zobject, swoole_server_packet_ce); zend_update_property_long( swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("server_socket"), req->info.server_fd); zend_update_property_double( swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("dispatch_time"), req->info.time); Connection *server_sock = serv->get_connection(req->info.server_fd); if (server_sock) { zend_update_property_long( swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("server_port"), server_sock->info.get_port()); } char address[INET6_ADDRSTRLEN]; if (packet->socket_type == SW_SOCK_UDP) { inet_ntop(AF_INET, &packet->socket_addr.addr.inet_v4.sin_addr, address, sizeof(address)); zend_update_property_string(swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("address"), address); zend_update_property_long(swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("port"), ntohs(packet->socket_addr.addr.inet_v4.sin_port)); } else if (packet->socket_type == SW_SOCK_UDP6) { inet_ntop(AF_INET6, &packet->socket_addr.addr.inet_v6.sin6_addr, address, sizeof(address)); zend_update_property_string(swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("address"), address); zend_update_property_long(swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("port"), ntohs(packet->socket_addr.addr.inet_v6.sin6_port)); } else if (packet->socket_type == SW_SOCK_UNIX_DGRAM) { zend_update_property_string(swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("address"), packet->socket_addr.addr.un.sun_path); } zend_update_property_stringl( swoole_server_packet_ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("data"), packet->data, packet->length); args[1] = zobject; argc = 2; } else { zval zaddr; array_init(&zaddr); add_assoc_long(&zaddr, "server_socket", req->info.server_fd); add_assoc_double(&zaddr, "dispatch_time", req->info.time); Connection *server_sock = serv->get_connection(req->info.server_fd); if (server_sock) { add_assoc_long(&zaddr, "server_port", server_sock->info.get_port()); } char address[INET6_ADDRSTRLEN]; if (packet->socket_type == SW_SOCK_UDP) { inet_ntop(AF_INET, &packet->socket_addr.addr.inet_v4.sin_addr, address, sizeof(address)); add_assoc_string(&zaddr, "address", address); add_assoc_long(&zaddr, "port", ntohs(packet->socket_addr.addr.inet_v4.sin_port)); } else if (packet->socket_type == SW_SOCK_UDP6) { inet_ntop(AF_INET6, &packet->socket_addr.addr.inet_v6.sin6_addr, address, sizeof(address)); add_assoc_string(&zaddr, "address", address); add_assoc_long(&zaddr, "port", ntohs(packet->socket_addr.addr.inet_v6.sin6_port)); } else if (packet->socket_type == SW_SOCK_UNIX_DGRAM) { add_assoc_string(&zaddr, "address", packet->socket_addr.addr.un.sun_path); } ZVAL_STRINGL(&args[1], packet->data, packet->length); args[2] = zaddr; argc = 3; } auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onPacket); if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onPipeMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } zval_ptr_dtor(&args[1]); if (!serv->event_object) { zval_ptr_dtor(&args[2]); } return SW_OK; } static sw_inline void php_swoole_create_task_object(zval *ztask, Server *serv, EventData *req, zval *zdata) { object_init_ex(ztask, swoole_server_task_ce); php_swoole_server_task_set_server(ztask, serv); php_swoole_server_task_set_info(ztask, &req->info); zend_update_property_long(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("worker_id"), (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_long( swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("id"), (zend_long) serv->get_task_id(req)); zend_update_property(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("data"), zdata); zend_update_property_double(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("dispatch_time"), req->info.time); zend_update_property_long( swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("flags"), (zend_long) req->info.ext_flags); } static int php_swoole_server_onTask(Server *serv, EventData *req) { sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); zend::Variable zresult; if (!php_swoole_server_task_unpack(zresult.ptr(), req)) { return SW_ERR; } zval retval; uint32_t argc; zval argv[4]; if (serv->task_enable_coroutine || serv->task_object) { argc = 2; argv[0] = *zserv; php_swoole_create_task_object(&argv[1], serv, req, zresult.ptr()); } else { argc = 4; argv[0] = *zserv; ZVAL_LONG(&argv[1], (zend_long) serv->get_task_id(req)); ZVAL_LONG(&argv[2], (zend_long) serv->get_task_src_worker_id(req)); argv[3] = zresult.value; } if (UNEXPECTED(!zend::function::call(server_object->get_callback(SW_SERVER_CB_onTask)->ptr(), argc, argv, &retval, serv->task_enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onTask handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (argc == 2) { zval_ptr_dtor(&argv[1]); } if (!ZVAL_IS_NULL(&retval)) { php_swoole_server_task_finish(serv, &retval, req); zval_ptr_dtor(&retval); } return SW_OK; } static int php_swoole_server_onFinish(Server *serv, EventData *req) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); TaskId task_id = serv->get_task_id(req); zend::Variable zresult; if (!php_swoole_server_task_unpack(zresult.ptr(), req)) { return SW_ERR; } if (req->info.ext_flags & SW_TASK_COROUTINE) { auto task_co_iterator = server_object->property->task_coroutine_map.find(task_id); if (task_co_iterator == server_object->property->task_coroutine_map.end()) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_TASK_TIMEOUT, "task[%ld] has expired", task_id); return SW_OK; } TaskCo *task_co = task_co_iterator->second; // Server->taskwait if (task_co->list == nullptr) { ZVAL_COPY_VALUE(task_co->result, zresult.ptr()); zresult.reset(); task_co->co->resume(); return SW_OK; } // Server->taskCo int task_index = -1; SW_LOOP_N(task_co->count) { if (task_co->list[i] == task_id) { task_index = i; break; } } if (task_index < 0) { php_swoole_fatal_error(E_WARNING, "task[%ld] is invalid", task_id); return SW_OK; } (void) add_index_zval(task_co->result, task_index, zresult.ptr()); zresult.reset(); server_object->property->task_coroutine_map.erase(task_id); if (php_swoole_array_length(task_co->result) == task_co->count) { task_co->co->resume(); } return SW_OK; } zend::Callable *cb = nullptr; if (req->info.ext_flags & SW_TASK_CALLBACK) { auto callback_iterator = server_object->property->task_callbacks.find(task_id); if (callback_iterator == server_object->property->task_callbacks.end()) { req->info.ext_flags = req->info.ext_flags & (~SW_TASK_CALLBACK); } else { cb = callback_iterator->second; } } else { cb = server_object->get_callback(SW_SERVER_CB_onFinish); } if (UNEXPECTED(cb == nullptr)) { php_swoole_fatal_error(E_WARNING, "require 'onFinish' callback"); return SW_ERR; } zval args[3]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_task_result_ce); zend_update_property_long( swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("task_id"), (zend_long) task_id); zend_update_property_long(swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("task_worker_id"), (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_double( swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time); zend_update_property(swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zresult.ptr()); argc = 2; } else { ZVAL_LONG(&args[1], (zend_long) task_id); args[2] = zresult.value; argc = 3; } if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onFinish handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (req->info.ext_flags & SW_TASK_CALLBACK) { sw_callable_free(server_object->property->task_callbacks[task_id]); server_object->property->task_callbacks.erase(task_id); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } return SW_OK; } static void php_swoole_server_onStart(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onStart)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onManagerStart(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onManagerStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onManagerStart)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onManagerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onManagerStop(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onManagerStop); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onManagerStop)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onManagerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onBeforeShutdown(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onBeforeShutdown); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onBeforeShutdown)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onBeforeShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onShutdown(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onShutdown); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onShutdown)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onWorkerStart(Server *serv, Worker *worker) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onWorkerStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("worker_id"), worker->id); zend_update_property_bool(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("taskworker"), serv->is_task_worker()); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("worker_pid"), getpid()); if (serv->is_task_worker() && !serv->task_enable_coroutine && !serv->is_thread_mode()) { PHPCoroutine::disable_hook(); } serv->get_worker_message_bus()->set_allocator(sw_zend_string_allocator()); zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onWorkerStart)", 2, args); } if (cb && UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onWorkerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onBeforeReload(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onBeforeReload); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onBeforeReload)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBeforeReload handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onAfterReload(Server *serv) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onAfterReload); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onAfterReload)", 1, zserv); } if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onAfterReload handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onWorkerStop(Server *serv, Worker *worker) { if (!worker->is_running()) { return; } zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto cb = server_object->get_callback(SW_SERVER_CB_onWorkerStop); zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onWorkerStop)", 2, args); } if (cb && UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onWorkerExit(Server *serv, Worker *worker) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto fci_cache = server_object->get_callback(SW_SERVER_CB_onWorkerExit); zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onWorkerExit)", 2, args); } if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerExit handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onUserWorkerStart(Server *serv, Worker *worker) { zval *object; zval *zserv = php_swoole_server_zval_ptr(serv); if (serv->is_thread_mode()) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); int index = worker->id - serv->worker_num - serv->task_worker_num; object = server_object->property->user_processes[index]; serv->get_worker_message_bus()->set_allocator(sw_zend_string_allocator()); } else { object = (zval *) worker->ptr; } zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(object), ZEND_STRL("id"), worker->id); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); php_swoole_process_start(worker, object); } static void php_swoole_server_onWorkerError(Server *serv, Worker *worker, const ExitStatus &exit_status) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); auto fci_cache = server_object->get_callback(SW_SERVER_CB_onWorkerError); zval args[5]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_status_info_ce); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_id"), worker->id); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_pid"), exit_status.get_pid()); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("status"), exit_status.get_status()); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("exit_code"), exit_status.get_code()); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("signal"), exit_status.get_signal()); argc = 2; } else { ZVAL_LONG(&args[1], worker->id); ZVAL_LONG(&args[2], exit_status.get_pid()); ZVAL_LONG(&args[3], exit_status.get_code()); ZVAL_LONG(&args[4], exit_status.get_signal()); argc = 5; } if (SWOOLE_G(enable_library)) { zend::function::call(R"(\Swoole\Server\Helper::onWorkerError)", 1, zserv); } if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerError handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } } void php_swoole_server_onConnect(Server *serv, DataHead *info) { auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onConnect); if (!cb) { return; } zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_event_ce); zend_update_property_long(swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) info->fd); zend_update_property_long( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) info->reactor_id); zend_update_property_double( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), info->time); argc = 2; } else { ZVAL_LONG(&args[1], info->fd); ZVAL_LONG(&args[2], info->reactor_id); argc = 3; } if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onConnect handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } } void php_swoole_server_onClose(Server *serv, DataHead *info) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); SessionId session_id = info->fd; if (serv->enable_coroutine && serv->send_yield) { auto _i_co_list = server_object->property->send_coroutine_map.find(session_id); if (_i_co_list != server_object->property->send_coroutine_map.end()) { auto co_list = _i_co_list->second; server_object->property->send_coroutine_map.erase(session_id); while (!co_list->empty()) { Coroutine *co = co_list->front(); co_list->pop_front(); swoole_set_last_error(ECONNRESET); co->resume(); } delete co_list; } } auto *cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onClose); Connection *conn = serv->get_connection_by_session_id(session_id); if (!conn) { return; } if (conn->websocket_status != swoole::websocket::STATUS_ACTIVE) { ListenPort *port = serv->get_port_by_server_fd(info->server_fd); if (port && port->open_websocket_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onDisconnect)) { cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onDisconnect); } } if (cb) { zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_event_ce); zend_update_property_long( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("fd"), (zend_long) session_id); zend_update_property_long( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("reactor_id"), (zend_long) info->reactor_id); zend_update_property_double( swoole_server_event_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), info->time); argc = 2; } else { ZVAL_LONG(&args[1], session_id); ZVAL_LONG(&args[2], info->reactor_id); argc = 3; } if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onClose handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } } if (conn->http2_stream) { php_swoole_http2_server_onClose(serv, conn->session_id); } } void php_swoole_server_onBufferFull(Server *serv, DataHead *info) { zval *zserv = php_swoole_server_zval_ptr(serv); auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onBufferFull); if (cb) { zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], info->fd); if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBufferFull handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } } void php_swoole_server_check_kernel_nobufs(Server *serv, SessionId session_id) { if (swoole_coroutine_is_in() && serv->has_kernel_nobufs_error(session_id)) { swoole::coroutine::System::sleep(0.01); } } bool php_swoole_server_send_yield(Server *serv, SessionId session_id, zend_string *sdata) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); Coroutine *co = Coroutine::get_current_safe(); size_t length = ZSTR_LEN(sdata); if (length == 0) { return false; } SW_LOOP { auto coroutine_iterator = server_object->property->send_coroutine_map.find(session_id); std::list *co_list; if (coroutine_iterator == server_object->property->send_coroutine_map.end()) { co_list = new std::list; server_object->property->send_coroutine_map[session_id] = co_list; } else { co_list = coroutine_iterator->second; } co_list->push_back(co); auto iter = std::prev(co_list->end()); if (!co->yield_ex(serv->send_timeout)) { co_list->erase(iter); return false; } bool rv = serv->send(session_id, ZSTR_VAL(sdata), length); if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD && serv->send_yield) { continue; } return rv; } return false; } static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendData *data) { auto cb = (zend::Callable *) serv->private_data_3; zval args[4]; zval *zserv = &args[0], *zfd = &args[1], *ztype = &args[2], *zdata = nullptr; zval retval; zend_long worker_id = -1; *zserv = *(php_swoole_server_zval_ptr(serv)); ZVAL_LONG(zfd, conn ? conn->session_id : data->info.fd); ZVAL_LONG(ztype, (zend_long) (data ? data->info.type : (int) SW_SERVER_EVENT_CLOSE)); if (data && sw_zend_function_max_num_args(cb->ptr()->function_handler) > 3) { // TODO: reduce memory copy zdata = &args[3]; ZVAL_STRINGL(zdata, data->data, data->info.len > SW_IPC_BUFFER_SIZE ? SW_IPC_BUFFER_SIZE : data->info.len); } HOOK_PHP_CALL_STACK(auto call_result = sw_zend_call_function_ex(nullptr, cb->ptr(), zdata ? 4 : 3, args, &retval);); if (UNEXPECTED(call_result != SUCCESS)) { php_swoole_error(E_WARNING, "%s->onDispatch handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } else if (!ZVAL_IS_NULL(&retval)) { worker_id = zval_get_long(&retval); if (worker_id >= (zend_long) serv->worker_num) { php_swoole_fatal_error(E_WARNING, "invalid target worker-id[" ZEND_LONG_FMT "]", worker_id); worker_id = -1; } zval_ptr_dtor(&retval); } if (zdata) { zval_ptr_dtor(zdata); } /* the exception should only be thrown after unlocked */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return worker_id; } void php_swoole_server_onBufferEmpty(Server *serv, DataHead *info) { zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); if (serv->send_yield) { auto _i_co_list = server_object->property->send_coroutine_map.find(info->fd); if (_i_co_list != server_object->property->send_coroutine_map.end()) { auto co_list = _i_co_list->second; server_object->property->send_coroutine_map.erase(info->fd); while (!co_list->empty()) { Coroutine *co = co_list->front(); co_list->pop_front(); co->resume(); } delete co_list; } } auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onBufferEmpty); if (cb) { zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], info->fd); if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBufferEmpty handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } } static void server_ctor(zval *zserv, Server *serv) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); *php_swoole_server_zval_ptr(serv) = *zserv; server_set_ptr(zserv, serv); /* primary port */ for (auto ls : serv->ports) { php_swoole_server_add_port(server_object, ls); } /* iterator */ do { zval connection_iterator; object_init_ex(&connection_iterator, swoole_connection_iterator_ce); ConnectionIterator *iterator = php_swoole_connection_iterator_get_ptr(&connection_iterator); iterator->serv = serv; zend_update_property(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("connections"), &connection_iterator); zval_ptr_dtor(&connection_iterator); } while (false); /* info */ auto port = serv->get_primary_port(); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("mode"), serv->get_mode()); zend_update_property_stringl( swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("host"), port->host.c_str(), port->host.length()); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("port"), port->get_port()); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("type"), port->get_type()); zend_update_property_bool(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("ssl"), port->ssl); } static PHP_METHOD(swoole_server, __construct) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); Server *serv = server_object->serv; if (serv) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zval *zserv = ZEND_THIS; char *host; size_t host_len = 0; zend_long sock_type = SW_SOCK_TCP; zend_long serv_port = 0; zend_long serv_mode = Server::MODE_BASE; if (!SWOOLE_G(cli)) { zend_throw_exception_ex( swoole_exception_ce, -1, "%s can only be used in CLI mode", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(serv_port) Z_PARAM_LONG(serv_mode) Z_PARAM_LONG(sock_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (serv_mode != Server::MODE_BASE && serv_mode != Server::MODE_PROCESS #ifdef SW_THREAD && serv_mode != Server::MODE_THREAD #endif ) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); zend_throw_error(nullptr, "invalid $mode parameters %d", (int) serv_mode); RETURN_FALSE; } #ifdef SW_THREAD if (sw_server() && sw_server()->is_worker_thread()) { server_ctor(ZEND_THIS, sw_server()); return; } if (!tsrm_is_main_thread()) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_exception_ex(swoole_exception_ce, -2, "This operation is only allowed in the main thread"); RETURN_FALSE; } #else if (sw_server() != nullptr) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); zend_throw_exception_ex( swoole_exception_ce, -3, "server is running. unable to create %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } #endif serv = new Server((enum Server::Mode) serv_mode); if (serv_mode == Server::MODE_BASE) { serv->reactor_num = 1; serv->worker_num = 1; } if (serv_port == 0 && strcasecmp(host, "SYSTEMD") == 0) { if (serv->add_systemd_socket() <= 0) { zend_throw_error(nullptr, "failed to add systemd socket"); RETURN_FALSE; } } else { auto *port = serv->add_port((SocketType) sock_type, host, serv_port); if (!port) { zend_throw_exception_ex(swoole_exception_ce, swoole_get_last_error(), "failed to listen server port[%s:" ZEND_LONG_FMT "], Error: %s[%d]", host, serv_port, swoole_strerror(swoole_get_last_error()), swoole_get_last_error()); RETURN_FALSE; } } server_ctor(zserv, serv); } static PHP_METHOD(swoole_server, __destruct) {} static PHP_METHOD(swoole_server, set) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (serv->is_worker_thread()) { swoole_set_last_error(SW_ERROR_SERVER_UNRELATED_THREAD); RETURN_FALSE; } if (serv->is_started()) { php_swoole_fatal_error( E_WARNING, "server is running, unable to execute %s->set", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zval *zset = nullptr, *ztmp; HashTable *vht; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); php_swoole_set_global_option(vht); php_swoole_set_coroutine_option(vht); php_swoole_set_aio_option(vht); if (php_swoole_array_get_value(vht, "chroot", ztmp)) { serv->chroot_ = zend::String(ztmp).to_std_string(); } if (php_swoole_array_get_value(vht, "user", ztmp)) { serv->user_ = zend::String(ztmp).to_std_string(); } if (php_swoole_array_get_value(vht, "group", ztmp)) { serv->group_ = zend::String(ztmp).to_std_string(); } if (php_swoole_array_get_value(vht, "daemonize", ztmp)) { serv->daemonize = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "pid_file", ztmp)) { serv->pid_file = zend::String(ztmp).to_std_string(); } if (php_swoole_array_get_value(vht, "reactor_num", ztmp)) { zend_long v = zval_get_long(ztmp); serv->reactor_num = SW_MAX(0, SW_MIN(v, UINT16_MAX)); if (serv->reactor_num == 0) { serv->reactor_num = SW_CPU_NUM; } } if (php_swoole_array_get_value(vht, "single_thread", ztmp)) { serv->single_thread = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "worker_num", ztmp)) { zend_long v = zval_get_long(ztmp); serv->worker_num = SW_MAX(0, SW_MIN(v, UINT32_MAX)); if (serv->worker_num == 0) { serv->worker_num = SW_CPU_NUM; } } if (php_swoole_array_get_value(vht, "max_wait_time", ztmp)) { zend_long v = zval_get_long(ztmp); serv->max_wait_time = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "max_queued_bytes", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); serv->max_queued_bytes = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "max_concurrency", ztmp)) { zend_long v = zval_get_long(ztmp); serv->set_max_concurrency(SW_MAX(1, SW_MIN(v, UINT32_MAX))); if (serv->get_worker_max_concurrency() == UINT_MAX) { serv->set_worker_max_concurrency(serv->get_max_concurrency()); } } if (php_swoole_array_get_value(vht, "worker_max_concurrency", ztmp)) { zend_long v = zval_get_long(ztmp); serv->set_worker_max_concurrency(SW_MAX(1, SW_MIN(v, UINT32_MAX))); } if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { serv->enable_coroutine = zval_is_true(ztmp); } else { serv->enable_coroutine = SwooleG.enable_coroutine; } if (php_swoole_array_get_value(vht, "send_timeout", ztmp)) { serv->send_timeout = zval_get_double(ztmp); } if (php_swoole_array_get_value(vht, "dispatch_mode", ztmp)) { zend_long v = zval_get_long(ztmp); serv->dispatch_mode = SW_MAX(0, SW_MIN(v, UINT8_MAX)); } if (php_swoole_array_get_value(vht, "send_yield", ztmp)) { serv->send_yield = zval_is_true(ztmp); if (serv->send_yield && !(serv->dispatch_mode == Server::DISPATCH_FDMOD || serv->dispatch_mode == Server::DISPATCH_IPMOD)) { php_swoole_error(E_WARNING, "'send_yield' option can only be set when using dispatch_mode=2/4"); serv->send_yield = false; } } else { serv->send_yield = serv->enable_coroutine; } if (php_swoole_array_get_value(vht, "dispatch_func", ztmp)) { auto fci_cache = sw_callable_create(ztmp); if (fci_cache) { if (serv->private_data_3) { sw_callable_free(serv->private_data_3); } serv->private_data_3 = fci_cache; serv->dispatch_func = php_swoole_server_dispatch_func; serv->single_thread = true; } } /** * for dispatch_mode = 1/3 */ if (php_swoole_array_get_value(vht, "discard_timeout_request", ztmp)) { serv->discard_timeout_request = zval_is_true(ztmp); } // onConnect/onClose event if (php_swoole_array_get_value(vht, "enable_unsafe_event", ztmp)) { serv->enable_unsafe_event = zval_is_true(ztmp); } // delay receive if (php_swoole_array_get_value(vht, "enable_delay_receive", ztmp)) { serv->enable_delay_receive = zval_is_true(ztmp); } #if defined(__linux__) and defined(HAVE_REUSEPORT) if (php_swoole_array_get_value(vht, "enable_reuse_port", ztmp)) { serv->enable_reuse_port = zval_is_true(ztmp); } #endif // task use object if (php_swoole_array_get_value(vht, "task_use_object", ztmp) || php_swoole_array_get_value(vht, "task_object", ztmp)) { serv->task_object = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "event_object", ztmp)) { serv->event_object = zval_is_true(ztmp); if (serv->event_object) { serv->task_object = true; } } // task coroutine if (php_swoole_array_get_value(vht, "task_enable_coroutine", ztmp)) { serv->task_enable_coroutine = zval_is_true(ztmp); } // task_worker_num if (php_swoole_array_get_value(vht, "task_worker_num", ztmp)) { zend_long v = zval_get_long(ztmp); serv->task_worker_num = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } // task ipc mode, 1,2,3 if (php_swoole_array_get_value(vht, "task_ipc_mode", ztmp)) { zend_long v = zval_get_long(ztmp); serv->task_ipc_mode = SW_MAX(0, SW_MIN(v, UINT8_MAX)); } /** * Temporary file directory for task_worker */ if (php_swoole_array_get_value(vht, "task_tmpdir", ztmp)) { zend::String str_v(ztmp); swoole_set_task_tmpdir(str_v.to_std_string()); } // task_max_request if (php_swoole_array_get_value(vht, "task_max_request", ztmp)) { zend_long v = zval_get_long(ztmp); serv->task_max_request = SW_MAX(0, SW_MIN(v, UINT32_MAX)); // task_max_request_grace if (php_swoole_array_get_value(vht, "task_max_request_grace", ztmp)) { zend_long v2 = zval_get_long(ztmp); serv->task_max_request_grace = SW_MAX(0, SW_MIN(v2, UINT32_MAX)); } else if (serv->task_max_request > SW_WORKER_MIN_REQUEST) { serv->task_max_request_grace = serv->task_max_request / 2; } } // max_connection if (php_swoole_array_get_value(vht, "max_connection", ztmp) || php_swoole_array_get_value(vht, "max_conn", ztmp)) { zend_long v = zval_get_long(ztmp); serv->set_max_connection(SW_MAX(0, SW_MIN(v, UINT32_MAX))); } if (php_swoole_array_get_value(vht, "start_session_id", ztmp)) { serv->set_start_session_id(zval_get_long(ztmp)); } // heartbeat_check_interval if (php_swoole_array_get_value(vht, "heartbeat_check_interval", ztmp)) { zend_long v = zval_get_long(ztmp); serv->heartbeat_check_interval = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } else if (php_swoole_array_get_value(vht, "heartbeat_idle_time", ztmp)) { zend_long v = zval_get_long(ztmp); serv->heartbeat_check_interval = v > 2 ? v / 2 : 1; } // max_request if (php_swoole_array_get_value(vht, "max_request", ztmp)) { zend_long v = zval_get_long(ztmp); serv->max_request = SW_MAX(0, SW_MIN(v, UINT32_MAX)); // max_request_grace if (php_swoole_array_get_value(vht, "max_request_grace", ztmp)) { zend_long v2 = zval_get_long(ztmp); serv->max_request_grace = SW_MAX(0, SW_MIN(v2, UINT32_MAX)); } else if (serv->max_request > SW_WORKER_MIN_REQUEST) { serv->max_request_grace = serv->max_request / 2; } } // reload async if (php_swoole_array_get_value(vht, "reload_async", ztmp)) { serv->reload_async = zval_is_true(ztmp); } // cpu affinity if (php_swoole_array_get_value(vht, "open_cpu_affinity", ztmp)) { serv->open_cpu_affinity = zval_is_true(ztmp); } // cpu affinity set if (php_swoole_array_get_value(vht, "cpu_affinity_ignore", ztmp)) { int ignore_num = zend_hash_num_elements(Z_ARRVAL_P(ztmp)); if (ignore_num >= SW_CPU_NUM) { php_swoole_fatal_error(E_ERROR, "cpu_affinity_ignore num must be less than cpu num (%d)", SW_CPU_NUM); RETURN_FALSE; } int available_num = SW_CPU_NUM - ignore_num; int *available_cpu = (int *) sw_malloc(sizeof(int) * available_num); if (!available_cpu) { php_swoole_fatal_error(E_WARNING, "malloc() failed"); RETURN_FALSE; } int flag, i, available_i = 0; zval *zval_core = nullptr; for (i = 0; i < SW_CPU_NUM; i++) { flag = 1; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), zval_core) if (i == zval_get_long(zval_core)) { flag = 0; break; } SW_HASHTABLE_FOREACH_END(); if (flag) { available_cpu[available_i] = i; available_i++; } } serv->cpu_affinity_available_num = available_num; if (serv->cpu_affinity_available) { sw_free(serv->cpu_affinity_available); } serv->cpu_affinity_available = available_cpu; } // parse cookie header if (php_swoole_array_get_value(vht, "http_parse_cookie", ztmp)) { serv->http_parse_cookie = zval_is_true(ztmp); } // parse x-www-form-urlencoded form data if (php_swoole_array_get_value(vht, "http_parse_post", ztmp)) { serv->http_parse_post = zval_is_true(ztmp); } // parse multipart/form-data file uploads if (php_swoole_array_get_value(vht, "http_parse_files", ztmp)) { serv->http_parse_files = zval_is_true(ztmp); } #ifdef SW_HAVE_COMPRESSION // http content compression if (php_swoole_array_get_value(vht, "http_compression", ztmp)) { serv->http_compression = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "http_compression_level", ztmp) || php_swoole_array_get_value(vht, "compression_level", ztmp) || php_swoole_array_get_value(vht, "http_gzip_level", ztmp)) { zend_long level = zval_get_long(ztmp); if (level > UINT8_MAX) { level = UINT8_MAX; } else if (level < 0) { level = 0; } serv->http_compression_level = level; } if (php_swoole_array_get_value(vht, "http_compression_min_length", ztmp) || php_swoole_array_get_value(vht, "compression_min_length", ztmp)) { serv->compression_min_length = php_swoole_parse_to_size(ztmp); } #endif #ifdef SW_HAVE_ZLIB if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) { serv->websocket_compression = zval_is_true(ztmp); } #endif // temporary directory for HTTP uploaded file. if (php_swoole_array_get_value(vht, "upload_tmp_dir", ztmp)) { zend::String str_v(ztmp); if (php_swoole_create_dir(str_v.val(), str_v.len()) < 0) { php_swoole_fatal_error(E_ERROR, "Unable to create upload_tmp_dir[%s]", str_v.val()); return; } serv->upload_tmp_dir = str_v.to_std_string(); } if (php_swoole_array_get_value(vht, "upload_max_filesize", ztmp)) { serv->upload_max_filesize = php_swoole_parse_to_size(ztmp); } /** * http static file handler */ if (php_swoole_array_get_value(vht, "enable_static_handler", ztmp)) { serv->enable_static_handler = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "document_root", ztmp)) { zend::String str_v(ztmp); if (str_v.len() >= PATH_MAX) { php_swoole_fatal_error(E_ERROR, "The length of document_root must be less than %d", PATH_MAX); return; } serv->set_document_root(std::string(str_v.val(), str_v.len())); } if (php_swoole_array_get_value(vht, "http_autoindex", ztmp)) { serv->http_autoindex = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "http_index_files", ztmp)) { if (ZVAL_IS_ARRAY(ztmp)) { zval *_http_index_files; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), _http_index_files) zend::String obj_http_index_files(_http_index_files); if (obj_http_index_files.len() > 0) { serv->add_static_handler_index_files(obj_http_index_files.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { php_swoole_fatal_error(E_ERROR, "http_index_files must be array"); RETURN_FALSE; } } if (php_swoole_array_get_value(vht, "http_compression_types", ztmp) || php_swoole_array_get_value(vht, "compression_types", ztmp)) { if (ZVAL_IS_ARRAY(ztmp)) { zval *ztype; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), ztype) zend::String type(ztype); if (type.len() > 0) { serv->add_http_compression_type(type.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { php_swoole_fatal_error(E_ERROR, "http_compression_types must be array"); RETURN_FALSE; } } /** * [static_handler] locations */ if (php_swoole_array_get_value(vht, "static_handler_locations", ztmp)) { if (ZVAL_IS_ARRAY(ztmp)) { zval *_location; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), _location) zend::String obj_location(_location); if (obj_location.len() > 0 && obj_location.val()[0] == '/') { serv->add_static_handler_location(obj_location.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { php_swoole_fatal_error(E_ERROR, "static_handler_locations num must be array"); RETURN_FALSE; } } /** * [url_rewrite] rules */ if (php_swoole_array_get_value(vht, "url_rewrite_rules", ztmp)) { if (ZVAL_IS_ARRAY(ztmp)) { zval *replacement; zend_string *pattern; ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(ztmp), pattern, replacement) { ZVAL_DEREF(replacement); if (Z_TYPE_P(replacement) == IS_STRING) { serv->add_rewrite_rule(std::string(ZSTR_VAL(pattern), ZSTR_LEN(pattern)), std::string(Z_STRVAL_P(replacement), Z_STRLEN_P(replacement))); } else { php_swoole_fatal_error(E_ERROR, "The `replacement` must be string"); RETURN_FALSE; } } ZEND_HASH_FOREACH_END(); } else { php_swoole_fatal_error(E_ERROR, "The `url_rewrite_rules` must be array"); RETURN_FALSE; } } /** * buffer input size */ if (php_swoole_array_get_value(vht, "input_buffer_size", ztmp) || php_swoole_array_get_value(vht, "buffer_input_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); serv->input_buffer_size = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } /** * buffer output size */ if (php_swoole_array_get_value(vht, "output_buffer_size", ztmp) || php_swoole_array_get_value(vht, "buffer_output_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); serv->output_buffer_size = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } // message queue key if (php_swoole_array_get_value(vht, "message_queue_key", ztmp)) { zend_long v = zval_get_long(ztmp); serv->message_queue_key = SW_MAX(0, SW_MIN(v, INT64_MAX)); } #ifdef SW_THREAD // bootstrap if (php_swoole_array_get_value(vht, "bootstrap", ztmp)) { zend::object_set(ZEND_THIS, ZEND_STRL("bootstrap"), ztmp); } else { zend::object_set(ZEND_THIS, ZEND_STRL("bootstrap"), SG(request_info).path_translated); } // thread arguments if (php_swoole_array_get_value(vht, "init_arguments", ztmp)) { server_object->init_arguments = *ztmp; } else { ZVAL_NULL(&server_object->init_arguments); } #endif #ifndef HAVE_MSGQUEUE if (serv->task_ipc_mode == Server::TASK_IPC_MSGQUEUE || serv->task_ipc_mode == Server::TASK_IPC_PREEMPTIVE) { php_swoole_fatal_error(E_ERROR, "not support `sysvmsg`"); RETURN_FALSE; } #endif if (serv->task_enable_coroutine && (serv->task_ipc_mode == Server::TASK_IPC_MSGQUEUE || serv->task_ipc_mode == Server::TASK_IPC_PREEMPTIVE)) { php_swoole_fatal_error(E_ERROR, "cannot use msgqueue when `task_enable_coroutine` is enable"); RETURN_FALSE; } sw_zend_call_method_with_1_params( server_object->property->ports.at(0), swoole_server_port_ce, nullptr, "set", nullptr, zset); zval *zsetting = sw_zend_read_and_convert_property_array(swoole_server_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); RETURN_TRUE; } static PHP_METHOD(swoole_server, on) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is running, unable to register event callback function"); RETURN_FALSE; } zval *name; zval *cb; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(name) Z_PARAM_ZVAL(cb) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend::String _event_name_ori(name); zend::String _event_name_tolower(zend_string_tolower(_event_name_ori.get()), false); ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); auto i = server_event_map.find(_event_name_tolower.to_std_string()); if (i == server_event_map.end()) { zval *port_object = server_object->property->ports.at(0); zval retval; sw_zend_call_method_with_2_params(port_object, swoole_server_port_ce, nullptr, "on", &retval, name, cb); RETURN_BOOL(Z_BVAL_P(&retval)); } else { int event_type = i->second.type; std::string property_name = "on" + i->second.name; zend_update_property( swoole_server_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), cb); if (server_object->property->callbacks[event_type]) { sw_callable_free(server_object->property->callbacks[event_type]); } auto fci_cache = sw_callable_create(cb); if (!fci_cache) { RETURN_FALSE; } server_object->property->callbacks[event_type] = fci_cache; RETURN_TRUE; } } static PHP_METHOD(swoole_server, getCallback) { zval *name; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(name) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend::String _event_name_ori(name); zend::String _event_name_tolower(zend_string_tolower(_event_name_ori.get()), false); auto i = server_event_map.find(_event_name_tolower.to_std_string()); if (i != server_event_map.end()) { std::string property_name = "on" + i->second.name; // Notice: we should use Z_OBJCE_P instead of swoole_server_ce, because we need to consider the subclasses. zval rv, *property = zend_read_property( Z_OBJCE_P(ZEND_THIS), SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), true, &rv); if (!ZVAL_IS_NULL(property)) { RETURN_ZVAL(property, 1, 0); } } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); sw_zend_call_method_with_1_params( server_object->property->ports.at(0), swoole_server_port_ce, nullptr, "getcallback", return_value, name); } static PHP_METHOD(swoole_server, listen) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is running, cannot add listener"); RETURN_FALSE; } char *host; size_t host_len; zend_long sock_type; zend_long port; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STRING(host, host_len) Z_PARAM_LONG(port) Z_PARAM_LONG(sock_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); ListenPort *ls; if (serv->is_worker_thread()) { ls = serv->get_port((enum swSocketType) sock_type, host, (int) port); } else { ls = serv->add_port((enum swSocketType) sock_type, host, (int) port); } if (!ls) { RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); zval *port_object = php_swoole_server_add_port(server_object, ls); RETURN_ZVAL(port_object, 1, 0); } extern Worker *php_swoole_process_get_and_check_worker(const zval *zobject); static PHP_METHOD(swoole_server, addProcess) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is running, cannot add process"); RETURN_FALSE; } int worker_id; Worker *worker; zval *process; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(process) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (!ZVAL_IS_OBJECT(process) || !instanceof_function(Z_OBJCE_P(process), swoole_process_ce)) { php_swoole_fatal_error(E_ERROR, "object is not instanceof swoole_process"); RETURN_FALSE; } if (serv->onUserWorkerStart == nullptr) { serv->onUserWorkerStart = php_swoole_server_onUserWorkerStart; } zval *tmp_process = (zval *) emalloc(sizeof(zval)); memcpy(tmp_process, process, sizeof(zval)); process = tmp_process; Z_TRY_ADDREF_P(process); ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); server_object->property->user_processes.push_back(process); if (serv->is_worker_thread()) { if (!serv->is_user_worker()) { swoole_set_last_error(SW_ERROR_SERVER_UNRELATED_THREAD); RETURN_FALSE; } worker_id = swoole_get_worker_id(); worker = serv->get_worker(worker_id); worker->redirect_stdin = worker->redirect_stdout = worker->redirect_stderr = false; worker_id -= serv->get_core_worker_num(); } else { worker = php_swoole_process_get_and_check_worker(process); worker_id = serv->add_worker(worker); if (worker_id < 0) { php_swoole_fatal_error(E_WARNING, "failed to add worker"); RETURN_FALSE; } worker->ptr = process; } zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(process), ZEND_STRL("id"), worker_id); RETURN_LONG(worker_id); } static PHP_METHOD(swoole_server, addCommand) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is running, can't add command"); RETURN_FALSE; } char *name; size_t l_name; zend_long accepted_process_types; zval *zfn; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STRING(name, l_name) Z_PARAM_LONG(accepted_process_types) Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (accepted_process_types & Server::Command::REACTOR_THREAD) { php_swoole_fatal_error(E_WARNING, "only support worker/task_worker process"); RETURN_FALSE; } auto cb = sw_callable_create(zfn); if (!cb) { RETURN_FALSE; } Server::Command::Handler fn = [cb](Server *serv, const std::string &msg) { zval *zserv = php_swoole_server_zval_ptr(serv); zval argv[2]; argv[0] = *zserv; ZVAL_STRINGL(&argv[1], msg.c_str(), msg.length()); zval return_value; if (UNEXPECTED(!zend::function::call(cb, 2, argv, &return_value, false))) { php_swoole_fatal_error(E_WARNING, "%s: command handler error", ZSTR_VAL(swoole_server_ce->name)); return std::string(R"({"data": "failed to call function", "code": -1})"); } if (!ZVAL_IS_STRING(&return_value)) { return std::string(R"({"data": "wrong return type", "code": -2})"); } return std::string(Z_STRVAL(return_value), Z_STRLEN(return_value)); }; if (!serv->add_command(std::string(name, l_name), accepted_process_types, fn)) { sw_callable_free(cb); RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); server_object->property->command_callbacks.push_back(cb); RETURN_TRUE; } static PHP_METHOD(swoole_server, start) { zval *zserv = ZEND_THIS; Server *serv = php_swoole_server_get_and_check_server(zserv); #ifdef SW_THREAD if (serv->is_worker_thread()) { zval *zsetting = sw_zend_read_and_convert_property_array(Z_OBJCE_P(ZEND_THIS), zserv, ZEND_STRL("setting"), 0); php_swoole_unserialize((zend_string *) serv->private_data_4, zsetting); auto ht = Z_ARRVAL_P(zsetting); /** * The coroutine configurations are thread-local variables, * and each worker thread must reset them once. */ php_swoole_set_coroutine_option(ht); worker_thread_fn(); RETURN_TRUE; } #endif if (serv->is_started()) { php_swoole_fatal_error( E_WARNING, "The server is running, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } if (serv->is_shutdown()) { php_swoole_fatal_error( E_WARNING, "The server have been shutdown, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } if (sw_reactor()) { php_swoole_fatal_error( E_WARNING, "The event-loop has already been created, unable to start %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); #ifdef SW_THREAD zend_string *bootstrap = nullptr; ZendArray *thread_argv = nullptr; if (serv->is_thread_mode()) { zval *_bootstrap = zend::object_get(ZEND_THIS, ZEND_STRL("bootstrap")); bootstrap = zend_string_dup(Z_STR_P(_bootstrap), true); if (!ZVAL_IS_NULL(&server_object->init_arguments)) { zval _thread_argv; call_user_function(NULL, nullptr, &server_object->init_arguments, &_thread_argv, 0, nullptr); if (ZVAL_IS_ARRAY(&_thread_argv)) { thread_argv = ZendArray::from(Z_ARRVAL(_thread_argv)); } zval_ptr_dtor(&_thread_argv); } serv->worker_thread_start = [bootstrap, thread_argv](std::shared_ptr thread, const WorkerFn &fn) { worker_thread_fn = fn; zend_string *bootstrap_copy = zend_string_dup(bootstrap, true); if (thread_argv) { thread_argv->add_ref(); } php_swoole_thread_start(thread, bootstrap_copy, thread_argv); }; // The runtime hook must be enabled before creating child threads. if (PHPCoroutine::get_hook_flags() > 0) { PHPCoroutine::enable_hook(PHPCoroutine::get_hook_flags()); } } #endif server_object->register_callback(); server_object->on_before_start(); if (serv->start() < 0) { php_swoole_fatal_error(E_ERROR, "failed to start server. Error: %s", serv->get_startup_error_message()); } #ifdef SW_THREAD if (bootstrap) { zend_string_release(bootstrap); } if (thread_argv) { thread_argv->del_ref(); } #endif RETURN_TRUE; } static PHP_METHOD(swoole_server, send) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zval *zfd; zend_string *sdata; zend_long server_socket = -1; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_ZVAL(zfd) Z_PARAM_STR(sdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(server_socket) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(ZVAL_IS_NULL(zfd))) { php_swoole_fatal_error(E_WARNING, "fd can not be null"); RETURN_FALSE; } auto data = ZSTR_VAL(sdata); size_t length = ZSTR_LEN(sdata); if (length == 0) { php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } // UNIX DGRAM SOCKET if (serv->have_dgram_sock && Z_TYPE_P(zfd) == IS_STRING && Z_STRVAL_P(zfd)[0] == '/') { network::Socket *sock = server_socket == -1 ? serv->dgram_socket : serv->get_server_socket(server_socket); if (sock == nullptr) { RETURN_FALSE; } RETURN_BOOL(sock->sendto(Z_STRVAL_P(zfd), 0, data, length) > 0); } zend_long fd = zval_get_long(zfd); if (UNEXPECTED(fd <= 0)) { php_swoole_fatal_error(E_WARNING, "invalid fd[" ZEND_LONG_FMT "]", fd); RETURN_FALSE; } bool rv = serv->send(fd, data, length); if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { rv = php_swoole_server_send_yield(serv, fd, sdata); } else { php_swoole_server_check_kernel_nobufs(serv, fd); } RETURN_BOOL(rv); } static PHP_METHOD(swoole_server, sendto) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } char *addr; size_t addr_len; zend_long port; char *data; size_t len; zend_long server_socket_fd = -1; enum swSocketType type; ZEND_PARSE_PARAMETERS_START(3, 4) Z_PARAM_STRING(addr, addr_len) Z_PARAM_LONG(port) Z_PARAM_STRING(data, len) Z_PARAM_OPTIONAL Z_PARAM_LONG(server_socket_fd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (len == 0) { php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } if (addr[0] == '/') { type = SW_SOCK_UNIX_DGRAM; } else if (strchr(addr, ':')) { type = SW_SOCK_UDP6; } else { type = SW_SOCK_UDP; } network::Socket *server_socket = nullptr; switch (type) { case SW_SOCK_UDP: if (!serv->udp_socket_ipv4) { php_swoole_fatal_error(E_WARNING, "UDP listener has to be added before executing sendto"); RETURN_FALSE; } else { server_socket = server_socket_fd < 0 ? serv->udp_socket_ipv4 : serv->get_server_socket(server_socket_fd); } break; case SW_SOCK_UDP6: if (!serv->udp_socket_ipv6) { php_swoole_fatal_error(E_WARNING, "UDP6 listener has to be added before executing sendto"); RETURN_FALSE; } else { server_socket = server_socket_fd < 0 ? serv->udp_socket_ipv6 : serv->get_server_socket(server_socket_fd); } break; case SW_SOCK_UNIX_DGRAM: if (!serv->dgram_socket) { php_swoole_fatal_error(E_WARNING, "UnixDgram listener has to be added before executing sendto"); RETURN_FALSE; } else { server_socket = server_socket_fd < 0 ? serv->dgram_socket : serv->get_server_socket(server_socket_fd); } break; default: abort(); break; } SW_CHECK_RETURN(server_socket->sendto(addr, port, data, len)); } static PHP_METHOD(swoole_server, sendfile) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; char *filename; size_t len; zend_long offset = 0; zend_long length = 0; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_LONG(fd) Z_PARAM_STRING(filename, len) Z_PARAM_OPTIONAL Z_PARAM_LONG(offset) Z_PARAM_LONG(length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (serv->is_master()) { php_swoole_fatal_error(E_WARNING, "can't sendfile[%s] to the connections in master process", filename); RETURN_FALSE; } RETURN_BOOL(serv->sendfile(fd, filename, len, offset, length)); } static PHP_METHOD(swoole_server, close) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; zend_bool reset = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(fd) Z_PARAM_OPTIONAL Z_PARAM_BOOL(reset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(serv->close(fd, reset)); } static PHP_METHOD(swoole_server, pause) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(fd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(fd); if (!conn) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); RETURN_FALSE; } RETURN_BOOL(serv->feedback(conn, SW_SERVER_EVENT_PAUSE_RECV)); } static PHP_METHOD(swoole_server, resume) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(fd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(fd); if (!conn) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); RETURN_FALSE; } RETURN_BOOL(serv->feedback(conn, SW_SERVER_EVENT_RESUME_RECV)); } static PHP_METHOD(swoole_server, stats) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("start_time"), serv->gs->start_time); add_assoc_long_ex(return_value, ZEND_STRL("connection_num"), serv->get_connection_num()); add_assoc_long_ex(return_value, ZEND_STRL("abort_count"), serv->gs->abort_count); add_assoc_long_ex(return_value, ZEND_STRL("accept_count"), serv->gs->accept_count); add_assoc_long_ex(return_value, ZEND_STRL("close_count"), serv->gs->close_count); add_assoc_long_ex(return_value, ZEND_STRL("worker_num"), serv->worker_num); add_assoc_long_ex(return_value, ZEND_STRL("task_worker_num"), serv->task_worker_num); add_assoc_long_ex(return_value, ZEND_STRL("user_worker_num"), serv->get_user_worker_num()); add_assoc_long_ex(return_value, ZEND_STRL("idle_worker_num"), serv->get_idle_worker_num()); add_assoc_long_ex(return_value, ZEND_STRL("dispatch_count"), serv->gs->dispatch_count); add_assoc_long_ex(return_value, ZEND_STRL("request_count"), serv->gs->request_count); add_assoc_long_ex(return_value, ZEND_STRL("response_count"), serv->gs->response_count); add_assoc_long_ex(return_value, ZEND_STRL("total_recv_bytes"), serv->gs->total_recv_bytes); add_assoc_long_ex(return_value, ZEND_STRL("total_send_bytes"), serv->gs->total_send_bytes); add_assoc_long_ex(return_value, ZEND_STRL("pipe_packet_msg_id"), serv->gs->pipe_packet_msg_id); add_assoc_long_ex(return_value, ZEND_STRL("concurrency"), serv->get_concurrency()); add_assoc_long_ex(return_value, ZEND_STRL("session_round"), serv->gs->session_round); add_assoc_long_ex(return_value, ZEND_STRL("min_fd"), serv->gs->min_fd); add_assoc_long_ex(return_value, ZEND_STRL("max_fd"), serv->gs->max_fd); if (sw_worker()) { add_assoc_long_ex(return_value, ZEND_STRL("worker_request_count"), sw_worker()->request_count); add_assoc_long_ex(return_value, ZEND_STRL("worker_response_count"), sw_worker()->response_count); add_assoc_long_ex(return_value, ZEND_STRL("worker_dispatch_count"), sw_worker()->dispatch_count); add_assoc_long_ex(return_value, ZEND_STRL("worker_concurrency"), sw_worker()->concurrency); } if (serv->task_ipc_mode > Server::TASK_IPC_UNIXSOCK && serv->get_task_worker_pool()->queue) { size_t queue_num = -1; size_t queue_bytes = -1; if (serv->get_task_worker_pool()->queue->stat(&queue_num, &queue_bytes)) { add_assoc_long_ex(return_value, ZEND_STRL("task_queue_num"), queue_num); add_assoc_long_ex(return_value, ZEND_STRL("task_queue_bytes"), queue_bytes); } } if (serv->task_worker_num > 0) { add_assoc_long_ex(return_value, ZEND_STRL("task_idle_worker_num"), serv->get_idle_task_worker_num()); add_assoc_long_ex(return_value, ZEND_STRL("tasking_num"), serv->get_tasking_num()); add_assoc_long_ex(return_value, ZEND_STRL("task_count"), serv->gs->task_count); } add_assoc_long_ex(return_value, ZEND_STRL("coroutine_num"), Coroutine::count()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_peek_num"), Coroutine::get_peak_num()); } static PHP_METHOD(swoole_server, reload) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_bool only_reload_task_workers = false; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(only_reload_task_workers) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(serv->reload(!only_reload_task_workers)); } static PHP_METHOD(swoole_server, heartbeat) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_bool close_connection = false; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_BOOL(close_connection) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (serv->heartbeat_check_interval < 1) { RETURN_FALSE; } array_init(return_value); double now = microtime(); serv->foreach_connection([serv, now, close_connection, return_value](Connection *conn) { SessionId session_id = conn->session_id; if (session_id <= 0) { return; } swoole_trace("heartbeat check fd=%d", conn->fd); if (serv->is_healthy_connection(now, conn)) { return; } if (close_connection) { conn->close_force = 1; serv->close(session_id, false); } add_next_index_long(return_value, session_id); }); } static PHP_METHOD(swoole_server, taskwait) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (!serv->is_worker()) { php_swoole_fatal_error(E_WARNING, "taskwait method can only be used in the worker process"); RETURN_FALSE; } zval *zdata; double timeout = SW_TASKWAIT_TIMEOUT; zend_long dst_worker_id = -1; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) Z_PARAM_LONG(dst_worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } EventData buf; if (php_swoole_server_task_pack(zdata, &buf) < 0) { RETURN_FALSE; } TaskId task_id = serv->get_task_id(&buf); // coroutine if (swoole_coroutine_is_in()) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); TaskCo task_co{}; task_co.co = Coroutine::get_current_safe(); task_co.count = 1; task_co.result = return_value; if (!serv->task(&buf, (int *) &dst_worker_id)) { RETURN_FALSE; } server_object->property->task_coroutine_map[task_id] = &task_co; auto retval = task_co.co->yield_ex(timeout); server_object->property->task_coroutine_map.erase(task_id); if (!retval) { RETURN_FALSE; } } else { auto retval = serv->task_sync(&buf, (int *) &dst_worker_id, timeout); if (!retval) { RETURN_FALSE; } zval zresult; auto task_result = serv->get_task_result(); if (!php_swoole_server_task_unpack(&zresult, task_result)) { RETURN_FALSE; } else { RETURN_ZVAL(&zresult, 0, 0); } } } static PHP_METHOD(swoole_server, taskWaitMulti) { if (swoole_coroutine_is_in()) { return ZEND_MN(swoole_server_taskCo)(INTERNAL_FUNCTION_PARAM_PASSTHRU); } Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (!serv->is_worker()) { php_swoole_fatal_error(E_WARNING, "taskWaitMulti method can only be used in the worker process"); RETURN_FALSE; } zval *ztasks; double timeout = SW_TASKWAIT_TIMEOUT; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(ztasks) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_server_task_check_param(serv, -1) < 0) { RETURN_FALSE; } array_init(return_value); int n_task = php_swoole_array_length(ztasks); if (n_task >= SW_MAX_CONCURRENT_TASK) { php_swoole_fatal_error(E_WARNING, "too many concurrent tasks"); RETURN_FALSE; } Server::MultiTask mt(n_task); mt.pack = [ztasks](uint16_t i, EventData *buf) { auto *ztask = zend::array_get(ztasks, (zend_ulong) i); return php_swoole_server_task_pack(ztask, buf); }; mt.unpack = [return_value](uint16_t i, EventData *result) { zval zresult; if (php_swoole_server_task_unpack(&zresult, result)) { add_index_zval(return_value, i, &zresult); } }; mt.fail = [return_value](uint16_t i) { add_index_bool(return_value, i, false); }; if (!serv->task_sync(mt, timeout)) { zval_ptr_dtor(return_value); RETURN_FALSE; } } static PHP_METHOD(swoole_server, taskCo) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (!serv->is_worker()) { php_swoole_fatal_error(E_WARNING, "taskCo method can only be used in the worker process"); RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); zval *ztasks; double timeout = SW_TASKWAIT_TIMEOUT; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(ztasks) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int dst_worker_id = -1; TaskId task_id; int i = 0; uint32_t n_task = php_swoole_array_length(ztasks); if (n_task >= SW_MAX_CONCURRENT_TASK) { php_swoole_fatal_error(E_WARNING, "too many concurrent tasks"); RETURN_FALSE; } if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } auto *list = static_cast(ecalloc(n_task, sizeof(TaskId))); if (list == nullptr) { RETURN_FALSE; } TaskCo task_co; task_co.co = Coroutine::get_current_safe(); array_init_size(return_value, n_task); zval *ztask; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztasks), ztask) { EventData buf; task_id = php_swoole_server_task_pack(ztask, &buf); if (task_id < 0) { php_swoole_fatal_error(E_WARNING, "failed to pack task"); goto _fail; } buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); dst_worker_id = -1; if (!serv->task(&buf, &dst_worker_id)) { task_id = -1; _fail: add_index_bool(return_value, i, false); n_task--; } else { server_object->property->task_coroutine_map[task_id] = &task_co; } list[i] = task_id; i++; } SW_HASHTABLE_FOREACH_END(); if (n_task == 0) { swoole_set_last_error(SW_ERROR_TASK_DISPATCH_FAIL); RETURN_FALSE; } task_co.result = return_value; task_co.list = list; task_co.count = n_task; if (!task_co.co->yield_ex(timeout)) { bool is_called_in_taskCo = strcasecmp(EX(func)->internal_function.function_name->val, "taskCo") == 0; for (uint32_t j = 0; j < n_task; j++) { if (!zend_hash_index_exists(Z_ARRVAL_P(return_value), j)) { if (is_called_in_taskCo) { add_index_bool(return_value, j, false); } server_object->property->task_coroutine_map.erase(list[j]); } } } } static PHP_METHOD(swoole_server, task) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); zval *zdata; zend_long dst_worker_id = -1; zval *zfn = nullptr; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(dst_worker_id) Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } EventData buf; TaskId task_id = php_swoole_server_task_pack(zdata, &buf); if (task_id < 0) { RETURN_FALSE; } if (!serv->is_worker()) { buf.info.ext_flags |= SW_TASK_NOREPLY; } else if (zfn && zval_is_true(zfn)) { buf.info.ext_flags |= SW_TASK_CALLBACK; auto cb = sw_callable_create(zfn); if (!cb) { RETURN_FALSE; } server_object->property->task_callbacks[task_id] = cb; } buf.info.ext_flags |= SW_TASK_NONBLOCK; if (serv->task(&buf, (int *) &dst_worker_id)) { RETURN_LONG(task_id); } else { RETURN_FALSE; } } static PHP_METHOD(swoole_server, command) { char *name; size_t l_name; zend_long process_id, process_type; zval *zdata; zend_bool json_decode = true; Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START(4, 5) Z_PARAM_STRING(name, l_name) Z_PARAM_LONG(process_id) Z_PARAM_LONG(process_type) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_BOOL(json_decode) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); smart_str buf = {}; if (php_json_encode(&buf, zdata, 0) == FAILURE || !buf.s) { RETURN_FALSE; } auto co = Coroutine::get_current_safe(); bool donot_yield = false; Server::Command::Callback fn = [co, return_value, json_decode, &donot_yield](Server *serv, const std::string &msg) { if (json_decode) { if (php_json_decode(return_value, msg.c_str(), (int) msg.length(), true, 0) == FAILURE) { RETVAL_FALSE; } } else { ZVAL_STRINGL(return_value, msg.c_str(), msg.length()); } if (co->is_suspending()) { co->resume(); } else { donot_yield = true; } }; if (!serv->command((uint16_t) process_id, (Server::Command::ProcessType) process_type, std::string(name, l_name), std::string(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)), fn)) { smart_str_free(&buf); RETURN_FALSE; } smart_str_free(&buf); if (!donot_yield) { co->yield(); } } static PHP_METHOD(swoole_server, sendMessage) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (!serv->onPipeMessage) { php_swoole_fatal_error(E_WARNING, "onPipeMessage is null, can't use sendMessage"); RETURN_FALSE; } zval *zmessage; zend_long worker_id = -1; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zmessage) Z_PARAM_LONG(worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if ((serv->is_worker() || serv->is_task_worker()) && worker_id == swoole_get_worker_id()) { php_swoole_fatal_error(E_WARNING, "can't send messages to self"); RETURN_FALSE; } if (worker_id < 0 || worker_id >= (long) serv->get_core_worker_num()) { php_swoole_fatal_error(E_WARNING, "worker_id[%d] is invalid", (int) worker_id); RETURN_FALSE; } EventData buf; if (php_swoole_server_task_pack(zmessage, &buf) < 0) { RETURN_FALSE; } RETURN_BOOL(serv->send_pipe_message(worker_id, &buf)); } static PHP_METHOD(swoole_server, finish) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } if (sw_unlikely(serv->task_enable_coroutine)) { php_swoole_fatal_error(E_ERROR, "please use %s->finish instead when task_enable_coroutine is enable", ZSTR_VAL(swoole_server_task_ce->name)); RETURN_FALSE; } zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(php_swoole_server_task_finish(serv, zdata, nullptr)); } static PHP_METHOD(swoole_server_task, finish) { Server *serv = php_swoole_server_task_get_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); DataHead *info = php_swoole_server_task_get_info(ZEND_THIS); RETURN_BOOL(php_swoole_server_task_finish(serv, zdata, (EventData *) info)); } static PHP_METHOD(swoole_server_task, pack) { zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); EventData buf; if (php_swoole_server_task_pack(zdata, &buf) < 0) { RETURN_FALSE; } buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_NOREPLY); RETURN_STRINGL((char *) &buf, buf.size()); } static PHP_METHOD(swoole_server_task, unpack) { zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *buf = reinterpret_cast(Z_STRVAL_P(zdata)); if (!php_swoole_server_task_unpack(return_value, buf)) { RETURN_FALSE; } } static PHP_METHOD(swoole_server, bind) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd = 0; zend_long uid = 0; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(fd) Z_PARAM_LONG(uid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (uid > UINT32_MAX || uid < INT32_MIN) { php_swoole_fatal_error(E_WARNING, "uid can not be greater than %u or less than %d", UINT32_MAX, INT32_MIN); RETURN_FALSE; } Connection *conn = serv->get_connection_verify(fd); if (conn == nullptr) { RETURN_FALSE; } sw_spinlock(&conn->lock); if (conn->uid != 0) { RETVAL_FALSE; } else { conn->uid = (uint32_t) uid; RETVAL_TRUE; } sw_spinlock_release(&conn->lock); } #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_server, getSocket) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); zend_long port = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); const ListenPort *lp = serv->get_port(port); php_socket *socket_object = php_swoole_convert_to_socket(lp->get_fd()); if (!socket_object) { RETURN_FALSE; } SW_ZVAL_SOCKET(return_value, socket_object); zval *zsocket = sw_zval_dup(return_value); Z_TRY_ADDREF_P(zsocket); } #endif static PHP_METHOD(swoole_server, getClientInfo) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; zend_long reactor_id = -1; zend_bool dont_check_connection = false; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_LONG(fd) Z_PARAM_OPTIONAL Z_PARAM_LONG(reactor_id) Z_PARAM_BOOL(dont_check_connection) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(fd); if (!conn) { RETURN_FALSE; } array_init(return_value); if (conn->uid > 0 || serv->dispatch_mode == Server::DISPATCH_UIDMOD) { add_assoc_long(return_value, "uid", conn->uid); } if (conn->worker_id > 0 || serv->dispatch_mode == Server::DISPATCH_CO_CONN_LB) { add_assoc_long(return_value, "worker_id", conn->worker_id); } ListenPort *port = serv->get_port_by_fd(conn->fd); if (port && port->open_websocket_protocol) { add_assoc_long(return_value, "websocket_status", conn->websocket_status); } if (conn->ssl_client_cert && conn->ssl_client_cert_pid == swoole_get_worker_pid()) { add_assoc_stringl(return_value, "ssl_client_cert", conn->ssl_client_cert->str, conn->ssl_client_cert->length); } // server socket Connection *server_socket = serv->get_connection(conn->server_fd); if (server_socket) { add_assoc_long(return_value, "server_port", server_socket->info.get_port()); } add_assoc_long(return_value, "server_fd", conn->server_fd); add_assoc_long(return_value, "socket_fd", conn->fd); add_assoc_long(return_value, "socket_type", conn->socket_type); add_assoc_long(return_value, "remote_port", conn->info.get_port()); add_assoc_string(return_value, "remote_ip", (char *) conn->info.get_addr()); add_assoc_long(return_value, "reactor_id", conn->reactor_id); add_assoc_long(return_value, "connect_time", conn->connect_time); add_assoc_long(return_value, "last_time", (int) conn->last_recv_time); add_assoc_double(return_value, "last_recv_time", conn->last_recv_time); add_assoc_double(return_value, "last_send_time", conn->last_send_time); add_assoc_double(return_value, "last_dispatch_time", conn->last_dispatch_time); add_assoc_long(return_value, "close_errno", conn->close_errno); add_assoc_long(return_value, "recv_queued_bytes", conn->recv_queued_bytes); add_assoc_long(return_value, "send_queued_bytes", conn->send_queued_bytes); } static PHP_METHOD(swoole_server, getClientList) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long start_session_id = 0; zend_long find_count = 10; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(start_session_id) Z_PARAM_LONG(find_count) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); // exceeded the maximum number of searches if (find_count > SW_MAX_FIND_COUNT) { php_swoole_fatal_error(E_WARNING, "swoole connection list max_find_count=%d", SW_MAX_FIND_COUNT); RETURN_FALSE; } // copy it out to avoid being overwritten by other processes int serv_max_fd = serv->get_maxfd(); int start_fd; if (start_session_id == 0) { start_fd = serv->get_minfd(); } else { Connection *conn = serv->get_connection_verify(start_session_id); if (!conn) { RETURN_FALSE; } start_fd = conn->fd; } if ((int) start_fd >= serv_max_fd) { RETURN_FALSE; } array_init(return_value); int fd = start_fd + 1; for (; fd <= serv_max_fd; fd++) { swoole_trace("maxfd=%d, fd=%d, find_count=" ZEND_LONG_FMT ", start_fd=" ZEND_LONG_FMT, serv_max_fd, fd, find_count, start_session_id); Connection *conn = serv->get_connection_for_iterator(fd); if (conn) { SessionId session_id = conn->session_id; if (session_id <= 0) { continue; } add_next_index_long(return_value, session_id); find_count--; } // finish fetch if (find_count <= 0) { break; } } } static PHP_METHOD(swoole_server, sendwait) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd; char *data; size_t length; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(fd) Z_PARAM_STRING(data, length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (length == 0) { php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } if (serv->is_process_mode() || serv->is_task_worker()) { php_swoole_fatal_error(E_WARNING, "can only be used with base mode and must be within worker process"); RETURN_FALSE; } RETURN_BOOL(serv->sendwait(fd, data, length)); } static PHP_METHOD(swoole_server, exists) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long session_id; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(session_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); if (!conn || conn->closed || conn->closing) { RETURN_FALSE; } else { RETURN_TRUE; } } static PHP_METHOD(swoole_server, protect) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long session_id; zend_bool value = true; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(session_id) Z_PARAM_OPTIONAL Z_PARAM_BOOL(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); if (!conn || conn->closed) { RETURN_FALSE; } else { conn->protect = value; RETURN_TRUE; } } static PHP_METHOD(swoole_server, getWorkerId) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (!serv->is_worker() && !serv->is_task_worker()) { RETURN_FALSE; } else { RETURN_LONG(swoole_get_worker_id()); } } static PHP_METHOD(swoole_server, getWorkerStatus) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long worker_id = -1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; Worker *worker = serv->get_worker(worker_id); if (!worker) { RETURN_FALSE; } else { RETURN_LONG(worker->status); } } static PHP_METHOD(swoole_server, getWorkerPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); zend_long worker_id = -1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; Worker *worker = serv->get_worker(worker_id); if (!worker) { RETURN_FALSE; } RETURN_LONG(worker->pid); } static PHP_METHOD(swoole_server, getManagerPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); RETURN_LONG(serv->get_manager_pid()); } static PHP_METHOD(swoole_server, getMasterPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); RETURN_LONG(serv->get_master_pid()); } static PHP_METHOD(swoole_server, shutdown) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); RETURN_BOOL(serv->shutdown()); } static PHP_METHOD(swoole_server, stop) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long worker_id = -1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(worker_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(serv->kill_worker(worker_id)); } // swoole_connection_iterator static PHP_METHOD(swoole_connection_iterator, __construct) { zend_throw_error(nullptr, "please use the Swoole\\Server->connections"); } static PHP_METHOD(swoole_connection_iterator, rewind) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); iterator->index = 0; iterator->current_fd = iterator->serv->get_minfd(); } static PHP_METHOD(swoole_connection_iterator, valid) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); int fd = iterator->current_fd; int max_fd = iterator->serv->get_maxfd(); for (; fd <= max_fd; fd++) { Connection *conn = iterator->serv->get_connection_for_iterator(fd); if (!conn) { continue; } SessionId session_id = conn->session_id; if (session_id <= 0 || (iterator->port && (iterator->port->get_fd() < 0 || conn->server_fd != iterator->port->get_fd()))) { continue; } iterator->session_id = session_id; iterator->current_fd = fd; iterator->index++; RETURN_TRUE; } RETURN_FALSE; } static PHP_METHOD(swoole_connection_iterator, current) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); RETURN_LONG(iterator->session_id); } static PHP_METHOD(swoole_connection_iterator, next) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); iterator->current_fd++; } static PHP_METHOD(swoole_connection_iterator, key) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); RETURN_LONG(iterator->index); } static PHP_METHOD(swoole_connection_iterator, count) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); if (iterator->port) { RETURN_LONG(iterator->port->get_connection_num()); } else { RETURN_LONG(iterator->serv->get_connection_num()); } } static PHP_METHOD(swoole_connection_iterator, offsetExists) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); zval *zserv = php_swoole_server_zval_ptr(iterator->serv); zval *zfd; zval retval; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zfd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); sw_zend_call_method_with_1_params(zserv, swoole_server_ce, nullptr, "exists", &retval, zfd); RETVAL_BOOL(Z_BVAL_P(&retval)); } static PHP_METHOD(swoole_connection_iterator, offsetGet) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); zval *zserv = php_swoole_server_zval_ptr(iterator->serv); zval *zfd; zval retval; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zfd) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); sw_zend_call_method_with_1_params(zserv, swoole_server_ce, nullptr, "getClientInfo", &retval, zfd); RETVAL_ZVAL(&retval, 0, 0); } static PHP_METHOD(swoole_connection_iterator, offsetSet) {} static PHP_METHOD(swoole_connection_iterator, offsetUnset) {} static PHP_METHOD(swoole_connection_iterator, __destruct) {} ================================================ FILE: ext-src/swoole_server_port.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_server.h" #include "php_swoole_call_stack.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_server_port_arginfo.h" END_EXTERN_C() using namespace swoole; struct ServerPortEvent { enum php_swoole_server_port_callback_type type; std::string name; ServerPortEvent(enum php_swoole_server_port_callback_type type, std::string &&name) : type(type), name(name) {} }; // clang-format off static std::unordered_map server_port_event_map({ { "connect", ServerPortEvent(SW_SERVER_CB_onConnect, "Connect") }, { "receive", ServerPortEvent(SW_SERVER_CB_onReceive, "Receive") }, { "close", ServerPortEvent(SW_SERVER_CB_onClose, "Close") }, { "packet", ServerPortEvent(SW_SERVER_CB_onPacket, "Packet") }, { "bufferfull", ServerPortEvent(SW_SERVER_CB_onBufferFull, "BufferFull") }, { "bufferempty", ServerPortEvent(SW_SERVER_CB_onBufferEmpty, "BufferEmpty") }, { "request", ServerPortEvent(SW_SERVER_CB_onRequest, "Request") }, { "handshake", ServerPortEvent(SW_SERVER_CB_onHandshake, "Handshake") }, { "beforehandshakeresponse", ServerPortEvent(SW_SERVER_CB_onBeforeHandshakeResponse, "BeforeHandshakeResponse") }, { "open", ServerPortEvent(SW_SERVER_CB_onOpen, "Open") }, { "message", ServerPortEvent(SW_SERVER_CB_onMessage, "Message") }, { "disconnect", ServerPortEvent(SW_SERVER_CB_onDisconnect, "Disconnect") }, }); // clang-format on zend_class_entry *swoole_server_port_ce; static zend_object_handlers swoole_server_port_handlers; struct ServerPortObject { ListenPort *port; ServerPortProperty property; zend_object std; }; static sw_inline ServerPortObject *php_swoole_server_port_fetch_object(zend_object *obj) { return (ServerPortObject *) ((char *) obj - swoole_server_port_handlers.offset); } static sw_inline ListenPort *php_swoole_server_port_get_ptr(zval *zobject) { return php_swoole_server_port_fetch_object(Z_OBJ_P(zobject))->port; } ListenPort *php_swoole_server_port_get_and_check_ptr(zval *zobject) { ListenPort *port = php_swoole_server_port_get_ptr(zobject); if (UNEXPECTED(!port)) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return port; } void php_swoole_server_port_set_ptr(zval *zobject, ListenPort *port) { php_swoole_server_port_fetch_object(Z_OBJ_P(zobject))->port = port; } ServerPortProperty *php_swoole_server_port_get_property(zval *zobject) { return &php_swoole_server_port_fetch_object(Z_OBJ_P(zobject))->property; } static ServerPortProperty *php_swoole_server_port_get_and_check_property(zval *zobject) { ServerPortProperty *property = php_swoole_server_port_get_property(zobject); if (UNEXPECTED(!property->serv)) { php_swoole_fatal_error(E_ERROR, "Invalid instance of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); } return property; } // Dereference from server object void php_swoole_server_port_deref(zend_object *object) { ServerPortObject *server_port = php_swoole_server_port_fetch_object(object); ServerPortProperty *property = &server_port->property; if (property->serv) { for (auto &callback : property->callbacks) { if (callback) { sw_callable_free(callback); callback = nullptr; } } property->serv = nullptr; } ListenPort *port = server_port->port; if (port) { if (port->protocol.private_data_1) { sw_callable_free(port->protocol.private_data_1); port->protocol.private_data_1 = nullptr; } server_port->port = nullptr; } } static void php_swoole_server_port_free_object(zend_object *object) { php_swoole_server_port_deref(object); zend_object_std_dtor(object); } static zend_object *php_swoole_server_port_create_object(zend_class_entry *ce) { auto *server_port = static_cast(zend_object_alloc(sizeof(ServerPortObject), ce)); zend_object_std_init(&server_port->std, ce); object_properties_init(&server_port->std, ce); server_port->std.handlers = &swoole_server_port_handlers; return &server_port->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_server_port, __construct); static PHP_METHOD(swoole_server_port, __destruct); static PHP_METHOD(swoole_server_port, on); static PHP_METHOD(swoole_server_port, set); static PHP_METHOD(swoole_server_port, getCallback); #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_server_port, getSocket); #endif SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_server_port_methods[] = { PHP_ME(swoole_server_port, __construct, arginfo_class_Swoole_Server_Port___construct, ZEND_ACC_PRIVATE) PHP_ME(swoole_server_port, __destruct, arginfo_class_Swoole_Server_Port___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_server_port, set, arginfo_class_Swoole_Server_Port_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_server_port, on, arginfo_class_Swoole_Server_Port_on, ZEND_ACC_PUBLIC) PHP_ME(swoole_server_port, getCallback, arginfo_class_Swoole_Server_Port_getCallback, ZEND_ACC_PUBLIC) #ifdef SWOOLE_SOCKETS_SUPPORT PHP_ME(swoole_server_port, getSocket, arginfo_class_Swoole_Server_Port_getSocket, ZEND_ACC_PUBLIC) #endif PHP_FE_END }; // clang-format on void php_swoole_server_port_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_server_port, "Swoole\\Server\\Port", nullptr, swoole_server_port_methods); #ifndef SW_THREAD SW_SET_CLASS_NOT_SERIALIZABLE(swoole_server_port); #endif SW_SET_CLASS_CLONEABLE(swoole_server_port, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_server_port, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_server_port, php_swoole_server_port_create_object, php_swoole_server_port_free_object, ServerPortObject, std); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onConnect"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onReceive"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onClose"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onPacket"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBufferFull"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBufferEmpty"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onRequest"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onHandshake"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onOpen"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onMessage"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onDisconnect"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBeforeHandshakeResponse"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_port_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_port_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_port_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_server_port_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("connections"), ZEND_ACC_PUBLIC); } /** * [Master/Worker] */ static ssize_t php_swoole_server_length_func(const Protocol *protocol, network::Socket *conn, PacketLength *pl) { auto *cb = static_cast(protocol->private_data_1); zval zdata; zval retval; ssize_t ret = -1; ZVAL_STRINGL(&zdata, pl->buf, pl->buf_size); HOOK_PHP_CALL_STACK(auto call_result = sw_zend_call_function_ex(nullptr, cb->ptr(), 1, &zdata, &retval);); if (UNEXPECTED(call_result) != SUCCESS) { php_swoole_fatal_error(E_WARNING, "length function handler error"); } else { ret = zval_get_long(&retval); zval_ptr_dtor(&retval); } zval_ptr_dtor(&zdata); /* the exception should only be thrown after unlocked */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } return ret; } static PHP_METHOD(swoole_server_port, __construct) { zend_throw_error(nullptr, "please use the Swoole\\Server->listen method"); RETURN_FALSE; } static PHP_METHOD(swoole_server_port, __destruct) {} static bool php_swoole_server_set_ssl_option(zend_array *vht, SSLContext *ctx) { zval *ztmp; if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { zend::String str_v(ztmp); if (!ctx->set_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl cert file[%s] not found", str_v.val()); return false; } } if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { zend::String str_v(ztmp); if (!ctx->set_key_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl key file[%s] not found", str_v.val()); return false; } } return true; } static PHP_METHOD(swoole_server_port, set) { zval *zset = nullptr; HashTable *vht; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); ListenPort *port = php_swoole_server_port_get_and_check_ptr(ZEND_THIS); ServerPortProperty *property = php_swoole_server_port_get_and_check_property(ZEND_THIS); if (port == nullptr || property == nullptr) { php_swoole_fatal_error(E_ERROR, "please use the swoole_server->listen method"); return; } // backlog if (php_swoole_array_get_value(vht, "backlog", ztmp)) { zend_long v = zval_get_long(ztmp); port->backlog = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->socket_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->socket_buffer_size <= 0) { port->socket_buffer_size = INT_MAX; } } /** * !!! Don't set this option, for tests only. */ if (php_swoole_array_get_value(vht, "kernel_socket_recv_buffer_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->kernel_socket_recv_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->kernel_socket_recv_buffer_size <= 0) { port->kernel_socket_recv_buffer_size = INT_MAX; } } /** * !!! Don't set this option, for tests only. */ if (php_swoole_array_get_value(vht, "kernel_socket_send_buffer_size", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->kernel_socket_send_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->kernel_socket_send_buffer_size <= 0) { port->kernel_socket_send_buffer_size = INT_MAX; } } // heartbeat idle time if (php_swoole_array_get_value(vht, "heartbeat_idle_time", ztmp)) { zend_long v = zval_get_long(ztmp); port->heartbeat_idle_time = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } if (php_swoole_array_get_value(vht, "buffer_high_watermark", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->buffer_high_watermark = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "buffer_low_watermark", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->buffer_low_watermark = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } // server: tcp_nodelay if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { port->open_tcp_nodelay = zval_is_true(ztmp); } else { port->open_tcp_nodelay = true; } // tcp_defer_accept if (php_swoole_array_get_value(vht, "tcp_defer_accept", ztmp)) { zend_long v = zval_get_long(ztmp); port->tcp_defer_accept = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); } // tcp_keepalive if (php_swoole_array_get_value(vht, "open_tcp_keepalive", ztmp)) { port->open_tcp_keepalive = zval_is_true(ztmp); } // buffer: eof check if (php_swoole_array_get_value(vht, "open_eof_check", ztmp)) { port->open_eof_check = zval_is_true(ztmp); } // buffer: split package with eof if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { port->protocol.split_by_eof = zval_is_true(ztmp); if (port->protocol.split_by_eof) { port->open_eof_check = true; } } // package eof if (php_swoole_array_get_value(vht, "package_eof", ztmp)) { zend::String str_v(ztmp); port->protocol.package_eof_len = str_v.len(); if (port->protocol.package_eof_len == 0) { php_swoole_fatal_error(E_ERROR, "package_eof cannot be an empty string"); RETURN_FALSE; } else if (port->protocol.package_eof_len > SW_DATA_EOF_MAXLEN) { php_swoole_fatal_error(E_ERROR, "package_eof max length is %d", SW_DATA_EOF_MAXLEN); RETURN_FALSE; } memcpy(port->protocol.package_eof, str_v.val(), str_v.len()); } // http_protocol if (php_swoole_array_get_value(vht, "open_http_protocol", ztmp)) { port->open_http_protocol = zval_is_true(ztmp); } // websocket protocol if (php_swoole_array_get_value(vht, "open_websocket_protocol", ztmp)) { port->open_websocket_protocol = zval_is_true(ztmp); if (port->open_websocket_protocol) { port->open_http_protocol = true; } } // websocket settings php_swoole_server_set_websocket_option(port, vht); // http2 protocol if (php_swoole_array_get_value(vht, "open_http2_protocol", ztmp)) { port->open_http2_protocol = zval_is_true(ztmp); if (port->open_http2_protocol) { port->open_http_protocol = true; } } // buffer: mqtt protocol if (php_swoole_array_get_value(vht, "open_mqtt_protocol", ztmp)) { port->open_mqtt_protocol = zval_is_true(ztmp); } // redis protocol if (php_swoole_array_get_value(vht, "open_redis_protocol", ztmp)) { port->open_redis_protocol = zval_get_long(ztmp); } if (php_swoole_array_get_value(vht, "max_idle_time", ztmp)) { double v = zval_get_double(ztmp); port->max_idle_time = SW_MAX(v, SW_TIMER_MIN_SEC); } // tcp_keepidle if (php_swoole_array_get_value(vht, "tcp_keepidle", ztmp)) { zend_long v = zval_get_long(ztmp); port->tcp_keepidle = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); } // tcp_keepinterval if (php_swoole_array_get_value(vht, "tcp_keepinterval", ztmp)) { zend_long v = zval_get_long(ztmp); port->tcp_keepinterval = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); } // tcp_keepcount if (php_swoole_array_get_value(vht, "tcp_keepcount", ztmp)) { zend_long v = zval_get_long(ztmp); port->tcp_keepcount = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); } // tcp_user_timeout if (php_swoole_array_get_value(vht, "tcp_user_timeout", ztmp)) { zend_long v = zval_get_long(ztmp); port->tcp_user_timeout = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); } // tcp_fastopen if (php_swoole_array_get_value(vht, "tcp_fastopen", ztmp)) { port->tcp_fastopen = zval_is_true(ztmp); } // open length check if (php_swoole_array_get_value(vht, "open_length_check", ztmp)) { port->open_length_check = zval_is_true(ztmp); } // package length size if (php_swoole_array_get_value(vht, "package_length_type", ztmp)) { zend::String str_v(ztmp); port->protocol.package_length_type = str_v.val()[0]; port->protocol.package_length_size = swoole_type_size(port->protocol.package_length_type); if (port->protocol.package_length_size == 0) { php_swoole_fatal_error(E_ERROR, "unknown package_length_type, see pack(). Link: https://php.net/pack"); RETURN_FALSE; } } // package length offset if (php_swoole_array_get_value(vht, "package_length_offset", ztmp)) { zend_long v = zval_get_long(ztmp); port->protocol.package_length_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); if (port->protocol.package_length_offset > SW_IPC_BUFFER_SIZE) { php_swoole_fatal_error(E_ERROR, "'package_length_offset' value is too large"); } } // package body start if (php_swoole_array_get_value(vht, "package_body_offset", ztmp) || php_swoole_array_get_value(vht, "package_body_start", ztmp)) { zend_long v = zval_get_long(ztmp); port->protocol.package_body_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); if (port->protocol.package_body_offset > SW_IPC_BUFFER_SIZE) { php_swoole_fatal_error(E_ERROR, "'package_body_offset' value is too large"); } } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { auto cb = sw_callable_create(ztmp); if (cb) { port->protocol.get_package_length = php_swoole_server_length_func; if (port->protocol.private_data_1) { sw_callable_free(port->protocol.private_data_1); } port->protocol.private_data_1 = cb; port->protocol.package_length_size = 0; port->protocol.package_length_type = '\0'; port->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; property->serv->single_thread = true; } } /** * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); port->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (port->ssl) { if (!php_swoole_server_set_ssl_option(vht, port->get_ssl_context())) { RETURN_FALSE; } if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { port->set_ssl_disable_compress(!zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { port->set_ssl_protocols(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { port->set_ssl_verify_peer(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { port->set_ssl_allow_self_signed(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_client_cert_file", ztmp)) { zend::String str_v(ztmp); if (!port->set_ssl_client_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl_client_cert_file[%s] not found", str_v.val()); return; } } if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { zend::String str_v(ztmp); port->set_ssl_cafile(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { zend::String str_v(ztmp); port->set_ssl_capath(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { zend_long v = zval_get_long(ztmp); port->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } if (php_swoole_array_get_value(vht, "ssl_prefer_server_ciphers", ztmp)) { port->set_ssl_prefer_server_ciphers(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { port->set_ssl_ciphers(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_ecdh_curve", ztmp)) { port->set_ssl_ecdh_curve(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_dhparam", ztmp)) { port->set_ssl_dhparam(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_sni_certs", ztmp)) { if (Z_TYPE_P(ztmp) != IS_ARRAY) { php_swoole_fatal_error(E_WARNING, "ssl_sni_certs requires an array mapping host names to cert paths"); RETURN_FALSE; } zval *current; zend_string *key; zend_ulong key_index; ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(ztmp), key_index, key, current) { (void) key_index; if (!key) { php_swoole_fatal_error(E_WARNING, "ssl_sni_certs array requires string host name keys"); RETURN_FALSE; } if (Z_TYPE_P(current) != IS_ARRAY) { php_swoole_fatal_error(E_WARNING, "invalid SNI_cert setting"); RETURN_FALSE; } auto context = port->dup_ssl_context(); if (!php_swoole_server_set_ssl_option(Z_ARRVAL_P(current), context.get())) { RETURN_FALSE; } if (!port->ssl_add_sni_cert(std::string(key->val, key->len), context)) { php_swoole_fatal_error(E_ERROR, "ssl_add_sni_cert() failed"); RETURN_FALSE; } } ZEND_HASH_FOREACH_END(); } if (!port->get_ssl_cert_file().empty() || !port->has_sni_contexts()) { if (!port->ssl_init()) { php_swoole_fatal_error(E_ERROR, "ssl_init() failed"); RETURN_FALSE; } } } if (SWOOLE_G(enable_library)) { zval params[1] = { *zset, }; zend::function::call(R"(\Swoole\Server\Helper::checkOptions)", 1, params); } zval *zsetting = sw_zend_read_and_convert_property_array(swoole_server_port_ce, ZEND_THIS, ZEND_STRL("setting"), 0); php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); property->zsetting = zsetting; } static PHP_METHOD(swoole_server_port, on) { char *name = nullptr; size_t len; zval *cb; ServerPortProperty *property = php_swoole_server_port_get_and_check_property(ZEND_THIS); Server *serv = property->serv; if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "can't register event callback function after server started"); RETURN_FALSE; } ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(name, len) Z_PARAM_ZVAL(cb) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); bool found = false; for (auto &i : server_port_event_map) { if (!swoole_strcaseeq(name, len, i.first.c_str(), i.first.length())) { continue; } found = true; int index = i.second.type; std::string property_name = std::string("on") + i.second.name; zend_update_property( swoole_server_port_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), cb); if (property->callbacks[index]) { sw_callable_free(property->callbacks[index]); } auto fci_cache = sw_callable_create(cb); if (!fci_cache) { RETURN_FALSE; } property->callbacks[index] = fci_cache; if (index == SW_SERVER_CB_onConnect && !serv->onConnect) { serv->onConnect = php_swoole_server_onConnect; } else if (index == SW_SERVER_CB_onPacket && !serv->onPacket) { serv->onPacket = php_swoole_server_onPacket; } else if (index == SW_SERVER_CB_onClose && !serv->onClose) { serv->onClose = php_swoole_server_onClose; } else if (index == SW_SERVER_CB_onBufferFull && !serv->onBufferFull) { serv->onBufferFull = php_swoole_server_onBufferFull; } else if (index == SW_SERVER_CB_onBufferEmpty && !serv->onBufferEmpty) { serv->onBufferEmpty = php_swoole_server_onBufferEmpty; } break; } if (!found) { php_swoole_error(E_WARNING, "unknown event types[%s]", name); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_server_port, getCallback) { zval *name; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(name) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend::String _event_name_ori(name); zend::String _event_name_tolower(zend_string_tolower(_event_name_ori.get()), false); auto i = server_port_event_map.find(_event_name_tolower.to_std_string()); if (i != server_port_event_map.end()) { std::string property_name = "on" + i->second.name; zval rv, *property = zend_read_property(swoole_server_port_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), true, &rv); if (!ZVAL_IS_NULL(property)) { RETURN_ZVAL(property, 1, 0); } } RETURN_NULL(); } #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_server_port, getSocket) { ListenPort *port = php_swoole_server_port_get_and_check_ptr(ZEND_THIS); php_socket *socket_object = php_swoole_convert_to_socket(port->get_fd()); if (!socket_object) { RETURN_FALSE; } SW_ZVAL_SOCKET(return_value, socket_object); zval *zsocket = sw_zval_dup(return_value); Z_TRY_ADDREF_P(zsocket); } #endif ================================================ FILE: ext-src/swoole_socket_coro.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_thread.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_util.h" #include "swoole_protocol.h" #include "swoole_mqtt.h" #include "thirdparty/php/sockets/php_sockets_cxx.h" #include BEGIN_EXTERN_C() #include "stubs/php_swoole_socket_coro_arginfo.h" END_EXTERN_C() using swoole::HttpProxy; using swoole::PacketLength; using swoole::Protocol; using swoole::Socks5Proxy; using swoole::network::Address; zend_class_entry *swoole_socket_coro_ce; static zend_object_handlers swoole_socket_coro_handlers; static zend_class_entry *swoole_socket_coro_exception_ce; static zend_object_handlers swoole_socket_coro_exception_handlers; struct SocketObject { SocketImpl *socket; zval zstream; bool reference; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_socket_coro, __construct); static PHP_METHOD(swoole_socket_coro, bind); static PHP_METHOD(swoole_socket_coro, listen); static PHP_METHOD(swoole_socket_coro, accept); static PHP_METHOD(swoole_socket_coro, connect); static PHP_METHOD(swoole_socket_coro, checkLiveness); static PHP_METHOD(swoole_socket_coro, getBoundCid); static PHP_METHOD(swoole_socket_coro, peek); static PHP_METHOD(swoole_socket_coro, recv); static PHP_METHOD(swoole_socket_coro, send); static PHP_METHOD(swoole_socket_coro, readVector); static PHP_METHOD(swoole_socket_coro, readVectorAll); static PHP_METHOD(swoole_socket_coro, writeVector); static PHP_METHOD(swoole_socket_coro, writeVectorAll); static PHP_METHOD(swoole_socket_coro, sendFile); static PHP_METHOD(swoole_socket_coro, recvAll); static PHP_METHOD(swoole_socket_coro, sendAll); static PHP_METHOD(swoole_socket_coro, recvPacket); static PHP_METHOD(swoole_socket_coro, recvLine); static PHP_METHOD(swoole_socket_coro, recvWithBuffer); static PHP_METHOD(swoole_socket_coro, recvfrom); static PHP_METHOD(swoole_socket_coro, sendto); static PHP_METHOD(swoole_socket_coro, getOption); static PHP_METHOD(swoole_socket_coro, setOption); static PHP_METHOD(swoole_socket_coro, setProtocol); static PHP_METHOD(swoole_socket_coro, sslHandshake); static PHP_METHOD(swoole_socket_coro, shutdown); static PHP_METHOD(swoole_socket_coro, close); static PHP_METHOD(swoole_socket_coro, cancel); static PHP_METHOD(swoole_socket_coro, getsockname); static PHP_METHOD(swoole_socket_coro, getpeername); static PHP_METHOD(swoole_socket_coro, isClosed); static PHP_METHOD(swoole_socket_coro, import); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_socket_coro_methods[] = { PHP_ME(swoole_socket_coro, __construct, arginfo_class_Swoole_Coroutine_Socket___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, bind, arginfo_class_Swoole_Coroutine_Socket_bind, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, listen, arginfo_class_Swoole_Coroutine_Socket_listen, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, accept, arginfo_class_Swoole_Coroutine_Socket_accept, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, connect, arginfo_class_Swoole_Coroutine_Socket_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, checkLiveness, arginfo_class_Swoole_Coroutine_Socket_checkLiveness, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, getBoundCid, arginfo_class_Swoole_Coroutine_Socket_getBoundCid, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, peek, arginfo_class_Swoole_Coroutine_Socket_peek, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recv, arginfo_class_Swoole_Coroutine_Socket_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvAll, arginfo_class_Swoole_Coroutine_Socket_recvAll, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvLine, arginfo_class_Swoole_Coroutine_Socket_recvLine, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvWithBuffer, arginfo_class_Swoole_Coroutine_Socket_recvWithBuffer, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvPacket, arginfo_class_Swoole_Coroutine_Socket_recvPacket, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, send, arginfo_class_Swoole_Coroutine_Socket_send, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, readVector, arginfo_class_Swoole_Coroutine_Socket_readVector, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, readVectorAll, arginfo_class_Swoole_Coroutine_Socket_readVectorAll, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, writeVector, arginfo_class_Swoole_Coroutine_Socket_writeVector, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, writeVectorAll, arginfo_class_Swoole_Coroutine_Socket_writeVectorAll, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, sendFile, arginfo_class_Swoole_Coroutine_Socket_sendFile, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, sendAll, arginfo_class_Swoole_Coroutine_Socket_sendAll, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvfrom, arginfo_class_Swoole_Coroutine_Socket_recvfrom, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, sendto, arginfo_class_Swoole_Coroutine_Socket_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, getOption, arginfo_class_Swoole_Coroutine_Socket_getOption, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, setProtocol, arginfo_class_Swoole_Coroutine_Socket_setProtocol, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, setOption, arginfo_class_Swoole_Coroutine_Socket_setOption, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, sslHandshake, arginfo_class_Swoole_Coroutine_Socket_sslHandshake, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, shutdown, arginfo_class_Swoole_Coroutine_Socket_shutdown, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, cancel, arginfo_class_Swoole_Coroutine_Socket_cancel, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, close, arginfo_class_Swoole_Coroutine_Socket_close, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, getpeername, arginfo_class_Swoole_Coroutine_Socket_getpeername, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, getsockname, arginfo_class_Swoole_Coroutine_Socket_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, isClosed, arginfo_class_Swoole_Coroutine_Socket_isClosed, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, import, arginfo_class_Swoole_Coroutine_Socket_import, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on #define swoole_get_socket_coro(_sock, _zobject) \ SocketObject *_sock = socket_coro_fetch_object(Z_OBJ_P(_zobject)); \ if (UNEXPECTED(!sock->socket)) { \ swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); \ } \ if (UNEXPECTED(_sock->socket->is_closed())) { \ zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(_zobject), ZEND_STRL("errCode"), EBADF); \ zend_update_property_string( \ swoole_socket_coro_ce, SW_Z8_OBJ_P(_zobject), ZEND_STRL("errMsg"), strerror(EBADF)); \ RETURN_FALSE; \ } static sw_inline SocketObject *socket_coro_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_socket_coro_handlers.offset); } /** * cannot execute close in the destructor, it may be shutting down, * executing close will try to resume other coroutines. */ static void socket_coro_free_object(zend_object *object) { auto *sock = socket_coro_fetch_object(object); if (!sock->reference && sock->socket) { if (!Z_ISUNDEF(sock->zstream)) { sock->socket->move_fd(); zval_ptr_dtor(&sock->zstream); } delete sock->socket; } zend_object_std_dtor(&sock->std); } static zend_object *socket_coro_create_object(zend_class_entry *ce) { auto *sock = static_cast(zend_object_alloc(sizeof(SocketObject), ce)); zend_object_std_init(&sock->std, ce); /* Even if you don't use properties yourself you should still call object_properties_init(), * because extending classes may use properties. (Generally a lot of the stuff you will do is * for the sake of not breaking extending classes). */ object_properties_init(&sock->std, ce); sock->std.handlers = &swoole_socket_coro_handlers; return &sock->std; } static void socket_coro_register_constants(int module_number) { REGISTER_LONG_CONSTANT("AF_UNIX", AF_UNIX, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AF_INET", AF_INET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AF_INET6", AF_INET6, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_STREAM", SOCK_STREAM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_DGRAM", SOCK_DGRAM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_RAW", SOCK_RAW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_SEQPACKET", SOCK_SEQPACKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_RDM", SOCK_RDM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_OOB", MSG_OOB, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_WAITALL", MSG_WAITALL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_CTRUNC", MSG_CTRUNC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_TRUNC", MSG_TRUNC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); #ifdef MSG_EOR REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_EOF REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_CONFIRM REGISTER_LONG_CONSTANT("MSG_CONFIRM", MSG_CONFIRM, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_ERRQUEUE REGISTER_LONG_CONSTANT("MSG_ERRQUEUE", MSG_ERRQUEUE, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_NOSIGNAL REGISTER_LONG_CONSTANT("MSG_NOSIGNAL", MSG_NOSIGNAL, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_DONTWAIT REGISTER_LONG_CONSTANT("MSG_DONTWAIT", MSG_DONTWAIT, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_MORE REGISTER_LONG_CONSTANT("MSG_MORE", MSG_MORE, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_WAITFORONE REGISTER_LONG_CONSTANT("MSG_WAITFORONE", MSG_WAITFORONE, CONST_CS | CONST_PERSISTENT); #endif #ifdef MSG_CMSG_CLOEXEC REGISTER_LONG_CONSTANT("MSG_CMSG_CLOEXEC", MSG_CMSG_CLOEXEC, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SO_DEBUG", SO_DEBUG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_REUSEADDR", SO_REUSEADDR, CONST_CS | CONST_PERSISTENT); #ifdef SO_REUSEPORT REGISTER_LONG_CONSTANT("SO_REUSEPORT", SO_REUSEPORT, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SO_KEEPALIVE", SO_KEEPALIVE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_DONTROUTE", SO_DONTROUTE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_LINGER", SO_LINGER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_BROADCAST", SO_BROADCAST, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_OOBINLINE", SO_OOBINLINE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_SNDBUF", SO_SNDBUF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_RCVBUF", SO_RCVBUF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_SNDLOWAT", SO_SNDLOWAT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_RCVLOWAT", SO_RCVLOWAT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_SNDTIMEO", SO_SNDTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_RCVTIMEO", SO_RCVTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_TYPE", SO_TYPE, CONST_CS | CONST_PERSISTENT); #ifdef SO_FAMILY REGISTER_LONG_CONSTANT("SO_FAMILY", SO_FAMILY, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SO_ERROR", SO_ERROR, CONST_CS | CONST_PERSISTENT); #ifdef SO_BINDTODEVICE REGISTER_LONG_CONSTANT("SO_BINDTODEVICE", SO_BINDTODEVICE, CONST_CS | CONST_PERSISTENT); #endif #ifdef SO_USER_COOKIE REGISTER_LONG_CONSTANT("SO_LABEL", SO_LABEL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_PEERLABEL", SO_PEERLABEL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_LISTENQLIMIT", SO_LISTENQLIMIT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_LISTENQLEN", SO_LISTENQLEN, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_USER_COOKIE", SO_USER_COOKIE, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SOL_SOCKET", SOL_SOCKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOMAXCONN", SOMAXCONN, CONST_CS | CONST_PERSISTENT); #ifdef TCP_NODELAY REGISTER_LONG_CONSTANT("TCP_NODELAY", TCP_NODELAY, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("MCAST_JOIN_GROUP", PHP_MCAST_JOIN_GROUP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_LEAVE_GROUP", PHP_MCAST_LEAVE_GROUP, CONST_CS | CONST_PERSISTENT); #ifdef HAS_MCAST_EXT REGISTER_LONG_CONSTANT("MCAST_BLOCK_SOURCE", PHP_MCAST_BLOCK_SOURCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_UNBLOCK_SOURCE", PHP_MCAST_UNBLOCK_SOURCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_JOIN_SOURCE_GROUP", PHP_MCAST_JOIN_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_LEAVE_SOURCE_GROUP", PHP_MCAST_LEAVE_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("IP_MULTICAST_IF", IP_MULTICAST_IF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IP_MULTICAST_TTL", IP_MULTICAST_TTL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IP_MULTICAST_LOOP", IP_MULTICAST_LOOP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_MULTICAST_IF", IPV6_MULTICAST_IF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_MULTICAST_HOPS", IPV6_MULTICAST_HOPS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_MULTICAST_LOOP", IPV6_MULTICAST_LOOP, CONST_CS | CONST_PERSISTENT); #ifdef IPV6_V6ONLY REGISTER_LONG_CONSTANT("IPV6_V6ONLY", IPV6_V6ONLY, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPERM /* Operation not permitted */ REGISTER_LONG_CONSTANT("SOCKET_EPERM", EPERM, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOENT /* No such file or directory */ REGISTER_LONG_CONSTANT("SOCKET_ENOENT", ENOENT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EINTR /* Interrupted system call */ REGISTER_LONG_CONSTANT("SOCKET_EINTR", EINTR, CONST_CS | CONST_PERSISTENT); #endif #ifdef EIO /* I/O error */ REGISTER_LONG_CONSTANT("SOCKET_EIO", EIO, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENXIO /* No such device or address */ REGISTER_LONG_CONSTANT("SOCKET_ENXIO", ENXIO, CONST_CS | CONST_PERSISTENT); #endif #ifdef E2BIG /* Arg list too long */ REGISTER_LONG_CONSTANT("SOCKET_E2BIG", E2BIG, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADF /* Bad file number */ REGISTER_LONG_CONSTANT("SOCKET_EBADF", EBADF, CONST_CS | CONST_PERSISTENT); #endif #ifdef EAGAIN /* Try again */ REGISTER_LONG_CONSTANT("SOCKET_EAGAIN", EAGAIN, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOMEM /* Out of memory */ REGISTER_LONG_CONSTANT("SOCKET_ENOMEM", ENOMEM, CONST_CS | CONST_PERSISTENT); #endif #ifdef EACCES /* Permission denied */ REGISTER_LONG_CONSTANT("SOCKET_EACCES", EACCES, CONST_CS | CONST_PERSISTENT); #endif #ifdef EFAULT /* Bad address */ REGISTER_LONG_CONSTANT("SOCKET_EFAULT", EFAULT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTBLK /* Block device required */ REGISTER_LONG_CONSTANT("SOCKET_ENOTBLK", ENOTBLK, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBUSY /* Device or resource busy */ REGISTER_LONG_CONSTANT("SOCKET_EBUSY", EBUSY, CONST_CS | CONST_PERSISTENT); #endif #ifdef EEXIST /* File exists */ REGISTER_LONG_CONSTANT("SOCKET_EEXIST", EEXIST, CONST_CS | CONST_PERSISTENT); #endif #ifdef EXDEV /* Cross-device link */ REGISTER_LONG_CONSTANT("SOCKET_EXDEV", EXDEV, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENODEV /* No such device */ REGISTER_LONG_CONSTANT("SOCKET_ENODEV", ENODEV, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTDIR /* Not a directory */ REGISTER_LONG_CONSTANT("SOCKET_ENOTDIR", ENOTDIR, CONST_CS | CONST_PERSISTENT); #endif #ifdef EISDIR /* Is a directory */ REGISTER_LONG_CONSTANT("SOCKET_EISDIR", EISDIR, CONST_CS | CONST_PERSISTENT); #endif #ifdef EINVAL /* Invalid argument */ REGISTER_LONG_CONSTANT("SOCKET_EINVAL", EINVAL, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENFILE /* File table overflow */ REGISTER_LONG_CONSTANT("SOCKET_ENFILE", ENFILE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EMFILE /* Too many open files */ REGISTER_LONG_CONSTANT("SOCKET_EMFILE", EMFILE, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTTY /* Not a typewriter */ REGISTER_LONG_CONSTANT("SOCKET_ENOTTY", ENOTTY, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOSPC /* No space left on device */ REGISTER_LONG_CONSTANT("SOCKET_ENOSPC", ENOSPC, CONST_CS | CONST_PERSISTENT); #endif #ifdef ESPIPE /* Illegal seek */ REGISTER_LONG_CONSTANT("SOCKET_ESPIPE", ESPIPE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EROFS /* Read-only file system */ REGISTER_LONG_CONSTANT("SOCKET_EROFS", EROFS, CONST_CS | CONST_PERSISTENT); #endif #ifdef EMLINK /* Too many links */ REGISTER_LONG_CONSTANT("SOCKET_EMLINK", EMLINK, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPIPE /* Broken pipe */ REGISTER_LONG_CONSTANT("SOCKET_EPIPE", EPIPE, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENAMETOOLONG /* File name too long */ REGISTER_LONG_CONSTANT("SOCKET_ENAMETOOLONG", ENAMETOOLONG, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOLCK /* No record locks available */ REGISTER_LONG_CONSTANT("SOCKET_ENOLCK", ENOLCK, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOSYS /* Function not implemented */ REGISTER_LONG_CONSTANT("SOCKET_ENOSYS", ENOSYS, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTEMPTY /* Directory not empty */ REGISTER_LONG_CONSTANT("SOCKET_ENOTEMPTY", ENOTEMPTY, CONST_CS | CONST_PERSISTENT); #endif #ifdef ELOOP /* Too many symbolic links encountered */ REGISTER_LONG_CONSTANT("SOCKET_ELOOP", ELOOP, CONST_CS | CONST_PERSISTENT); #endif #ifdef EWOULDBLOCK /* Operation would block */ REGISTER_LONG_CONSTANT("SOCKET_EWOULDBLOCK", EWOULDBLOCK, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOMSG /* No message of desired type */ REGISTER_LONG_CONSTANT("SOCKET_ENOMSG", ENOMSG, CONST_CS | CONST_PERSISTENT); #endif #ifdef EIDRM /* Identifier removed */ REGISTER_LONG_CONSTANT("SOCKET_EIDRM", EIDRM, CONST_CS | CONST_PERSISTENT); #endif #ifdef ECHRNG /* Channel number out of range */ REGISTER_LONG_CONSTANT("SOCKET_ECHRNG", ECHRNG, CONST_CS | CONST_PERSISTENT); #endif #ifdef EL2NSYNC /* Level 2 not synchronized */ REGISTER_LONG_CONSTANT("SOCKET_EL2NSYNC", EL2NSYNC, CONST_CS | CONST_PERSISTENT); #endif #ifdef EL3HLT /* Level 3 halted */ REGISTER_LONG_CONSTANT("SOCKET_EL3HLT", EL3HLT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EL3RST /* Level 3 reset */ REGISTER_LONG_CONSTANT("SOCKET_EL3RST", EL3RST, CONST_CS | CONST_PERSISTENT); #endif #ifdef ELNRNG /* Link number out of range */ REGISTER_LONG_CONSTANT("SOCKET_ELNRNG", ELNRNG, CONST_CS | CONST_PERSISTENT); #endif #ifdef EUNATCH /* Protocol driver not attached */ REGISTER_LONG_CONSTANT("SOCKET_EUNATCH", EUNATCH, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOCSI /* No CSI structure available */ REGISTER_LONG_CONSTANT("SOCKET_ENOCSI", ENOCSI, CONST_CS | CONST_PERSISTENT); #endif #ifdef EL2HLT /* Level 2 halted */ REGISTER_LONG_CONSTANT("SOCKET_EL2HLT", EL2HLT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADE /* Invalid exchange */ REGISTER_LONG_CONSTANT("SOCKET_EBADE", EBADE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADR /* Invalid request descriptor */ REGISTER_LONG_CONSTANT("SOCKET_EBADR", EBADR, CONST_CS | CONST_PERSISTENT); #endif #ifdef EXFULL /* Exchange full */ REGISTER_LONG_CONSTANT("SOCKET_EXFULL", EXFULL, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOANO /* No anode */ REGISTER_LONG_CONSTANT("SOCKET_ENOANO", ENOANO, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADRQC /* Invalid request code */ REGISTER_LONG_CONSTANT("SOCKET_EBADRQC", EBADRQC, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADSLT /* Invalid slot */ REGISTER_LONG_CONSTANT("SOCKET_EBADSLT", EBADSLT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOSTR /* Device not a stream */ REGISTER_LONG_CONSTANT("SOCKET_ENOSTR", ENOSTR, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENODATA /* No data available */ REGISTER_LONG_CONSTANT("SOCKET_ENODATA", ENODATA, CONST_CS | CONST_PERSISTENT); #endif #ifdef ETIME /* Timer expired */ REGISTER_LONG_CONSTANT("SOCKET_ETIME", ETIME, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOSR /* Out of streams resources */ REGISTER_LONG_CONSTANT("SOCKET_ENOSR", ENOSR, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENONET /* Machine is not on the network */ REGISTER_LONG_CONSTANT("SOCKET_ENONET", ENONET, CONST_CS | CONST_PERSISTENT); #endif #ifdef EREMOTE /* Object is remote */ REGISTER_LONG_CONSTANT("SOCKET_EREMOTE", EREMOTE, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOLINK /* Link has been severed */ REGISTER_LONG_CONSTANT("SOCKET_ENOLINK", ENOLINK, CONST_CS | CONST_PERSISTENT); #endif #ifdef EADV /* Advertise error */ REGISTER_LONG_CONSTANT("SOCKET_EADV", EADV, CONST_CS | CONST_PERSISTENT); #endif #ifdef ESRMNT /* Srmount error */ REGISTER_LONG_CONSTANT("SOCKET_ESRMNT", ESRMNT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ECOMM /* Communication error on send */ REGISTER_LONG_CONSTANT("SOCKET_ECOMM", ECOMM, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPROTO /* Protocol error */ REGISTER_LONG_CONSTANT("SOCKET_EPROTO", EPROTO, CONST_CS | CONST_PERSISTENT); #endif #ifdef EMULTIHOP /* Multihop attempted */ REGISTER_LONG_CONSTANT("SOCKET_EMULTIHOP", EMULTIHOP, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADMSG /* Not a data message */ REGISTER_LONG_CONSTANT("SOCKET_EBADMSG", EBADMSG, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTUNIQ /* Name not unique on network */ REGISTER_LONG_CONSTANT("SOCKET_ENOTUNIQ", ENOTUNIQ, CONST_CS | CONST_PERSISTENT); #endif #ifdef EBADFD /* File descriptor in bad state */ REGISTER_LONG_CONSTANT("SOCKET_EBADFD", EBADFD, CONST_CS | CONST_PERSISTENT); #endif #ifdef EREMCHG /* Remote address changed */ REGISTER_LONG_CONSTANT("SOCKET_EREMCHG", EREMCHG, CONST_CS | CONST_PERSISTENT); #endif #ifdef ERESTART /* Interrupted system call should be restarted */ REGISTER_LONG_CONSTANT("SOCKET_ERESTART", ERESTART, CONST_CS | CONST_PERSISTENT); #endif #ifdef ESTRPIPE /* Streams pipe error */ REGISTER_LONG_CONSTANT("SOCKET_ESTRPIPE", ESTRPIPE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EUSERS /* Too many users */ REGISTER_LONG_CONSTANT("SOCKET_EUSERS", EUSERS, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTSOCK /* Socket operation on non-socket */ REGISTER_LONG_CONSTANT("SOCKET_ENOTSOCK", ENOTSOCK, CONST_CS | CONST_PERSISTENT); #endif #ifdef EDESTADDRREQ /* Destination address required */ REGISTER_LONG_CONSTANT("SOCKET_EDESTADDRREQ", EDESTADDRREQ, CONST_CS | CONST_PERSISTENT); #endif #ifdef EMSGSIZE /* Message too long */ REGISTER_LONG_CONSTANT("SOCKET_EMSGSIZE", EMSGSIZE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPROTOTYPE /* Protocol wrong type for socket */ REGISTER_LONG_CONSTANT("SOCKET_EPROTOTYPE", EPROTOTYPE, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOPROTOOPT /* Protocol not available */ REGISTER_LONG_CONSTANT("SOCKET_ENOPROTOOPT", ENOPROTOOPT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPROTONOSUPPORT /* Protocol not supported */ REGISTER_LONG_CONSTANT("SOCKET_EPROTONOSUPPORT", EPROTONOSUPPORT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ESOCKTNOSUPPORT /* Socket type not supported */ REGISTER_LONG_CONSTANT("SOCKET_ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EOPNOTSUPP /* Operation not supported on transport endpoint */ REGISTER_LONG_CONSTANT("SOCKET_EOPNOTSUPP", EOPNOTSUPP, CONST_CS | CONST_PERSISTENT); #endif #ifdef EPFNOSUPPORT /* Protocol family not supported */ REGISTER_LONG_CONSTANT("SOCKET_EPFNOSUPPORT", EPFNOSUPPORT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EAFNOSUPPORT /* Address family not supported by protocol */ REGISTER_LONG_CONSTANT("SOCKET_EAFNOSUPPORT", EAFNOSUPPORT, CONST_CS | CONST_PERSISTENT); #endif #ifdef EADDRINUSE /* Address already in use */ REGISTER_LONG_CONSTANT("SOCKET_EADDRINUSE", EADDRINUSE, CONST_CS | CONST_PERSISTENT); #endif #ifdef EADDRNOTAVAIL /* Cannot assign requested address */ REGISTER_LONG_CONSTANT("SOCKET_EADDRNOTAVAIL", EADDRNOTAVAIL, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENETDOWN /* Network is down */ REGISTER_LONG_CONSTANT("SOCKET_ENETDOWN", ENETDOWN, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENETUNREACH /* Network is unreachable */ REGISTER_LONG_CONSTANT("SOCKET_ENETUNREACH", ENETUNREACH, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENETRESET /* Network dropped connection because of reset */ REGISTER_LONG_CONSTANT("SOCKET_ENETRESET", ENETRESET, CONST_CS | CONST_PERSISTENT); #endif #ifdef ECONNABORTED /* Software caused connection abort */ REGISTER_LONG_CONSTANT("SOCKET_ECONNABORTED", ECONNABORTED, CONST_CS | CONST_PERSISTENT); #endif #ifdef ECONNRESET /* Connection reset by peer */ REGISTER_LONG_CONSTANT("SOCKET_ECONNRESET", ECONNRESET, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOBUFS /* No buffer space available */ REGISTER_LONG_CONSTANT("SOCKET_ENOBUFS", ENOBUFS, CONST_CS | CONST_PERSISTENT); #endif #ifdef EISCONN /* Transport endpoint is already connected */ REGISTER_LONG_CONSTANT("SOCKET_EISCONN", EISCONN, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOTCONN /* Transport endpoint is not connected */ REGISTER_LONG_CONSTANT("SOCKET_ENOTCONN", ENOTCONN, CONST_CS | CONST_PERSISTENT); #endif #ifdef ESHUTDOWN /* Cannot send after transport endpoint shutdown */ REGISTER_LONG_CONSTANT("SOCKET_ESHUTDOWN", ESHUTDOWN, CONST_CS | CONST_PERSISTENT); #endif #ifdef ETOOMANYREFS /* Too many references: cannot splice */ REGISTER_LONG_CONSTANT("SOCKET_ETOOMANYREFS", ETOOMANYREFS, CONST_CS | CONST_PERSISTENT); #endif #ifdef ETIMEDOUT /* Connection timed out */ REGISTER_LONG_CONSTANT("SOCKET_ETIMEDOUT", ETIMEDOUT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ECONNREFUSED /* Connection refused */ REGISTER_LONG_CONSTANT("SOCKET_ECONNREFUSED", ECONNREFUSED, CONST_CS | CONST_PERSISTENT); #endif #ifdef EHOSTDOWN /* Host is down */ REGISTER_LONG_CONSTANT("SOCKET_EHOSTDOWN", EHOSTDOWN, CONST_CS | CONST_PERSISTENT); #endif #ifdef EHOSTUNREACH /* No route to host */ REGISTER_LONG_CONSTANT("SOCKET_EHOSTUNREACH", EHOSTUNREACH, CONST_CS | CONST_PERSISTENT); #endif #ifdef EALREADY /* Operation already in progress */ REGISTER_LONG_CONSTANT("SOCKET_EALREADY", EALREADY, CONST_CS | CONST_PERSISTENT); #endif #ifdef EINPROGRESS /* Operation now in progress */ REGISTER_LONG_CONSTANT("SOCKET_EINPROGRESS", EINPROGRESS, CONST_CS | CONST_PERSISTENT); #endif #ifdef EISNAM /* Is a named type file */ REGISTER_LONG_CONSTANT("SOCKET_EISNAM", EISNAM, CONST_CS | CONST_PERSISTENT); #endif #ifdef EREMOTEIO /* Remote I/O error */ REGISTER_LONG_CONSTANT("SOCKET_EREMOTEIO", EREMOTEIO, CONST_CS | CONST_PERSISTENT); #endif #ifdef EDQUOT /* Quota exceeded */ REGISTER_LONG_CONSTANT("SOCKET_EDQUOT", EDQUOT, CONST_CS | CONST_PERSISTENT); #endif #ifdef ENOMEDIUM /* No medium found */ REGISTER_LONG_CONSTANT("SOCKET_ENOMEDIUM", ENOMEDIUM, CONST_CS | CONST_PERSISTENT); #endif #ifdef EMEDIUMTYPE /* Wrong medium type */ REGISTER_LONG_CONSTANT("SOCKET_EMEDIUMTYPE", EMEDIUMTYPE, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("IPPROTO_IP", IPPROTO_IP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPPROTO_IPV6", IPPROTO_IPV6, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_TCP", IPPROTO_TCP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_UDP", IPPROTO_UDP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AI_PASSIVE", AI_PASSIVE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AI_CANONNAME", AI_CANONNAME, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AI_NUMERICHOST", AI_NUMERICHOST, CONST_CS | CONST_PERSISTENT); #if HAVE_AI_V4MAPPED REGISTER_LONG_CONSTANT("AI_V4MAPPED", AI_V4MAPPED, CONST_CS | CONST_PERSISTENT); #endif #if HAVE_AI_ALL REGISTER_LONG_CONSTANT("AI_ALL", AI_ALL, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("AI_ADDRCONFIG", AI_ADDRCONFIG, CONST_CS | CONST_PERSISTENT); #if HAVE_AI_IDN REGISTER_LONG_CONSTANT("AI_IDN", AI_IDN, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AI_CANONIDN", AI_CANONIDN, CONST_CS | CONST_PERSISTENT); // REGISTER_LONG_CONSTANT("AI_IDN_ALLOW_UNASSIGNED", AI_IDN_ALLOW_UNASSIGNED, CONST_CS | CONST_PERSISTENT); // REGISTER_LONG_CONSTANT("AI_IDN_USE_STD3_ASCII_RULES", AI_IDN_USE_STD3_ASCII_RULES, CONST_CS | CONST_PERSISTENT); #endif #ifdef AI_NUMERICSERV REGISTER_LONG_CONSTANT("AI_NUMERICSERV", AI_NUMERICSERV, CONST_CS | CONST_PERSISTENT); #endif } void php_swoole_socket_coro_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_socket_coro, "Swoole\\Coroutine\\Socket", "Co\\Socket", swoole_socket_coro_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_socket_coro); SW_SET_CLASS_CLONEABLE(swoole_socket_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_socket_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_socket_coro, socket_coro_create_object, socket_coro_free_object, SocketObject, std); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("domain"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("protocol"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_socket_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); #ifdef SWOOLE_SOCKETS_SUPPORT zend_declare_property_bool(swoole_socket_coro_ce, ZEND_STRL("__ext_sockets_nonblock"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("__ext_sockets_timeout"), 0, ZEND_ACC_PUBLIC); #endif SW_INIT_CLASS_ENTRY_EX(swoole_socket_coro_exception, "Swoole\\Coroutine\\Socket\\Exception", "Co\\Socket\\Exception", nullptr, swoole_exception); if (!zend_hash_str_find_ptr(&module_registry, ZEND_STRL("sockets"))) { socket_coro_register_constants(module_number); } #ifdef ECANCELED SW_REGISTER_LONG_CONSTANT("SOCKET_ECANCELED", ECANCELED); #endif #ifdef TCP_INFO SW_REGISTER_LONG_CONSTANT("TCP_INFO", TCP_INFO); #endif } static sw_inline void socket_coro_sync_properties(const zval *zobject, const SocketObject *sock) { zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sock->socket->errCode); zend_update_property_string(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), sock->socket->errMsg); } static void sw_inline socket_coro_init(const zval *zobject, const SocketObject *sock) { sock->socket->set_zero_copy(true); sock->socket->set_buffer_allocator(sw_zend_string_allocator()); zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("fd"), sock->socket->get_fd()); zend_update_property_long( swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("domain"), sock->socket->get_sock_domain()); zend_update_property_long( swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("type"), sock->socket->get_sock_type()); zend_update_property_long( swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("protocol"), sock->socket->get_sock_protocol()); } SW_API bool php_swoole_export_socket(zval *zobject, SocketImpl *_socket) { zend_object *object = socket_coro_create_object(swoole_socket_coro_ce); if (!object) { return false; } auto *sock = socket_coro_fetch_object(object); sock->reference = true; sock->socket = _socket; ZVAL_OBJ(zobject, object); socket_coro_init(zobject, sock); return true; } SW_API zend_object *php_swoole_dup_socket(int fd, swSocketType type) { php_swoole_check_reactor(); int new_fd = dup(fd); if (new_fd < 0) { php_swoole_sys_error(E_WARNING, "dup(%d) failed", fd); return nullptr; } return php_swoole_create_socket_from_fd(new_fd, type); } SW_API zend_object *php_swoole_create_socket(swSocketType type) { zval zobject; zend_object *object = socket_coro_create_object(swoole_socket_coro_ce); auto *sock = socket_coro_fetch_object(object); sock->socket = new SocketImpl(type); if (UNEXPECTED(sock->socket->get_fd() < 0)) { php_swoole_sys_error(E_WARNING, "new Socket() failed"); delete sock->socket; sock->socket = nullptr; OBJ_RELEASE(object); return nullptr; } ZVAL_OBJ(&zobject, object); socket_coro_init(&zobject, sock); return object; } SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code, const char *msg) { swoole_set_last_error(code); zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), code); zend_update_property_string(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), msg); } SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code) { php_swoole_socket_set_error_properties(zobject, code, swoole_strerror(code)); } SW_API void php_swoole_socket_set_error_properties(const zval *zobject, const SocketImpl *socket) { php_swoole_socket_set_error_properties(zobject, socket->errCode, socket->errMsg); } static zend_object *socket_coro_create_object(SocketImpl *socket) { zval zobject; auto *object = socket_coro_create_object(swoole_socket_coro_ce); auto *sock = socket_coro_fetch_object(object); sock->socket = socket; if (UNEXPECTED(sock->socket->get_fd() < 0)) { php_swoole_sys_error(E_WARNING, "new Socket() failed"); delete sock->socket; sock->socket = nullptr; OBJ_RELEASE(object); return nullptr; } ZVAL_OBJ(&zobject, object); socket_coro_init(&zobject, sock); return object; } SW_API zend_object *php_swoole_create_socket_from_fd(int fd, swSocketType type) { return socket_coro_create_object(new SocketImpl(fd, type)); } SW_API zend_object *php_swoole_create_socket_from_fd(int fd, int _domain, int _type, int _protocol) { return socket_coro_create_object(new SocketImpl(fd, _domain, _type, _protocol)); } SW_API SocketImpl *php_swoole_get_socket(const zval *zobject) { SW_ASSERT(Z_OBJCE_P(zobject) == swoole_socket_coro_ce); auto *sock = socket_coro_fetch_object(Z_OBJ_P(zobject)); return sock->socket; } SW_API bool php_swoole_socket_is_closed(const zval *zobject) { auto *_sock = socket_coro_fetch_object(Z_OBJ_P(zobject)); return _sock->socket == nullptr || _sock->socket->is_closed(); } SW_API void php_swoole_init_socket_object(zval *zobject, SocketImpl *socket) { auto *object = socket_coro_create_object(swoole_socket_coro_ce); auto *sock = socket_coro_fetch_object(object); sock->socket = socket; ZVAL_OBJ(zobject, object); socket_coro_init(zobject, sock); } SW_API bool php_swoole_socket_set_protocol(SocketImpl *sock, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; bool ret = true; /** * ssl */ if (php_swoole_array_get_value(vht, "open_ssl", ztmp)) { if (zval_is_true(ztmp)) { sock->enable_ssl_encrypt(); } } if (php_swoole_array_get_value(vht, "open_http2_protocol", ztmp)) { sock->http2 = zval_is_true(ztmp); } if (sock->ssl_is_enable()) { if (!php_swoole_socket_set_ssl(sock, zset)) { ret = false; } } /** * protocol */ // buffer: eof check if (php_swoole_array_get_value(vht, "open_eof_check", ztmp)) { sock->open_eof_check = zval_is_true(ztmp); } // buffer: split package with eof if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { sock->protocol.split_by_eof = zval_is_true(ztmp); if (sock->protocol.split_by_eof) { sock->open_eof_check = true; } } // package eof if (php_swoole_array_get_value(vht, "package_eof", ztmp)) { zend::String str_v(ztmp); if (str_v.len() == 0) { php_swoole_fatal_error(E_ERROR, "package_eof cannot be an empty string"); ret = false; } else if (str_v.len() > SW_DATA_EOF_MAXLEN) { php_swoole_fatal_error(E_ERROR, "package_eof max length is %d", SW_DATA_EOF_MAXLEN); ret = false; } else { sock->protocol.package_eof_len = str_v.len(); memcpy(sock->protocol.package_eof, str_v.val(), str_v.len()); } } if (php_swoole_array_get_value(vht, "open_fastcgi_protocol", ztmp)) { #define FCGI_HEADER_LEN 8 #define FCGI_MAX_LENGTH 0xffff sock->open_length_check = zval_is_true(ztmp); sock->protocol.package_length_size = FCGI_HEADER_LEN; sock->protocol.package_length_offset = 0; sock->protocol.package_body_offset = 0; sock->protocol.get_package_length = [](const Protocol *protocol, swoole::network::Socket *conn, PacketLength *pl) { const auto *p = reinterpret_cast(pl->buf); ssize_t length = 0; if (pl->buf_size >= FCGI_HEADER_LEN) { length = ((p[4] << 8) | p[5]) + p[6]; if (length > FCGI_MAX_LENGTH) { length = -1; } else { length += FCGI_HEADER_LEN; } } return length; }; } // open mqtt protocol if (php_swoole_array_get_value(vht, "open_mqtt_protocol", ztmp)) { sock->open_length_check = zval_is_true(ztmp); if (zval_is_true(ztmp)) { swoole::mqtt::set_protocol(&sock->protocol); } } // open length check if (php_swoole_array_get_value(vht, "open_length_check", ztmp)) { sock->open_length_check = zval_is_true(ztmp); sock->protocol.get_package_length = Protocol::default_length_func; } // package length size if (php_swoole_array_get_value(vht, "package_length_type", ztmp)) { zend::String str_v(ztmp); sock->protocol.package_length_type = str_v.val()[0]; sock->protocol.package_length_size = swoole_type_size(sock->protocol.package_length_type); if (sock->protocol.package_length_size == 0) { php_swoole_fatal_error(E_WARNING, "Unknown package_length_type name '%c', see pack(). Link: https://php.net/pack", sock->protocol.package_length_type); ret = false; } } // package length offset if (php_swoole_array_get_value(vht, "package_length_offset", ztmp)) { zend_long v = zval_get_long(ztmp); sock->protocol.package_length_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } // package body start if (php_swoole_array_get_value(vht, "package_body_offset", ztmp)) { zend_long v = zval_get_long(ztmp); sock->protocol.package_body_offset = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { auto cb = sw_callable_create(ztmp); if (cb) { sock->protocol.get_package_length = php_swoole_length_func; if (sock->protocol.private_data_1) { sw_callable_free(sock->protocol.private_data_1); } sock->protocol.private_data_1 = cb; sock->protocol.package_length_size = 0; sock->protocol.package_length_type = '\0'; sock->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; } } /** * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { zend_long v = php_swoole_parse_to_size(ztmp); sock->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } else { sock->protocol.package_max_length = SW_INPUT_BUFFER_SIZE; } return ret; } SW_API bool php_swoole_socket_set(SocketImpl *cli, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; bool ret = true; /** * timeout */ if (php_swoole_array_get_value(vht, "timeout", ztmp)) { cli->set_timeout(zval_get_double(ztmp)); } if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_CONNECT); } if (php_swoole_array_get_value(vht, "read_timeout", ztmp)) { cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_READ); } if (php_swoole_array_get_value(vht, "write_timeout", ztmp)) { cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_WRITE); } std::string _bind_address; int _bind_port = 0; if (php_swoole_array_get_value(vht, "bind_port", ztmp)) { zend_long v = zval_get_long(ztmp); _bind_port = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } if (php_swoole_array_get_value(vht, "bind_address", ztmp)) { zend::String tmp = ztmp; _bind_address = tmp.to_std_string(); } if (!_bind_address.empty() && !cli->bind(_bind_address, _bind_port)) { ret = false; } /** * socket send/recv buffer size */ if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { zend_long size = php_swoole_parse_to_size(ztmp); if (size <= 0) { php_swoole_fatal_error(E_WARNING, "socket buffer size must be greater than 0, got " ZEND_LONG_FMT, size); ret = false; } else { cli->set_option(SOL_SOCKET, SO_RCVBUF, size) && cli->set_option(SOL_SOCKET, SO_SNDBUF, size); } } /** * client: tcp_nodelay */ if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { if (cli->get_type() == SW_SOCK_TCP || cli->get_type() != SW_SOCK_TCP6) { cli->get_socket()->set_tcp_nodelay(zval_is_true(ztmp)); } } /** * openssl and protocol options */ if (!php_swoole_socket_set_protocol(cli, zset)) { ret = false; } /** * socks5 proxy */ if (php_swoole_array_get_value(vht, "socks5_host", ztmp)) { if (!cli->get_socket()->is_inet()) { zend_throw_exception_ex( swoole_exception_ce, SW_ERROR_OPERATION_NOT_SUPPORT, "Only tcp socket supports socks5 proxy settings"); return false; } zend::String host(ztmp); if (php_swoole_array_get_value(vht, "socks5_port", ztmp)) { std::string user, pwd; auto socks5_port = zval_get_long(ztmp); if (php_swoole_array_get_value(vht, "socks5_username", ztmp)) { user = zend::String(ztmp).to_std_string(); if (!user.empty() && php_swoole_array_get_value(vht, "socks5_password", ztmp)) { pwd = zend::String(ztmp).to_std_string(); } else { php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); ret = false; } } cli->set_socks5_proxy(host.to_std_string(), socks5_port, user, pwd); } else { php_swoole_fatal_error(E_WARNING, "socks5_port should not be null"); ret = false; } } /** * http proxy */ else if (php_swoole_array_get_value(vht, "http_proxy_host", ztmp)) { if (!cli->get_socket()->is_inet()) { zend_throw_exception_ex( swoole_exception_ce, SW_ERROR_OPERATION_NOT_SUPPORT, "Only tcp socket supports http proxy settings"); return false; } zend::String host(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_port", ztmp)) { std::string user, pwd; auto http_proxy_port = zval_get_long(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_username", ztmp) || php_swoole_array_get_value(vht, "http_proxy_user", ztmp)) { user = zend::String(ztmp).to_std_string(); if (!user.empty() && php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { pwd = zend::String(ztmp).to_std_string(); } else { php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); ret = false; } } cli->set_http_proxy(host.to_std_string(), http_proxy_port, user, pwd); } else { php_swoole_fatal_error(E_WARNING, "http_proxy_port should not be null"); ret = false; } } return ret; } SW_API bool php_swoole_socket_set_ssl(SocketImpl *sock, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { zend_long v = zval_get_long(ztmp); sock->set_ssl_protocols(v); } if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { sock->set_ssl_disable_compress(!zval_is_true(ztmp)); } else if (php_swoole_array_get_value(vht, "ssl_disable_compression", ztmp)) { sock->set_ssl_disable_compress(!zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { zend::String str_v(ztmp); if (!sock->set_ssl_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_WARNING, "ssl cert file[%s] not found", str_v.val()); return false; } } if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { zend::String str_v(ztmp); if (!sock->set_ssl_key_file(str_v.to_std_string())) { php_swoole_fatal_error(E_WARNING, "ssl key file[%s] not found", str_v.val()); return false; } } if (!sock->get_ssl_cert_file().empty() && sock->get_ssl_key_file().empty()) { php_swoole_fatal_error(E_WARNING, "ssl require key file"); } if (!sock->get_ssl_key_file().empty() && sock->get_ssl_cert_file().empty()) { php_swoole_fatal_error(E_WARNING, "ssl require cert file"); } if (php_swoole_array_get_value(vht, "ssl_passphrase", ztmp)) { sock->set_ssl_passphrase(zend::String(ztmp).to_std_string()); } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (php_swoole_array_get_value(vht, "ssl_host_name", ztmp)) { sock->set_tls_host_name(zend::String(ztmp).to_std_string()); } #endif if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { sock->set_ssl_verify_peer(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { sock->set_ssl_allow_self_signed(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { sock->set_ssl_cafile(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { sock->set_ssl_capath(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { zend_long v = zval_get_long(ztmp); sock->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { sock->set_ssl_ciphers(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_ecdh_curve", ztmp)) { sock->set_ssl_ecdh_curve(zend::String(ztmp).to_std_string()); } #ifdef OPENSSL_IS_BORINGSSL if (php_swoole_array_get_value(vht, "ssl_grease", ztmp)) { zend_long v = zval_get_long(ztmp); sock->set_ssl_grease(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } #endif return true; } PHP_FUNCTION(swoole_coroutine_socketpair) { zend_long domain, type, protocol; php_socket_t pair[2]; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_LONG(domain) Z_PARAM_LONG(type) Z_PARAM_LONG(protocol) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (0 != socketpair((int) domain, (int) type, (int) protocol, pair)) { php_swoole_sys_error(E_WARNING, "failed to create socket"); RETURN_FALSE; } php_swoole_check_reactor(); auto sock_type = swoole::network::Socket::convert_to_type(domain, type); zend_object *s1 = php_swoole_create_socket_from_fd(pair[0], sock_type); if (s1 == nullptr) { RETURN_FALSE; } zend_object *s2 = php_swoole_create_socket_from_fd(pair[1], sock_type); if (s2 == nullptr) { OBJ_RELEASE(s1); RETURN_FALSE; } zval zobject1, zobject2; ZVAL_OBJ(&zobject1, s1); ZVAL_OBJ(&zobject2, s2); array_init(return_value); add_next_index_zval(return_value, &zobject1); add_next_index_zval(return_value, &zobject2); } static PHP_METHOD(swoole_socket_coro, __construct) { zend_long domain, type, protocol = IPPROTO_IP; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 3) Z_PARAM_LONG(domain) Z_PARAM_LONG(type) Z_PARAM_OPTIONAL Z_PARAM_LONG(protocol) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *sock = socket_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); if (sock->socket) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } php_swoole_check_reactor(); sock->socket = new SocketImpl((int) domain, (int) type, (int) protocol); if (UNEXPECTED(sock->socket->get_fd() < 0)) { zend_throw_exception_ex( swoole_socket_coro_exception_ce, errno, "new Socket() failed. Error: %s [%d]", strerror(errno), errno); delete sock->socket; sock->socket = nullptr; RETURN_FALSE; } socket_coro_init(ZEND_THIS, sock); } static PHP_METHOD(swoole_socket_coro, bind) { char *address; size_t l_address; zend_long port = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(address, l_address) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->bind(std::string(address, l_address), port)) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, listen) { zend_long backlog = SW_BACKLOG; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(backlog) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->listen(backlog)) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, accept) { double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); SocketImpl *conn = sock->socket->accept(timeout); if (conn) { zend_object *client = socket_coro_create_object(swoole_socket_coro_ce); auto *client_sock = socket_coro_fetch_object(client); client_sock->socket = conn; ZVAL_OBJ(return_value, &client_sock->std); socket_coro_init(return_value, client_sock); // It must be copied once to avoid destroying the function when the connection closes. if (sock->socket->protocol.private_data_1) { auto *cb = static_cast(sock->socket->protocol.private_data_1); conn->protocol.private_data_1 = cb->dup(); } } else { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } } static PHP_METHOD(swoole_socket_coro, connect) { char *host; size_t l_host; zend_long port = 0; double timeout = 0; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STRING(host, l_host) Z_PARAM_OPTIONAL Z_PARAM_LONG(port) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); if (sock->socket->is_port_required()) { if (ZEND_NUM_ARGS() == 1) { php_swoole_error(E_WARNING, "Socket of type AF_INET/AF_INET6 requires port argument"); RETURN_FALSE; } else if (port == 0 || port >= 65536) { php_swoole_error(E_WARNING, "Invalid port argument[" ZEND_LONG_FMT "]", port); RETURN_FALSE; } } SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_CONNECT); if (!sock->socket->connect(std::string(host, l_host), port)) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, checkLiveness) { swoole_get_socket_coro(sock, ZEND_THIS); bool liveness = sock->socket->check_liveness(); socket_coro_sync_properties(ZEND_THIS, sock); RETURN_BOOL(liveness); } static PHP_METHOD(swoole_socket_coro, getBoundCid) { zend_long event; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(event) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); RETURN_LONG(sock->socket->get_bound_cid((swEventType) event)); } static PHP_METHOD(swoole_socket_coro, peek) { zend_long length = SW_BUFFER_SIZE_BIG; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(length <= 0)) { length = SW_BUFFER_SIZE_BIG; } swoole_get_socket_coro(sock, ZEND_THIS); zend_string *buf = zend_string_alloc(length, false); ssize_t bytes = sock->socket->peek(ZSTR_VAL(buf), length); socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(bytes < 0)) { zend_string_free(buf); RETURN_FALSE; } else if (UNEXPECTED(bytes == 0)) { zend_string_free(buf); RETURN_EMPTY_STRING(); } else { RETURN_STR(sw_zend_string_recycle(buf, length, bytes)); } } enum RecvMode { SOCKET_RECV, SOCKET_RECV_ALL, SOCKET_RECV_LINE, SOCKET_RECV_WITH_BUFFER, }; static inline void socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMode type) { zend_long length = SW_BUFFER_SIZE_BIG; double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(length) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(length <= 0)) { length = SW_BUFFER_SIZE_BIG; } swoole_get_socket_coro(sock, ZEND_THIS); zend_string *buf = zend_string_alloc(length, false); SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t bytes = -1; switch (type) { case SOCKET_RECV: bytes = sock->socket->recv(ZSTR_VAL(buf), length); break; case SOCKET_RECV_ALL: bytes = sock->socket->recv_all(ZSTR_VAL(buf), length); break; case SOCKET_RECV_LINE: bytes = sock->socket->recv_line(ZSTR_VAL(buf), length); break; case SOCKET_RECV_WITH_BUFFER: bytes = sock->socket->recv_with_buffer(ZSTR_VAL(buf), length); break; default: assert(0); break; } socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(bytes < 0)) { zend_string_free(buf); RETURN_FALSE; } else if (UNEXPECTED(bytes == 0)) { zend_string_free(buf); RETURN_EMPTY_STRING(); } else { RETURN_STR(sw_zend_string_recycle(buf, length, bytes)); } } static PHP_METHOD(swoole_socket_coro, recv) { socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV); } static PHP_METHOD(swoole_socket_coro, recvAll) { socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_ALL); } static PHP_METHOD(swoole_socket_coro, recvLine) { socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_LINE); } static PHP_METHOD(swoole_socket_coro, recvWithBuffer) { socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_WITH_BUFFER); } static PHP_METHOD(swoole_socket_coro, recvPacket) { double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); ssize_t retval = sock->socket->recv_packet(timeout); socket_coro_sync_properties(ZEND_THIS, sock); if (retval < 0) { RETURN_FALSE; } else if (retval == 0) { sock->socket->get_read_buffer()->clear(); RETURN_EMPTY_STRING(); } else { auto strval = sock->socket->pop_packet(); if (strval == nullptr) { sock->socket->set_err(ENOMEM); RETURN_FALSE; } else { zend::assign_zend_string_by_val(return_value, strval, retval); } } } static sw_inline void socket_coro_send(INTERNAL_FUNCTION_PARAMETERS, const bool all) { char *data; size_t length; double timeout = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(data, length) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_WRITE); ssize_t retval = all ? sock->socket->send_all(data, length) : sock->socket->send(data, length); socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(retval < 0)) { RETURN_FALSE; } else { RETURN_LONG(retval); } } static PHP_METHOD(swoole_socket_coro, send) { socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static void socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { zval *ziov = nullptr; zval *zelement = nullptr; double timeout = 0; int iovcnt = 0; int iov_index = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(ziov) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); ON_SCOPE_EXIT { socket_coro_sync_properties(ZEND_THIS, sock); }; HashTable *vht = Z_ARRVAL_P(ziov); iovcnt = zend_array_count(vht); if (iovcnt > IOV_MAX) { sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, SW_IOV_MAX_ERROR_MSG, IOV_MAX); sock->socket->set_err(EINVAL, sw_tg_buffer()->to_std_string()); RETURN_FALSE; } std::unique_ptr iov(new iovec[iovcnt]); SW_HASHTABLE_FOREACH_START(vht, zelement) if (!ZVAL_IS_STRING(zelement)) { zend_throw_exception_ex(swoole_socket_coro_exception_ce, EINVAL, "Item #[%d] must be of type string, %s given", iov_index, zend_get_type_by_const(Z_TYPE_P(zelement))); RETURN_FALSE; } if (Z_STRLEN_P(zelement) == 0) { zend_throw_exception_ex( swoole_socket_coro_exception_ce, EINVAL, "Item #[%d] cannot be empty string", iov_index); RETURN_FALSE; } iov[iov_index].iov_base = Z_STRVAL_P(zelement); iov[iov_index].iov_len = Z_STRLEN_P(zelement); iov_index++; SW_HASHTABLE_FOREACH_END(); swoole::network::IOVector io_vector((struct iovec *) iov.get(), iovcnt); SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_WRITE); ssize_t retval = all ? sock->socket->writev_all(&io_vector) : sock->socket->writev(&io_vector); if (UNEXPECTED(retval < 0)) { RETURN_FALSE; } else { RETURN_LONG(retval); } } static PHP_METHOD(swoole_socket_coro, writeVector) { socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_socket_coro, writeVectorAll) { socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static void socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { zval *ziov = nullptr; zval *zelement = nullptr; double timeout = 0; int iovcnt = 0; int iov_index = 0; ssize_t total_length = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(ziov) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); ON_SCOPE_EXIT { socket_coro_sync_properties(ZEND_THIS, sock); }; HashTable *vht = Z_ARRVAL_P(ziov); iovcnt = zend_array_count(vht); if (iovcnt > IOV_MAX) { sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, SW_IOV_MAX_ERROR_MSG, IOV_MAX); sock->socket->set_err(EINVAL, sw_tg_buffer()->to_std_string()); RETURN_FALSE; } std::unique_ptr iov(new iovec[iovcnt]); SW_HASHTABLE_FOREACH_START(vht, zelement) { if (!ZVAL_IS_LONG(zelement)) { zend_throw_exception_ex(swoole_socket_coro_exception_ce, EINVAL, "Item #[%d] must be of type int, %s given", iov_index, zend_get_type_by_const(Z_TYPE_P(zelement))); RETURN_FALSE; } if (Z_LVAL_P(zelement) < 0) { zend_throw_exception_ex( swoole_socket_coro_exception_ce, EINVAL, "Item #[%d] must be greater than 0", iov_index); RETURN_FALSE; } size_t iov_len = Z_LVAL_P(zelement); iov[iov_index].iov_base = zend_string_alloc(iov_len, false)->val; iov[iov_index].iov_len = iov_len; iov_index++; total_length += iov_len; } SW_HASHTABLE_FOREACH_END(); swoole::network::IOVector io_vector((struct iovec *) iov.get(), iovcnt); SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t retval = all ? sock->socket->readv_all(&io_vector) : sock->socket->readv(&io_vector); auto free_func = [](const iovec *iov, int iovcnt, int iov_index) { for (; iov_index < iovcnt; iov_index++) { zend_string_free(zend::fetch_zend_string_by_val((char *) iov[iov_index].iov_base)); } }; if (UNEXPECTED(retval < 0)) { free_func(iov.get(), iovcnt, 0); RETURN_FALSE; } else if (UNEXPECTED(retval == 0)) { free_func(iov.get(), iovcnt, 0); RETURN_EMPTY_ARRAY(); } else { array_init(return_value); size_t real_count; if (retval < total_length) { /** * Free the extra memory. * For example iov is [5, 5, 5], but we get ['hello', 'world'], we should free the last iov. */ iov_index = io_vector.get_index(); size_t offset_bytes = io_vector.get_offset_bytes(); real_count = iov_index + 1; zend_string *str = zend::fetch_zend_string_by_val((char *) iov[iov_index].iov_base); iov[iov_index].iov_base = sw_zend_string_recycle(str, iov[iov_index].iov_len, offset_bytes)->val; iov[iov_index].iov_len = offset_bytes; free_func(iov.get(), iovcnt, real_count); } else { real_count = iovcnt; } SW_LOOP_N(real_count) { ((char *) iov[i].iov_base)[iov[i].iov_len] = '\0'; add_next_index_str(return_value, zend::fetch_zend_string_by_val((char *) iov[i].iov_base)); } } } static PHP_METHOD(swoole_socket_coro, readVector) { socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_socket_coro, readVectorAll) { socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_socket_coro, sendFile) { char *file; size_t file_len; zend_long offset = 0; zend_long length = 0; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STRING(file, file_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(offset) Z_PARAM_LONG(length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (file_len == 0) { php_swoole_fatal_error(E_WARNING, "file to send is empty"); RETURN_FALSE; } swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->sendfile(file, offset, length)) { socket_coro_sync_properties(ZEND_THIS, sock); RETVAL_FALSE; } else { RETVAL_TRUE; } } static PHP_METHOD(swoole_socket_coro, sendAll) { socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_socket_coro, recvfrom) { zval *peername; double timeout = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL_EX(peername, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); zend_string *buf = zend_string_alloc(SW_BUFFER_SIZE_BIG, false); SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t bytes = sock->socket->recvfrom(ZSTR_VAL(buf), SW_BUFFER_SIZE_BIG); socket_coro_sync_properties(ZEND_THIS, sock); if (bytes < 0) { zend_string_free(buf); RETURN_FALSE; } else if (bytes == 0) { zend_string_free(buf); RETURN_EMPTY_STRING(); } else { zval_dtor(peername); array_init(peername); add_assoc_string(peername, "address", (char *) sock->socket->get_addr()); add_assoc_long(peername, "port", sock->socket->get_port()); ZSTR_LEN(buf) = bytes; ZSTR_VAL(buf)[bytes] = 0; RETURN_STR(buf); } } static PHP_METHOD(swoole_socket_coro, sendto) { char *data; size_t l_data; char *addr; size_t l_addr; zend_long port = 0; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STRING(addr, l_addr) Z_PARAM_LONG(port) Z_PARAM_STRING(data, l_data) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); ssize_t retval = sock->socket->sendto(std::string(addr, l_addr), port, data, l_data); socket_coro_sync_properties(ZEND_THIS, sock); if (retval < 0) { RETURN_FALSE; } else { RETURN_LONG(retval); } } static PHP_METHOD(swoole_socket_coro, shutdown) { zend_long how = SHUT_RDWR; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(how) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->shutdown(how)) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, close) { swoole_get_socket_coro(sock, ZEND_THIS); if (sock->reference) { php_swoole_error(E_WARNING, "cannot close the referenced resource"); RETURN_FALSE; } if (sock->socket->protocol.private_data_1) { sw_callable_free(sock->socket->protocol.private_data_1); sock->socket->protocol.private_data_1 = nullptr; } if (!Z_ISUNDEF(sock->zstream)) { php_stream *stream = nullptr; php_stream_from_zval_no_verify(stream, &sock->zstream); if (stream != nullptr) { /* close & destroy stream, incl. removing it from the rsrc list; * resource stored in php_sock->zstream will become invalid */ php_stream_free(stream, PHP_STREAM_FREE_KEEP_RSRC | PHP_STREAM_FREE_CLOSE | (stream->is_persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : 0)); } ZVAL_UNDEF(&sock->zstream); sock->socket->move_fd(); } else { sock->socket->close(); } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, getsockname) { swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->getsockname()) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } array_init(return_value); add_assoc_string(return_value, "address", sock->socket->get_addr()); add_assoc_long(return_value, "port", sock->socket->get_port()); } static PHP_METHOD(swoole_socket_coro, getpeername) { swoole_get_socket_coro(sock, ZEND_THIS); Address sa; if (!sock->socket->getpeername(&sa)) { socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } array_init(return_value); add_assoc_string(return_value, "address", sa.get_addr()); add_assoc_long(return_value, "port", sa.get_port()); } static PHP_METHOD(swoole_socket_coro, getOption) { struct linger linger_val; socklen_t optlen; int other_val; zend_long level, optname; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(level) Z_PARAM_LONG(optname) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); auto _socket = sock->socket->get_socket(); if (level == IPPROTO_IP) { switch (optname) { case IP_MULTICAST_IF: { struct in_addr if_addr; unsigned int if_index; optlen = sizeof(if_addr); if (_socket->get_option(level, optname, &if_addr, &optlen) != 0) { php_swoole_sys_error(E_WARNING, "getsockopt(%d, " ZEND_LONG_FMT ", " ZEND_LONG_FMT ")", sock->socket->get_fd(), level, optname); RETURN_FALSE; } if (php_add4_to_if_index(&if_addr, sock->socket, &if_index) == SUCCESS) { RETURN_LONG((zend_long) if_index); } else { RETURN_FALSE; } } default: break; } } else if (level == IPPROTO_IPV6) { int ret = php_do_getsockopt_ipv6_rfc3542(sock->socket, level, optname, return_value); if (ret == SUCCESS) { return; } else if (ret == FAILURE) { RETURN_FALSE; } /* else continue */ } /* sol_socket options and general case */ switch (optname) { case SO_LINGER: { optlen = sizeof(linger_val); if (_socket->get_option(level, optname, (char *) &linger_val, &optlen) != 0) { php_swoole_sys_error(E_WARNING, "getsockopt(%d, " ZEND_LONG_FMT ", " ZEND_LONG_FMT ")", sock->socket->get_fd(), level, optname); RETURN_FALSE; } array_init(return_value); add_assoc_long(return_value, "l_onoff", linger_val.l_onoff); add_assoc_long(return_value, "l_linger", linger_val.l_linger); break; } case SO_RCVTIMEO: case SO_SNDTIMEO: { double timeout = sock->socket->get_timeout(optname == SO_RCVTIMEO ? SW_TIMEOUT_READ : SW_TIMEOUT_WRITE); array_init(return_value); int sec = (int) timeout; add_assoc_long(return_value, "sec", (int) timeout); add_assoc_long(return_value, "usec", (timeout - (double) sec) * 1000000); break; } #ifdef TCP_INFO case TCP_INFO: { tcp_info info; socklen_t len = sizeof(info); if (_socket->get_option(SOL_TCP, TCP_INFO, &info, &len) < 0) { php_swoole_sys_error(E_WARNING, "getsockopt(%d, SOL_TCP, TCP_INFO)", sock->socket->get_fd()); } else { array_init(return_value); auto info_map = sw_socket_parse_tcp_info(&info); for (const auto &iter : info_map) { add_assoc_long_ex(return_value, iter.first.c_str(), iter.first.length(), (zend_long) iter.second); } } break; } #endif default: { optlen = sizeof(other_val); if (_socket->get_option(level, optname, (char *) &other_val, &optlen) != 0) { php_swoole_sys_error(E_WARNING, "getsockopt(%d, " ZEND_LONG_FMT ", " ZEND_LONG_FMT ")", sock->socket->get_fd(), level, optname); RETURN_FALSE; } if (optlen == 1) { char _val; memcpy(&_val, &other_val, optlen); RETURN_LONG(_val); } else { RETURN_LONG(other_val); } break; } } } static PHP_METHOD(swoole_socket_coro, setOption) { zval *optval; struct linger lv; int ov, optlen, retval; struct timeval tv; zend_long level, optname; char *opt_ptr; HashTable *opt_ht; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_LONG(level) Z_PARAM_LONG(optname) Z_PARAM_ZVAL(optval) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); uint32_t optval_arg_index = 3; #define HANDLE_SUBCALL(res) \ do { \ if (res == 1) { \ goto default_case; \ } else if (res == SUCCESS) { \ RETURN_TRUE; \ } else { \ RETURN_FALSE; \ } \ } while (0) if (level == IPPROTO_IP) { int res = php_do_setsockopt_ip_mcast(sock->socket, level, optname, optval); HANDLE_SUBCALL(res); } else if (level == IPPROTO_IPV6) { int res = php_do_setsockopt_ipv6_mcast(sock->socket, level, optname, optval); if (res == 1) { res = php_do_setsockopt_ipv6_rfc3542(sock->socket, level, optname, optval); } HANDLE_SUBCALL(res); } switch (optname) { case SO_LINGER: { const char l_onoff_key[] = "l_onoff"; const char l_linger_key[] = "l_linger"; if (Z_TYPE_P(optval) != IS_ARRAY) { if (UNEXPECTED(Z_TYPE_P(optval) != IS_OBJECT)) { zend_argument_type_error(optval_arg_index, "must be of type array when argument $option is SO_LINGER, %s given", zend_zval_value_name(optval)); RETURN_THROWS(); } else { opt_ht = Z_OBJPROP_P(optval); } } else { opt_ht = Z_ARRVAL_P(optval); } zval *l_onoff, *l_linger; if ((l_onoff = zend_hash_str_find(opt_ht, l_onoff_key, sizeof(l_onoff_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", l_onoff_key); RETURN_FALSE; } if ((l_linger = zend_hash_str_find(opt_ht, l_linger_key, sizeof(l_linger_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", l_linger_key); RETURN_FALSE; } zend_long val_lonoff = zval_get_long(l_onoff); zend_long val_linger = zval_get_long(l_linger); if (val_lonoff < 0 || val_lonoff > USHRT_MAX) { zend_argument_value_error(optval_arg_index, "\"%s\" must be between 0 and %u", l_onoff_key, USHRT_MAX); RETURN_THROWS(); } if (val_linger < 0 || val_linger > USHRT_MAX) { zend_argument_value_error(optval_arg_index, "\"%s\" must be between 0 and %d", l_linger, USHRT_MAX); RETURN_THROWS(); } lv.l_onoff = (unsigned short) val_lonoff; lv.l_linger = (unsigned short) val_linger; optlen = sizeof(lv); opt_ptr = (char *) &lv; break; } case SO_RCVTIMEO: case SO_SNDTIMEO: { constexpr char sec_key[] = "sec"; constexpr char usec_key[] = "usec"; zval *sec, *usec; if (Z_TYPE_P(optval) != IS_ARRAY) { if (UNEXPECTED(Z_TYPE_P(optval) != IS_OBJECT)) { zend_argument_type_error(optval_arg_index, "must be of type array when argument $option is %s, %s given", optname == SO_RCVTIMEO ? "SO_RCVTIMEO" : "SO_SNDTIMEO", zend_zval_value_name(optval)); RETURN_THROWS(); } else { opt_ht = Z_OBJPROP_P(optval); } } else { opt_ht = Z_ARRVAL_P(optval); } opt_ht = Z_ARRVAL_P(optval); if ((sec = zend_hash_str_find(opt_ht, sec_key, sizeof(sec_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", sec_key); RETURN_FALSE; } if ((usec = zend_hash_str_find(opt_ht, usec_key, sizeof(usec_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", usec_key); RETURN_FALSE; } zend_long valsec = zval_get_long(sec); zend_long valusec = zval_get_long(usec); tv.tv_sec = valsec; tv.tv_usec = valusec; sock->socket->set_timeout(&tv, optname == SO_RCVTIMEO ? SW_TIMEOUT_READ : SW_TIMEOUT_CONNECT | SW_TIMEOUT_WRITE); RETURN_TRUE; break; } #ifdef SO_BINDTODEVICE case SO_BINDTODEVICE: { if (Z_TYPE_P(optval) == IS_STRING) { opt_ptr = Z_STRVAL_P(optval); optlen = Z_STRLEN_P(optval); } else { opt_ptr = (char *) ""; optlen = 0; } break; } #endif default: { default_case: ov = zval_get_long(optval); optlen = sizeof(ov); opt_ptr = (char *) &ov; break; } } retval = sock->socket->get_socket()->set_option(level, optname, opt_ptr, optlen); if (retval != 0) { php_swoole_sys_error(E_WARNING, "setsockopt(%d) failed", sock->socket->get_fd()); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_socket_coro, cancel) { swoole_get_socket_coro(sock, ZEND_THIS); zend_long event = SW_EVENT_READ; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(event) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(sock->socket->cancel(event == SW_EVENT_READ ? SW_EVENT_READ : SW_EVENT_WRITE)); } static PHP_METHOD(swoole_socket_coro, setProtocol) { swoole_get_socket_coro(sock, ZEND_THIS); zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (php_swoole_array_length(zset) == 0) { RETURN_FALSE; } else { RETURN_BOOL(php_swoole_socket_set_protocol(sock->socket, zset)); } } static PHP_METHOD(swoole_socket_coro, sslHandshake) { swoole_get_socket_coro(sock, ZEND_THIS); RETURN_BOOL(sock->socket->ssl_handshake()); } static PHP_METHOD(swoole_socket_coro, isClosed) { RETURN_BOOL(php_swoole_socket_is_closed(ZEND_THIS)); } static PHP_METHOD(swoole_socket_coro, import) { zval *zstream; php_stream *stream; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_RESOURCE(zstream) ZEND_PARSE_PARAMETERS_END(); php_stream_from_zval(stream, zstream); swSocketType type = SW_SOCK_TCP; int socket_fd; if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void **) &socket_fd, 1)) { /* error supposedly already shown */ RETURN_FALSE; } int sock_domain = AF_INET, sock_type = SOCK_STREAM; php_sockaddr_storage addr; socklen_t addr_len = sizeof(addr); #ifdef SO_DOMAIN socklen_t sock_domain_len = sizeof(sock_domain); if (getsockopt(socket_fd, SOL_SOCKET, SO_DOMAIN, &sock_domain, &sock_domain_len) == 0) { } else #endif if (getsockname(socket_fd, (struct sockaddr *) &addr, &addr_len) == 0) { sock_domain = addr.ss_family; } else { php_swoole_sys_error(E_WARNING, "getsockname() failed"); RETURN_FALSE; } #ifdef SO_TYPE socklen_t sock_type_len = sizeof(sock_type); if (getsockopt(socket_fd, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len) < 0) { php_swoole_sys_error(E_WARNING, "getsockopt(SOL_SOCKET, SO_TYPE) failed"); RETURN_FALSE; } #endif type = swoole::network::Socket::convert_to_type(sock_domain, sock_type); /* determine blocking mode */ int t = fcntl(socket_fd, F_GETFL); if (t < 0) { php_swoole_sys_error(E_WARNING, "fcntl(F_GETFL) failed"); RETURN_FALSE; } zend_object *object = php_swoole_create_socket_from_fd(socket_fd, type); SocketObject *sock = socket_coro_fetch_object(object); ZVAL_COPY(&sock->zstream, zstream); php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); sock->socket->get_socket()->nonblock = (t & O_NONBLOCK); RETURN_OBJ(object); } ================================================ FILE: ext-src/swoole_sqlite.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "php_swoole_cxx.h" #include "swoole_coroutine.h" #include "php_swoole_sqlite.h" #ifdef SW_USE_SQLITE using swoole::Coroutine; static bool swoole_sqlite_blocking = true; void swoole_sqlite_set_blocking(bool blocking) { if (blocking) { swoole_sqlite_blocking = blocking; return; } int thread_safe_mode = sqlite3_threadsafe(); if (!thread_safe_mode) { swoole_warning("hook sqlite coroutine failed because thread safe mode is single-thread."); return; } swoole_sqlite_blocking = blocking; } int swoole_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_open_v2"); if (!swoole_sqlite_blocking && Coroutine::get_current()) { flags |= SQLITE_OPEN_FULLMUTEX; } int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_open_v2(filename, ppDb, flags, zVfs); }); return result; } int swoole_sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_prepare_v2"); int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_prepare_v2(db, zSql, nByte, ppStmt, pzTail); }); return result; } int swoole_sqlite3_exec( sqlite3 *db, const char *sql, int (*callback)(void *, int, char **, char **), void *argument, char **errmsg) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_exec"); int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_exec(db, sql, callback, argument, errmsg); }); return result; } int swoole_sqlite3_close(sqlite3 *db) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_close"); int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_close(db); }); return result; } int swoole_sqlite3_close_v2(sqlite3 *db) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_close_v2"); int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_close_v2(db); }); return result; } int swoole_sqlite3_step(sqlite3_stmt *stmt) { swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_step"); int result = 0; php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_step(stmt); }); return result; } void php_swoole_sqlite_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("SQLITE_ATTR_OPEN_FLAGS")) == nullptr) { #ifdef SQLITE_DETERMINISTIC #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); #else REGISTER_PDO_CLASS_CONST_LONG("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); #endif #endif #if PHP_VERSION_ID >= 80500 REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_CREATE", (zend_long) SQLITE_OPEN_CREATE); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_EXTENDED_RESULT_CODES", (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); #else REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_CREATE", (zend_long) SQLITE_OPEN_CREATE); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_EXTENDED_RESULT_CODES", (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); #endif } php_pdo_unregister_driver(&swoole_pdo_sqlite_driver); php_pdo_register_driver(&swoole_pdo_sqlite_driver); } void php_swoole_sqlite_mshutdown() { php_pdo_unregister_driver(&swoole_pdo_sqlite_driver); } #endif ================================================ FILE: ext-src/swoole_stdext.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #ifdef SW_STDEXT #include "php_swoole_stdext.h" #include "php_swoole_cxx.h" #include "php_variables.h" SW_EXTERN_C_BEGIN #include "ext/pcre/php_pcre.h" #include "ext/json/php_json.h" #include "thirdparty/php/zend/zend_execute.c" SW_EXTERN_C_END enum HashFlag { HASH_FLAG_TYPED_ARRAY = (1 << 12), }; /** * This module aims to enhance the PHP standard library without modifying the php-src core code. * It seeks to introduce strongly-typed arrays and enable the use of built-in methods directly on arrays and strings, * instead of relying on array_* or str_* functions. */ struct CallInfo { zval func; zval this_; uint8_t op1_type; }; struct ArrayTypeValue { uint8_t type_of_value; uint8_t type_of_key; uint16_t offset_of_value_type_str; uint16_t len_of_value_type_str; bool parse(const char *type_str, size_t len_of_type_str); }; struct ArrayTypeInfo { ArrayTypeValue self; ArrayTypeValue element; zend_class_entry *value_ce; uint16_t len_of_type_str; char type_str[0]; bool parse(zend_string *type_def); bool equals(const ArrayTypeInfo *other) const { return self.type_of_key == other->self.type_of_key && self.type_of_value == other->self.type_of_value && len_of_type_str == other->len_of_type_str && memcmp(type_str, other->type_str, len_of_type_str) == 0; } bool element_type_equals(const ArrayTypeInfo *element_array_type_info) const { return element_array_type_info->get_type_of_key() == element.type_of_key && element_array_type_info->get_type_of_value() == element.type_of_value && element_array_type_info->len_of_type_str == get_len_of_value_type_str() && memcmp(element_array_type_info->type_str, get_value_type_str(), get_len_of_value_type_str()) == 0; } const char *get_value_type_str() const { return type_str + self.offset_of_value_type_str; } uint16_t get_len_of_type_str() const { return len_of_type_str; } uint16_t get_len_of_value_type_str() const { return self.len_of_value_type_str; } uint8_t get_type_of_key() const { return self.type_of_key; } uint8_t get_type_of_value() const { return self.type_of_value; } bool is_list() const { return self.type_of_key == 0; } bool value_is_bool() const { return self.type_of_value == IS_TRUE || self.type_of_value == IS_FALSE; } bool value_is_object() const { return self.type_of_value == IS_OBJECT; } bool value_is_array() const { return self.type_of_value == IS_ARRAY; } bool value_is_string() const { return self.type_of_value == IS_STRING; } bool value_is_numeric() const { return self.type_of_value == IS_LONG || self.type_of_value == IS_DOUBLE; } bool instance_of(const zval *value) const { return instanceof_function(Z_OBJCE_P(value), value_ce); } bool check(const zend_array *ht, const zval *key, const zval *value) const; ArrayTypeInfo *dup() const { const auto copy = static_cast(emalloc(sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1)); memcpy(copy, this, sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1); return copy; } }; static zend_function *fn_swoole_call_array_method = nullptr; static zend_function *fn_swoole_call_string_method = nullptr; static zend_function *fn_swoole_call_stream_method = nullptr; static zend_function *fn_array_push = nullptr; static zend_function *fn_array_unshift = nullptr; static zend_function *fn_array_splice = nullptr; static zif_handler ori_handler_array_push; static zif_handler ori_handler_array_unshift; static zif_handler ori_handler_array_splice; static int opcode_handler_array_assign(zend_execute_data *execute_data); static int opcode_handler_array_assign_op(zend_execute_data *execute_data); static int opcode_handler_array_unset(zend_execute_data *execute_data); static int opcode_handler_foreach_begin(zend_execute_data *execute_data); static int opcode_handler_method_call(zend_execute_data *execute_data); static ArrayTypeInfo *get_type_info(zend_array *array); static PHP_FUNCTION(swoole_array_push); static PHP_FUNCTION(swoole_array_unshift); static PHP_FUNCTION(swoole_array_splice); static bool is_typed_array(const zval *zval) { return HT_FLAGS(Z_ARRVAL_P(zval)) & HASH_FLAG_TYPED_ARRAY; } static std::unordered_map array_methods = { {"all", "array_all"}, {"any", "array_any"}, {"changeKeyCase", "array_change_key_case"}, {"chunk", "array_chunk"}, {"column", "array_column"}, {"countValues", "array_count_values"}, {"diff", "array_diff"}, {"diffAssoc", "array_diff_assoc"}, {"diffKey", "array_diff_key"}, {"filter", "array_filter"}, {"find", "array_find"}, {"flip", "array_flip"}, {"intersect", "array_intersect"}, {"intersectAssoc", "array_intersect_assoc"}, {"isList", "array_is_list"}, {"keyExists", "swoole_array_key_exists"}, {"keyFirst", "array_key_first"}, {"keyLast", "array_key_last"}, {"keys", "array_keys"}, {"map", "swoole_array_map"}, {"pad", "array_pad"}, {"product", "array_product"}, {"rand", "array_rand"}, {"reduce", "array_reduce"}, {"replace", "array_replace"}, {"reverse", "array_reverse"}, {"search", "swoole_array_search"}, {"slice", "array_slice"}, {"sum", "array_sum"}, {"unique", "array_unique"}, {"values", "array_values"}, {"count", "count"}, {"merge", "array_merge"}, {"contains", "swoole_array_contains"}, {"join", "swoole_array_join"}, {"isTyped", "swoole_array_is_typed"}, {"isEmpty", "swoole_array_is_empty"}, // pass by ref {"sort", "sort"}, {"pop", "array_pop"}, {"push", "array_push"}, {"shift", "array_shift"}, {"unshift", "array_unshift"}, {"splice", "array_splice"}, {"walk", "array_walk"}, {"replaceStr", "swoole_array_replace_str"}, {"iReplaceStr", "swoole_array_ireplace_str"}, // serialize {"serialize", "serialize"}, {"marshal", "serialize"}, {"jsonEncode", "json_encode"}, }; /** * i=ignore case, l=left, r=right */ static std::unordered_map string_methods = { {"length", "strlen"}, {"isEmpty", "swoole_str_is_empty"}, {"lower", "strtolower"}, {"upper", "strtoupper"}, {"lowerFirst", "lcfirst"}, {"upperFirst", "ucfirst"}, {"upperWords", "ucwords"}, {"addCSlashes", "addcslashes"}, {"addSlashes", "addslashes"}, {"chunkSplit", "chunk_split"}, {"countChars", "count_chars"}, {"htmlEntityDecode", "html_entity_decode"}, {"htmlEntityEncode", "htmlentities"}, {"htmlSpecialCharsEncode", "htmlspecialchars"}, {"htmlSpecialCharsDecode", "htmlspecialchars_decode"}, {"trim", "trim"}, {"lTrim", "ltrim"}, {"rTrim", "rtrim"}, {"parseStr", "swoole_parse_str"}, {"parseUrl", "parse_url"}, {"contains", "str_contains"}, {"incr", "str_increment"}, {"decr", "str_decrement"}, {"pad", "str_pad"}, {"repeat", "str_repeat"}, {"replace", "swoole_str_replace"}, {"iReplace", "swoole_str_ireplace"}, {"shuffle", "str_shuffle"}, {"split", "swoole_str_split"}, // explode {"startsWith", "str_starts_with"}, {"endsWith", "str_ends_with"}, {"wordCount", "str_word_count"}, {"iCompare", "strcasecmp"}, {"compare", "strcmp"}, {"find", "strstr"}, {"iFind", "stristr"}, {"stripTags", "strip_tags"}, {"stripCSlashes", "stripcslashes"}, {"stripSlashes", "stripslashes"}, {"iIndexOf", "stripos"}, {"indexOf", "strpos"}, {"lastIndexOf", "strrpos"}, {"iLastIndexOf", "strripos"}, {"lastCharIndexOf", "strrchr"}, {"substr", "substr"}, {"substrCompare", "substr_compare"}, {"substrCount", "substr_count"}, {"substrReplace", "substr_replace"}, {"reverse", "strrev"}, {"md5", "md5"}, {"sha1", "sha1"}, {"crc32", "crc32"}, {"hash", "swoole_hash"}, {"hashCode", "swoole_hashcode"}, {"base64Decode", "base64_decode"}, {"base64Encode", "base64_encode"}, {"urlDecode", "urldecode"}, {"urlEncode", "urlencode"}, {"rawUrlEncode", "rawurlencode"}, {"rawUrlDecode", "rawurldecode"}, {"match", "swoole_str_match"}, {"matchAll", "swoole_str_match_all"}, {"isNumeric", "is_numeric"}, // mbstring {"mbUpperFirst", "mb_ucfirst"}, {"mbLowerFirst", "mb_lcfirst"}, {"mbTrim", "mb_trim"}, {"mbSubstrCount", "mb_substr_count"}, {"mbSubstr", "mb_substr"}, {"mbUpper", "mb_strtoupper"}, {"mbLower", "mb_strtolower"}, {"mbFind", "mb_strstr"}, {"mbIndexOf", "mb_strpos"}, {"mbLastIndexOf", "mb_strrpos"}, {"mbILastIndexOf", "mb_strripos"}, {"mbLastCharIndexOf", "mb_strrchr"}, {"mbILastCharIndex", "mb_strrichr"}, {"mbLength", "mb_strlen"}, {"mbIFind", "mb_stristr"}, {"mbIIndexOf", "mb_stripos"}, {"mbCut", "mb_strcut"}, {"mbRTrim", "mb_rtrim"}, {"mbLTrim", "mb_ltrim"}, {"mbDetectEncoding", "mb_detect_encoding"}, {"mbConvertEncoding", "mb_convert_encoding"}, {"mbConvertCase", "mb_convert_case"}, // serialize {"unserialize", "unserialize"}, {"unmarshal", "unserialize"}, {"jsonDecode", "swoole_str_json_decode"}, {"jsonDecodeToObject", "swoole_str_json_decode_to_object"}, }; static std::unordered_map stream_methods = { {"write", "fwrite"}, {"read", "fread"}, {"close", "fclose"}, {"dataSync", "fdatasync"}, {"sync", "fsync"}, {"truncate", "ftruncate"}, {"stat", "fstat"}, {"seek", "fseek"}, {"tell", "ftell"}, {"lock", "flock"}, {"eof", "feof"}, {"getChar", "fgetc"}, {"getLine", "fgets"}, }; static void move_first_element(const zval src[], zval dst[], int size, int position) { zval first = src[0]; for (int i = 0; i < position; i++) { dst[i] = src[i + 1]; } dst[position] = first; for (int i = position + 1; i < size; i++) { dst[i] = src[i]; } } static void call_func_move_first_arg(zend_function *fn, zend_execute_data *execute_data, zval *retval, int position) { const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); const auto argv = static_cast(ecalloc(arg_count, sizeof(zval))); move_first_element(arg_ptr, argv, arg_count, position); zend_call_known_function(fn, nullptr, nullptr, retval, arg_count, argv, nullptr); efree(argv); } static void call_method(const std::unordered_map &method_map, zend_execute_data *execute_data, zval *retval) { const auto call_info = reinterpret_cast(execute_data->run_time_cache); const auto name = std::string(Z_STRVAL(call_info->func), Z_STRLEN(call_info->func)); const auto iter = method_map.find(name); ON_SCOPE_EXIT { efree(call_info); execute_data->run_time_cache = nullptr; }; if (iter == method_map.end()) { zend_throw_error( nullptr, "The method `%s` is undefined on %s", name.c_str(), zend_zval_type_name(&call_info->this_)); return; } const auto real_fn = iter->second; const auto fn = zend::get_function(real_fn.c_str(), real_fn.length()); if (!fn) { zend_throw_error(nullptr, "The function `%s` is undefined", real_fn.c_str()); return; } const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); const auto argv = static_cast(ecalloc(arg_count + 1, sizeof(zval))); argv[0] = call_info->this_; for (int i = 0; i < arg_count; i++) { argv[i + 1] = arg_ptr[i]; } zend_call_known_function(fn, nullptr, nullptr, retval, arg_count + 1, argv, nullptr); if (call_info->op1_type == IS_VAR) { zval_ptr_dtor(&call_info->this_); } efree(argv); } static int opcode_handler_method_call(zend_execute_data *execute_data) { const zend_op *opline = EX(opline); zval *object; if (opline->op1_type == IS_CONST) { object = RT_CONSTANT(opline, opline->op1); } else if (UNEXPECTED(opline->op1_type == IS_UNUSED)) { return ZEND_USER_OPCODE_DISPATCH; } else { object = EX_VAR(opline->op1.var); } auto type = Z_TYPE_P(object); if (type == IS_REFERENCE) { type = Z_TYPE_P(Z_REFVAL_P(object)); } if (type == IS_ARRAY || type == IS_STRING || type == IS_RESOURCE) { auto call_info = static_cast(emalloc(sizeof(CallInfo))); call_info->func = *RT_CONSTANT(opline, opline->op2); call_info->this_ = *object; call_info->op1_type = opline->op1_type; zend_function *fbc = nullptr; switch (type) { case IS_ARRAY: fbc = fn_swoole_call_array_method; break; case IS_STRING: fbc = fn_swoole_call_string_method; break; default: fbc = fn_swoole_call_stream_method; } zend_execute_data *call = zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, object); call->run_time_cache = reinterpret_cast(call_info); call->prev_execute_data = EX(call); EX(call) = call; EX(opline)++; return ZEND_USER_OPCODE_CONTINUE; } return ZEND_USER_OPCODE_DISPATCH; } void php_swoole_stdext_minit(int module_number) { zend_set_user_opcode_handler(ZEND_INIT_METHOD_CALL, opcode_handler_method_call); zend_set_user_opcode_handler(ZEND_ASSIGN_DIM, opcode_handler_array_assign); zend_set_user_opcode_handler(ZEND_ASSIGN_DIM_OP, opcode_handler_array_assign_op); zend_set_user_opcode_handler(ZEND_UNSET_DIM, opcode_handler_array_unset); zend_set_user_opcode_handler(ZEND_FE_RESET_RW, opcode_handler_foreach_begin); fn_swoole_call_array_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_array_method")); fn_swoole_call_string_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_string_method")); fn_swoole_call_stream_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_stream_method")); fn_array_push = zend::get_function(CG(function_table), ZEND_STRL("array_push")); fn_array_unshift = zend::get_function(CG(function_table), ZEND_STRL("array_unshift")); fn_array_splice = zend::get_function(CG(function_table), ZEND_STRL("array_splice")); ori_handler_array_push = fn_array_push->internal_function.handler; fn_array_push->internal_function.handler = ZEND_FN(swoole_array_push); ori_handler_array_unshift = fn_array_unshift->internal_function.handler; fn_array_unshift->internal_function.handler = ZEND_FN(swoole_array_unshift); ori_handler_array_splice = fn_array_splice->internal_function.handler; fn_array_splice->internal_function.handler = ZEND_FN(swoole_array_splice); } #define SW_CREATE_PHP_FUNCTION_WRAPPER(php_func_name, swoole_func_name, position) \ PHP_FUNCTION(swoole_func_name) { \ static zend_function *fn_##swoole_func_name = nullptr; \ if (!fn_##swoole_func_name) { \ fn_##swoole_func_name = zend::get_function(CG(function_table), ZEND_STRL(#php_func_name)); \ } \ call_func_move_first_arg(fn_##swoole_func_name, execute_data, return_value, position); \ } // array SW_CREATE_PHP_FUNCTION_WRAPPER(array_search, swoole_array_search, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(in_array, swoole_array_contains, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(implode, swoole_array_join, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(array_key_exists, swoole_array_key_exists, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(array_map, swoole_array_map, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_array_replace_str, 2); SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_array_ireplace_str, 2); // string SW_CREATE_PHP_FUNCTION_WRAPPER(explode, swoole_str_split, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(hash, swoole_hash, 1); SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_str_replace, 2); SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_str_ireplace, 2); PHP_FUNCTION(swoole_parse_str) { char *arg; size_t arglen; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(arg, arglen) ZEND_PARSE_PARAMETERS_END(); array_init(return_value); auto res = estrndup(arg, arglen); sapi_module.treat_data(PARSE_STRING, res, return_value); } PHP_FUNCTION(swoole_str_is_empty) { zend_string *str; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(str) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(str->len == 0); } PHP_FUNCTION(swoole_array_is_empty) { zval *array; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(array) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(zend_array_count(Z_ARRVAL_P(array)) == 0); } PHP_FUNCTION(swoole_call_array_method) { call_method(array_methods, execute_data, return_value); } PHP_FUNCTION(swoole_call_string_method) { call_method(string_methods, execute_data, return_value); } PHP_FUNCTION(swoole_call_stream_method) { call_method(stream_methods, execute_data, return_value); } static HashTable *make_typed_array(const uint32_t nSize, const uint32_t nTypeStr) { const auto ht = static_cast(emalloc(sizeof(HashTable) + sizeof(ArrayTypeInfo) + nTypeStr + 1)); _zend_hash_init(ht, nSize, ZVAL_PTR_DTOR, false); HT_FLAGS(ht) |= HASH_FLAG_TYPED_ARRAY; return ht; } void copy_array_type_info(zval *container, zend_array *src) { auto src_type_info = get_type_info(src); zend_array *ht = Z_ARRVAL_P(container); auto extra_size = sizeof(ArrayTypeInfo) + src_type_info->get_len_of_type_str() + 1; const auto tmp = static_cast(emalloc(sizeof(HashTable) + extra_size)); memcpy(tmp, ht, sizeof(HashTable)); memcpy(reinterpret_cast(tmp) + sizeof(HashTable), src_type_info, extra_size); HT_FLAGS(tmp) |= HASH_FLAG_TYPED_ARRAY; Z_ARRVAL_P(container) = tmp; efree(ht); } static ArrayTypeInfo *get_type_info(zend_array *array) { return reinterpret_cast(reinterpret_cast(array) + sizeof(HashTable)); } static zend_string *get_array_type_def(const ArrayTypeInfo *info) { zend_string *result = zend_string_alloc(info->get_len_of_value_type_str() + 16, false); char *p = result->val; *p = '<'; if (info->get_type_of_key() == IS_STRING) { p++; strcpy(p, "string,"); p += 7; } else if (info->get_type_of_key() == IS_LONG) { p++; strcpy(p, "int,"); p += 4; } memcpy(p, info->get_value_type_str(), info->get_len_of_value_type_str()); p += info->get_len_of_value_type_str(); *p = '>'; p++; *p = '\0'; result->len = p - result->val; return result; } bool ArrayTypeInfo::check(const zend_array *ht, const zval *key, const zval *value) const { if (get_type_of_key() > 0) { if (Z_TYPE_P(key) != get_type_of_key()) { zend_type_error("Array key type mismatch, expected `%s`, got `%s`", zend_get_type_by_const(get_type_of_key()), zend_get_type_by_const(Z_TYPE_P(key))); return false; } } else { if (Z_TYPE_P(key) == IS_LONG) { if (Z_LVAL_P(key) > zend_hash_num_elements(ht)) { zend_throw_error( nullptr, "Incorrect array key `%ld`, out of the permitted range", (long) Z_LVAL_P(key)); return false; } } else if (!(Z_TYPE_P(key) == IS_UNDEF || Z_TYPE_P(key) == IS_NULL)) { zend_throw_error(nullptr, "Incorrect array key, must be undef or int"); return false; } } ZVAL_DEREF(value); if (value_is_bool() && ZVAL_IS_BOOL(value)) { return true; } if (Z_TYPE_P(value) != get_type_of_value()) { zend_type_error("Array value type mismatch, expected `%s`, got `%s`", zend_get_type_by_const(get_type_of_value()), zend_get_type_by_const(Z_TYPE_P(value))); return false; } if (value_is_object() && !instance_of(value)) { zend_type_error( "Array value type mismatch, expected `%s`, got `%s`", value_ce->name->val, Z_OBJCE_P(value)->name->val); return false; } if (value_is_array()) { const auto element_array_type_info = get_type_info(Z_ARRVAL_P(value)); const auto element_ht = Z_ARRVAL_P(value); if (!(HT_FLAGS(element_ht) & HASH_FLAG_TYPED_ARRAY)) { zend_type_error("Array value type mismatch, expected `%.*s`, got `array`", get_len_of_value_type_str(), get_value_type_str()); return false; } if (!element_type_equals(element_array_type_info)) { const auto element_type_str = get_array_type_def(element_array_type_info); zend_type_error("Array value type mismatch, expected `%.*s`, got `%.*s`", get_len_of_value_type_str(), get_value_type_str(), (int) ZSTR_LEN(element_type_str), ZSTR_VAL(element_type_str)); zend_string_release(element_type_str); return false; } } return true; } static zval *get_array_on_opline(const zend_op *opline EXECUTE_DATA_DC) { auto array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (ZVAL_IS_REF(array)) { array = Z_REFVAL_P(array); } if (!ZVAL_IS_ARRAY(array)) { return nullptr; } return array; } #ifdef DEBUG static void debug_val(const char *tag, int op_type, zval *value) { printf("[%s] refcount=%d, op1_type=%d, type=%s, refcounted=%d\n", tag, Z_REFCOUNTED_P(value) ? Z_REFCOUNT_P(value) : 0, op_type, zend_get_type_by_const(Z_TYPE_P(value)), Z_REFCOUNTED_P(value)); } #else #define debug_val(tag, op_type, value) #endif // In a release version, this function suddenly changes from static to ZEND_API. // We don't know which version it is. In principle, the ZEND_API should not be changed in the release version, // but PHP still does so, which is against the R&D specification. We have to copy the code of this function once. #define zend_cannot_add_element sw_zend_cannot_add_element static zend_never_inline ZEND_COLD void ZEND_FASTCALL sw_zend_cannot_add_element(void) { zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied"); } static void array_add_or_update(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { zval *var_ptr; HashTable *source = Z_ARRVAL_P(container); SEPARATE_ARRAY(container); if (source != Z_ARRVAL_P(container)) { copy_array_type_info(container, source); } HashTable *ht = Z_ARRVAL_P(container); const zend_op *op_data = opline + 1; if (ZVAL_IS_NULL(key)) { var_ptr = zend_hash_next_index_insert(ht, value); if (UNEXPECTED(!var_ptr)) { zend_cannot_add_element(); goto assign_dim_op_ret_null; } } else { zval *variable_ptr; if (opline->op2_type == IS_CONST) { variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); } else { variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == nullptr)) { goto assign_dim_op_ret_null; } debug_val("1", op_data->op1_type, value); var_ptr = zend_assign_to_variable(variable_ptr, value, op_data->op1_type, EX_USES_STRICT_TYPES()); debug_val("2", op_data->op1_type, value); if (UNEXPECTED(!var_ptr)) { assign_dim_op_ret_null: FREE_OP(op_data->op1_type, op_data->op1.var); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_NULL(EX_VAR(opline->result.var)); } return; } } debug_val("3", op_data->op1_type, value); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } if (op_data->op1_type == IS_VAR) { Z_TRY_ADDREF_P(value); } FREE_OP(op_data->op1_type, op_data->op1.var); debug_val("4", op_data->op1_type, value); } static void array_op(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { HashTable *source = Z_ARRVAL_P(container); SEPARATE_ARRAY(container); if (source != Z_ARRVAL_P(container)) { copy_array_type_info(container, source); } const auto type_info = get_type_info(Z_ARRVAL_P(container)); zval *variable_ptr; if ((opline + 1)->op1_type == IS_CONST) { variable_ptr = zend_fetch_dimension_address_inner_RW_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); } else { variable_ptr = zend_fetch_dimension_address_inner_RW(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == nullptr)) { assign_dim_op_ret_null: FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_NULL(EX_VAR(opline->result.var)); } return; } do { if (UNEXPECTED(Z_ISREF_P(variable_ptr))) { zend_reference *ref = Z_REF_P(variable_ptr); variable_ptr = Z_REFVAL_P(variable_ptr); if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { zend_binary_assign_op_typed_ref(ref, value OPLINE_CC EXECUTE_DATA_CC); break; } } const auto opcode = opline->extended_value; if (opcode == ZEND_CONCAT) { if (!type_info->value_is_string()) { zend_type_error("Only string support concat operation"); goto assign_dim_op_ret_null; } } else { if (!type_info->value_is_numeric()) { zend_type_error("Only int or float support arithmetic operation"); goto assign_dim_op_ret_null; } } zend_binary_op(variable_ptr, variable_ptr, value OPLINE_CC); } while (false); if (UNEXPECTED(RETURN_VALUE_USED(opline))) { ZVAL_COPY(EX_VAR(opline->result.var), variable_ptr); } FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); } typedef std::function ArrayFn; static int opcode_handler_array(zend_execute_data *execute_data, const ArrayFn &fn) { const zend_op *opline = EX(opline); const zend_op *op_data = opline + 1; zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); if (UNEXPECTED(!array)) { return ZEND_USER_OPCODE_DISPATCH; } zend_array *ht = Z_ARRVAL_P(array); if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { return ZEND_USER_OPCODE_DISPATCH; } const auto value = get_op_data_zval_ptr_r(op_data->op1_type, op_data->op1); zval *key; if (opline->op2_type == IS_CONST) { key = RT_CONSTANT(opline, opline->op2); } else if (UNEXPECTED(opline->op2_type == IS_UNUSED)) { key = &EG(uninitialized_zval); } else { key = EX_VAR(opline->op2.var); } const auto type_info = get_type_info(ht); if (!type_info->check(ht, key, value)) { FREE_OP(op_data->op1_type, op_data->op1.var); return ZEND_USER_OPCODE_CONTINUE; } fn(opline, array, key, value EXECUTE_DATA_CC); EX(opline) += 2; return ZEND_USER_OPCODE_CONTINUE; } static int opcode_handler_array_assign(zend_execute_data *execute_data) { return opcode_handler_array(execute_data, array_add_or_update); } static int opcode_handler_array_assign_op(zend_execute_data *execute_data) { return opcode_handler_array(execute_data, array_op); } static int opcode_handler_foreach_begin(zend_execute_data *execute_data) { const zend_op *opline = EX(opline); zval *array; if (opline->op1_type == IS_VAR || opline->op1_type == IS_CV) { array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); } else if (opline->op1_type == IS_CONST) { array = RT_CONSTANT(opline, opline->op1); } else { array = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC); } ZVAL_DEREF(array); if (UNEXPECTED(!array || !ZVAL_IS_ARRAY(array))) { return ZEND_USER_OPCODE_DISPATCH; } const zend_array *ht = Z_ARRVAL_P(array); if (HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY) { zend_throw_error(nullptr, "The type array do not support using references for element value during iteration"); ZVAL_UNDEF(EX_VAR(opline->result.var)); Z_FE_ITER_P(EX_VAR(opline->result.var)) = static_cast(-1); return ZEND_USER_OPCODE_CONTINUE; } return ZEND_USER_OPCODE_DISPATCH; } static int opcode_handler_array_unset(zend_execute_data *execute_data) { const zend_op *opline = EX(opline); const zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); if (!array) { return ZEND_USER_OPCODE_DISPATCH; } zend_array *ht = Z_ARRVAL_P(array); if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { return ZEND_USER_OPCODE_DISPATCH; } const auto type_info = get_type_info(ht); if (type_info->is_list()) { zend_throw_error(nullptr, "The typed array list do not support random deletion of elements"); return ZEND_USER_OPCODE_CONTINUE; } return ZEND_USER_OPCODE_DISPATCH; } static void remove_all_spaces(char **val, uint16_t *len) { if (!*val || *len == 0) { return; } const char *src = *val; char *dst = *val; size_t new_len = 0; for (size_t i = 0; i < *len; i++) { if (!isspace((uchar) *src)) { *dst = *src; dst++; new_len++; } src++; } *len = new_len; } static int8_t get_type(const char *val, size_t len) { if (SW_STRCASEEQ(val, len, "int")) { return IS_LONG; } else if (SW_STRCASEEQ(val, len, "float")) { return IS_DOUBLE; } else if (SW_STRCASEEQ(val, len, "string")) { return IS_STRING; } else if (SW_STRCASEEQ(val, len, "bool")) { return IS_TRUE; // IS_TRUE or IS_FALSE } else if (val[0] == '<' && val[len - 1] == '>') { return IS_ARRAY; } else if (SW_STRCASEEQ(val, len, "resource")) { return IS_RESOURCE; } else if (SW_STRCASEEQ(val, len, "null")) { return IS_NULL; } else { return IS_OBJECT; } } bool ArrayTypeValue::parse(const char *type_str, const size_t len_of_type_str) { auto pos = strchr(type_str, ','); if (pos == nullptr) { type_of_key = 0; offset_of_value_type_str = 1; } else { type_of_key = get_type(type_str + 1, pos - type_str - 1); if (type_of_key != IS_STRING && type_of_key != IS_LONG) { zend_throw_error(nullptr, "The key type of array must be string or int, but got %s", pos + 1); return false; } offset_of_value_type_str = pos - type_str + 1; } len_of_value_type_str = len_of_type_str - offset_of_value_type_str - 1; type_of_value = get_type(type_str + offset_of_value_type_str, len_of_value_type_str); return true; } bool ArrayTypeInfo::parse(zend_string *type_def) { if (type_def->len >= 65535) { zend_throw_error(nullptr, "The type definition string is too long (must be less than 65535 characters)"); return false; } zend_string *lc_type_def = zend_string_tolower(type_def); memcpy(type_str, lc_type_def->val, lc_type_def->len + 1); len_of_type_str = lc_type_def->len; zend_string_release(lc_type_def); char *tmp_type_str = type_str; remove_all_spaces(&tmp_type_str, &len_of_type_str); tmp_type_str[len_of_type_str] = '\0'; if (tmp_type_str != type_str) { memmove(type_str, tmp_type_str, len_of_type_str + 1); } if (type_str[0] != '<' || type_str[len_of_type_str - 1] != '>') { zend_throw_error(nullptr, "The type definition of typed array must start with '<' and end with '>'"); return false; } if (!self.parse(type_str, len_of_type_str)) { return false; } if (self.type_of_value == IS_OBJECT) { zend::String type_str_of_value(type_str + self.offset_of_value_type_str, self.len_of_value_type_str); value_ce = zend_lookup_class(type_str_of_value.get()); if (!value_ce) { zend_throw_error(nullptr, "Class '%s' not found", type_str_of_value.val()); return false; } } return true; } PHP_FUNCTION(swoole_typed_array) { zend_string *type_def; zval *init_values = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(type_def) Z_PARAM_OPTIONAL Z_PARAM_ARRAY(init_values) ZEND_PARSE_PARAMETERS_END(); auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); if (!tmp_info->parse(type_def)) { efree(tmp_info); RETURN_NULL(); } if (init_values && is_typed_array(init_values)) { auto type_info = get_type_info(Z_ARRVAL_P(init_values)); if (tmp_info->equals(type_info)) { ZVAL_COPY(return_value, init_values); } else { zend_throw_error(nullptr, "The type definition of the typed array does not match the initial values"); } efree(tmp_info); return; } auto n = init_values ? zend_array_count(Z_ARRVAL_P(init_values)) : 0; auto array = make_typed_array(n, tmp_info->len_of_type_str); ZVAL_ARR(return_value, array); auto info = get_type_info(array); memcpy(info, tmp_info, sizeof(ArrayTypeInfo) + tmp_info->len_of_type_str + 1); efree(tmp_info); if (info->self.type_of_value == IS_ARRAY) { if (!info->element.parse(info->get_value_type_str(), info->get_len_of_value_type_str())) { zval_ptr_dtor(return_value); RETURN_NULL(); } } if (init_values) { zend_string *str_key; zend_ulong num_key; zval *zv; zval zk; HashTable *ht = Z_ARRVAL_P(init_values); ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) { if (str_key) { ZVAL_STR(&zk, str_key); } else { ZVAL_LONG(&zk, num_key); } if (!info->check(array, &zk, zv)) { zval_ptr_dtor(return_value); RETURN_NULL(); } Z_TRY_ADDREF_P(zv); if (str_key) { zend_hash_add(array, str_key, zv); } else { zend_hash_index_add(array, num_key, zv); } } ZEND_HASH_FOREACH_END(); } } PHP_FUNCTION(swoole_array_is_typed) { zend_string *type_def = nullptr; zval *array; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY(array) Z_PARAM_OPTIONAL Z_PARAM_STR(type_def) ZEND_PARSE_PARAMETERS_END(); HashTable *ht = Z_ARRVAL_P(array); if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { RETURN_FALSE; } if (!type_def) { RETURN_TRUE; } const auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); if (!tmp_info->parse(type_def)) { efree(tmp_info); RETURN_FALSE; } const auto info = get_type_info(ht); RETVAL_BOOL(info->equals(tmp_info)); efree(tmp_info); } static PHP_FUNCTION(swoole_array_push) { zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); zval *array = &arg_ptr[0]; ZVAL_DEREF(array); if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { auto source = Z_ARRVAL_P(array); auto type_info = get_type_info(source); for (int i = 1; i < arg_count; i++) { if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { return; } } } ori_handler_array_push(INTERNAL_FUNCTION_PARAM_PASSTHRU); } static PHP_FUNCTION(swoole_array_unshift) { zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); zval *array = &arg_ptr[0]; ZVAL_DEREF(array); if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { auto source = Z_ARRVAL_P(array); auto type_info = get_type_info(source); for (int i = 1; i < arg_count; i++) { if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { return; } } ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; } else { ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); } } static PHP_FUNCTION(swoole_array_splice) { const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); const zval *array = &arg_ptr[0]; ZVAL_DEREF(array); if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { if (arg_count > 3) { auto type_info = get_type_info(Z_ARRVAL_P(array)); auto values = &arg_ptr[3]; ZVAL_DEREF(values); if (Z_TYPE_P(values) == IS_ARRAY) { zval *zv; HashTable *ht = Z_ARRVAL_P(values); ZEND_HASH_FOREACH_VAL(ht, zv) { if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), zv)) { return; } } ZEND_HASH_FOREACH_END(); } else { if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), values)) { return; } } } const auto source = Z_ARRVAL_P(array); ori_handler_array_splice(execute_data, return_value); HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; } else { ori_handler_array_splice(execute_data, return_value); } } static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ */ { /* parameters */ zend_string *regex; /* Regular expression */ zend_string *subject; /* String to match against */ pcre_cache_entry *pce; /* Compiled regular expression */ zend_long flags = 0; /* Match control flags */ zend_long start_offset = 0; /* Where the new search starts */ ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR(subject) Z_PARAM_STR(regex) Z_PARAM_OPTIONAL Z_PARAM_LONG(flags) Z_PARAM_LONG(start_offset) ZEND_PARSE_PARAMETERS_END(); /* Compile regex or get it from cache. */ if ((pce = pcre_get_compiled_regex_cache(regex)) == nullptr) { RETURN_FALSE; } zval count = {}; php_pcre_pce_incref(pce); #if PHP_VERSION_ID >= 80400 php_pcre_match_impl(pce, subject, &count, return_value, global == 1, flags, start_offset); #else php_pcre_match_impl(pce, subject, &count, return_value, global, ZEND_NUM_ARGS() >= 3, flags, start_offset); #endif php_pcre_pce_decref(pce); } /* }}} */ PHP_FUNCTION(swoole_str_match) { php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } PHP_FUNCTION(swoole_str_match_all) { php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } static void php_do_json_decode(INTERNAL_FUNCTION_PARAMETERS, bool assoc) /* {{{ */ { char *str; size_t str_len; zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; zend_long options = 0; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STRING(str, str_len) Z_PARAM_OPTIONAL Z_PARAM_LONG(depth) Z_PARAM_LONG(options) ZEND_PARSE_PARAMETERS_END(); if (assoc) { options |= PHP_JSON_OBJECT_AS_ARRAY; } else { options &= ~PHP_JSON_OBJECT_AS_ARRAY; } zend::json_decode(return_value, str, str_len, options, depth); } /* }}} */ PHP_FUNCTION(swoole_str_json_decode) { php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } PHP_FUNCTION(swoole_str_json_decode_to_object) { php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } #endif ================================================ FILE: ext-src/swoole_table.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "swoole_table.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_table_arginfo.h" END_EXTERN_C() using namespace swoole; static inline void table_row2array(const Table *table, TableRow *row, zval *return_value) { array_init(return_value); for (const auto col : *table->column_list) { if (col->type == TableColumn::TYPE_STRING) { TableStringLength len = 0; char *str = nullptr; row->get_value(col, &str, &len); add_assoc_stringl_ex(return_value, col->name.c_str(), col->name.length(), str, len); } else if (col->type == TableColumn::TYPE_FLOAT) { double dval = 0; row->get_value(col, &dval); add_assoc_double_ex(return_value, col->name.c_str(), col->name.length(), dval); } else if (col->type == TableColumn::TYPE_INT) { long lval = 0; row->get_value(col, &lval); add_assoc_long_ex(return_value, col->name.c_str(), col->name.length(), lval); } else { abort(); } } } static inline void table_get_field_value(Table *table, TableRow *row, zval *return_value, const zend_string *field) { TableColumn *col = table->get_column(std::string(ZSTR_VAL(field), ZSTR_LEN(field))); if (!col) { ZVAL_FALSE(return_value); return; } if (col->type == TableColumn::TYPE_STRING) { TableStringLength len = 0; char *str = nullptr; row->get_value(col, &str, &len); ZVAL_STRINGL(return_value, str, len); } else if (col->type == TableColumn::TYPE_FLOAT) { double dval = 0; row->get_value(col, &dval); ZVAL_DOUBLE(return_value, dval); } else if (col->type == TableColumn::TYPE_INT) { long lval = 0; row->get_value(col, &lval); ZVAL_LONG(return_value, lval); } else { abort(); } } static zend_class_entry *swoole_table_ce; static zend_object_handlers swoole_table_handlers; struct TableObject { Table *ptr; zend_object std; }; static TableObject *table_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_table_handlers.offset); } static Table *table_get_ptr(const zval *zobject) { return table_fetch_object(Z_OBJ_P(zobject))->ptr; } static Table *table_get_and_check_ptr(const zval *zobject) { Table *table = table_get_ptr(zobject); if (UNEXPECTED(!table)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return table; } static Table *table_get_and_check_ptr2(const zval *zobject) { Table *table = table_get_and_check_ptr(zobject); if (!table->ready()) { php_swoole_fatal_error(E_ERROR, "table is not created or has been destroyed"); } return table; } static void table_set_ptr(const zval *zobject, Table *ptr) { table_fetch_object(Z_OBJ_P(zobject))->ptr = ptr; } static void table_free_object(zend_object *object) { zend_object_std_dtor(object); } static zend_object *table_create_object(zend_class_entry *ce) { auto *table = static_cast(zend_object_alloc(sizeof(TableObject), ce)); zend_object_std_init(&table->std, ce); object_properties_init(&table->std, ce); table->std.handlers = &swoole_table_handlers; return &table->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_table, __construct); static PHP_METHOD(swoole_table, column); static PHP_METHOD(swoole_table, create); static PHP_METHOD(swoole_table, set); static PHP_METHOD(swoole_table, get); static PHP_METHOD(swoole_table, del); static PHP_METHOD(swoole_table, exists); static PHP_METHOD(swoole_table, incr); static PHP_METHOD(swoole_table, decr); static PHP_METHOD(swoole_table, count); static PHP_METHOD(swoole_table, destroy); static PHP_METHOD(swoole_table, getSize); static PHP_METHOD(swoole_table, getMemorySize); static PHP_METHOD(swoole_table, stats); static PHP_METHOD(swoole_table, rewind); static PHP_METHOD(swoole_table, next); static PHP_METHOD(swoole_table, current); static PHP_METHOD(swoole_table, key); static PHP_METHOD(swoole_table, valid); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_table_methods[] = { PHP_ME(swoole_table, __construct, arginfo_class_Swoole_Table___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, column, arginfo_class_Swoole_Table_column, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, create, arginfo_class_Swoole_Table_create, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, destroy, arginfo_class_Swoole_Table_destroy, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, set, arginfo_class_Swoole_Table_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, get, arginfo_class_Swoole_Table_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, count, arginfo_class_Swoole_Table_count, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, del, arginfo_class_Swoole_Table_del, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_table, delete, del, arginfo_class_Swoole_Table_del, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, exists, arginfo_class_Swoole_Table_exists, ZEND_ACC_PUBLIC) PHP_MALIAS(swoole_table, exist, exists, arginfo_class_Swoole_Table_exists, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, incr, arginfo_class_Swoole_Table_incr, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, decr, arginfo_class_Swoole_Table_decr, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, getSize, arginfo_class_Swoole_Table_getSize, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, getMemorySize, arginfo_class_Swoole_Table_getMemorySize, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, stats, arginfo_class_Swoole_Table_stats, ZEND_ACC_PUBLIC) // implement Iterator PHP_ME(swoole_table, rewind, arginfo_class_Swoole_Table_rewind, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, valid, arginfo_class_Swoole_Table_valid, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, next, arginfo_class_Swoole_Table_next, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, current, arginfo_class_Swoole_Table_current, ZEND_ACC_PUBLIC) PHP_ME(swoole_table, key, arginfo_class_Swoole_Table_key, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_table_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_table, "Swoole\\Table", nullptr, swoole_table_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_table); SW_SET_CLASS_CLONEABLE(swoole_table, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_table, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_table, table_create_object, table_free_object, TableObject, std); zend_class_implements(swoole_table_ce, 1, zend_ce_iterator); zend_class_implements(swoole_table_ce, 1, zend_ce_countable); zend_declare_property_null(swoole_table_ce, ZEND_STRL("size"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_table_ce, ZEND_STRL("memorySize"), ZEND_ACC_PUBLIC); zend_declare_class_constant_long(swoole_table_ce, ZEND_STRL("TYPE_INT"), TableColumn::TYPE_INT); zend_declare_class_constant_long(swoole_table_ce, ZEND_STRL("TYPE_STRING"), TableColumn::TYPE_STRING); zend_declare_class_constant_long(swoole_table_ce, ZEND_STRL("TYPE_FLOAT"), TableColumn::TYPE_FLOAT); } PHP_METHOD(swoole_table, __construct) { Table *table = table_get_ptr(ZEND_THIS); if (table) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend_long table_size; double conflict_proportion = SW_TABLE_CONFLICT_PROPORTION; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_LONG(table_size) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(conflict_proportion) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); table = Table::make(table_size, static_cast(conflict_proportion)); if (table == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); RETURN_FALSE; } table->set_hash_func([](const char *key, size_t len) -> uint64_t { return zend_string_hash_val(zend::fetch_zend_string_by_val((void *) key)); }); table_set_ptr(ZEND_THIS, table); } PHP_METHOD(swoole_table, column) { Table *table = table_get_and_check_ptr(ZEND_THIS); char *name; size_t len; long type; long size = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|l", &name, &len, &type, &size) == FAILURE) { RETURN_FALSE; } if (type == TableColumn::TYPE_STRING) { if (size < 1) { php_swoole_fatal_error(E_WARNING, "the length of string type values has to be more than zero"); RETURN_FALSE; } size = SW_MEM_ALIGNED_SIZE(size); } if (table->ready()) { php_swoole_fatal_error(E_WARNING, "unable to add column after table has been created"); RETURN_FALSE; } RETURN_BOOL(table->add_column(std::string(name, len), static_cast(type), size)); } static PHP_METHOD(swoole_table, create) { Table *table = table_get_and_check_ptr(ZEND_THIS); if (!table->create()) { php_swoole_fatal_error(E_ERROR, "unable to allocate memory"); RETURN_FALSE; } zend_update_property_long( swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("size"), (zend_long) table->get_size()); zend_update_property_long( swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("memorySize"), (zend_long) table->get_memory_size()); RETURN_TRUE; } static PHP_METHOD(swoole_table, destroy) { Table *table = table_get_and_check_ptr2(ZEND_THIS); table->destroy(); table_set_ptr(ZEND_THIS, nullptr); RETURN_TRUE; } static PHP_METHOD(swoole_table, set) { Table *table = table_get_and_check_ptr2(ZEND_THIS); zval *array; char *key; size_t keylen; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) Z_PARAM_STRING(key, keylen) Z_PARAM_ARRAY(array) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (!table->ready()) { php_swoole_fatal_error(E_ERROR, "the table object does not exist"); RETURN_FALSE; } if (keylen >= SW_TABLE_KEY_SIZE) { php_swoole_fatal_error(E_WARNING, "key[%s] is too long", key); } int out_flags; TableRow *_rowlock = nullptr; TableRow *row = table->set(key, keylen, &_rowlock, &out_flags); if (!row) { _rowlock->unlock(); php_swoole_error(E_WARNING, "failed to set('%*s'), unable to allocate memory", (int) keylen, key); RETURN_FALSE; } HashTable *ht = Z_ARRVAL_P(array); if (out_flags & SW_TABLE_FLAG_NEW_ROW) { for (const auto col : *table->column_list) { zval *zv = zend_hash_str_find(ht, col->name.c_str(), col->name.length()); if (zv == nullptr || ZVAL_IS_NULL(zv)) { col->clear(row); } else { if (col->type == TableColumn::TYPE_STRING) { zend_string *str = zval_get_string(zv); row->set_value(col, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); } else if (col->type == TableColumn::TYPE_FLOAT) { double _value = zval_get_double(zv); row->set_value(col, &_value, 0); } else { long _value = zval_get_long(zv); row->set_value(col, &_value, 0); } } } } else { const char *k; uint32_t klen; int ktype; zval *zv; SW_HASHTABLE_FOREACH_START2(ht, k, klen, ktype, zv) { if (k == nullptr) { continue; } TableColumn *col = table->get_column(std::string(k, klen)); if (col == nullptr) { continue; } else if (col->type == TableColumn::TYPE_STRING) { zend_string *str = zval_get_string(zv); row->set_value(col, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); } else if (col->type == TableColumn::TYPE_FLOAT) { double _value = zval_get_double(zv); row->set_value(col, &_value, 0); } else { long _value = zval_get_long(zv); row->set_value(col, &_value, 0); } } (void) ktype; SW_HASHTABLE_FOREACH_END(); } _rowlock->unlock(); RETURN_TRUE; } static PHP_METHOD(swoole_table, incr) { Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t key_len; char *col; size_t col_len; zval *incrby = nullptr; if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|z", &key, &key_len, &col, &col_len, &incrby) == FAILURE) { RETURN_FALSE; } int out_flags; TableRow *_rowlock = nullptr; TableRow *row = table->set(key, key_len, &_rowlock, &out_flags); if (!row) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "unable to allocate memory"); RETURN_FALSE; } TableColumn *column = table->get_column(std::string(col, col_len)); if (column == nullptr) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "column[%s] does not exist", col); RETURN_FALSE; } if (out_flags & SW_TABLE_FLAG_NEW_ROW) { table->clear_row(row); } if (column->type == TableColumn::TYPE_STRING) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "can't execute 'incr' on a string type column"); RETURN_FALSE; } else if (column->type == TableColumn::TYPE_FLOAT) { double set_value = 0; memcpy(&set_value, row->data + column->index, sizeof(set_value)); if (incrby) { set_value += zval_get_double(incrby); } else { set_value += 1; } row->set_value(column, &set_value, 0); RETVAL_DOUBLE(set_value); } else { long set_value = 0; memcpy(&set_value, row->data + column->index, sizeof(set_value)); if (incrby) { set_value += zval_get_long(incrby); } else { set_value += 1; } row->set_value(column, &set_value, 0); RETVAL_LONG(set_value); } _rowlock->unlock(); } static PHP_METHOD(swoole_table, decr) { Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t key_len; char *col; size_t col_len; zval *decrby = nullptr; if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|z", &key, &key_len, &col, &col_len, &decrby) == FAILURE) { RETURN_FALSE; } int out_flags; TableRow *_rowlock = nullptr; TableRow *row = table->set(key, key_len, &_rowlock, &out_flags); if (!row) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "unable to allocate memory"); RETURN_FALSE; } TableColumn *column = table->get_column(std::string(col, col_len)); if (column == nullptr) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "column[%s] does not exist", col); RETURN_FALSE; } if (out_flags & SW_TABLE_FLAG_NEW_ROW) { table->clear_row(row); } if (column->type == TableColumn::TYPE_STRING) { _rowlock->unlock(); php_swoole_fatal_error(E_WARNING, "can't execute 'decr' on a string type column"); RETURN_FALSE; } else if (column->type == TableColumn::TYPE_FLOAT) { double set_value = 0; memcpy(&set_value, row->data + column->index, sizeof(set_value)); if (decrby) { set_value -= zval_get_double(decrby); } else { set_value -= 1; } row->set_value(column, &set_value, 0); RETVAL_DOUBLE(set_value); } else { long set_value = 0; memcpy(&set_value, row->data + column->index, sizeof(set_value)); if (decrby) { set_value -= zval_get_long(decrby); } else { set_value -= 1; } row->set_value(column, &set_value, 0); RETVAL_LONG(set_value); } _rowlock->unlock(); } static PHP_METHOD(swoole_table, get) { Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; zend_string *field = nullptr; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_STRING(key, keylen) Z_PARAM_OPTIONAL Z_PARAM_STR_OR_NULL(field) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); TableRow *_rowlock = nullptr; TableRow *row = table->get(key, keylen, &_rowlock); if (!row) { RETVAL_FALSE; } else if (field) { table_get_field_value(table, row, return_value, field); } else { table_row2array(table, row, return_value); } _rowlock->unlock(); } static PHP_METHOD(swoole_table, exists) { Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &keylen) == FAILURE) { RETURN_FALSE; } RETURN_BOOL(table->exists(key, keylen)); } static PHP_METHOD(swoole_table, del) { Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_STRING(key, keylen) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(table->del(key, keylen)); } static PHP_METHOD(swoole_table, count) { #define COUNT_NORMAL 0 #define COUNT_RECURSIVE 1 Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } zend_long mode = COUNT_NORMAL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &mode) == FAILURE) { RETURN_FALSE; } if (mode == COUNT_NORMAL) { RETURN_LONG(table->count()); } else { RETURN_LONG(table->count() * table->column_list->size()); } } static PHP_METHOD(swoole_table, getMemorySize) { Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } else { RETURN_LONG(table->get_memory_size()); } } static PHP_METHOD(swoole_table, getSize) { Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } else { RETURN_LONG(table->get_size()); } } static PHP_METHOD(swoole_table, stats) { Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_FALSE; } array_init(return_value); add_assoc_long(return_value, "num", table->count()); add_assoc_long(return_value, "conflict_count", table->conflict_count); add_assoc_long(return_value, "conflict_max_level", table->conflict_max_level); add_assoc_long(return_value, "insert_count", (zend_long) table->insert_count); add_assoc_long(return_value, "update_count", (zend_long) table->update_count); add_assoc_long(return_value, "delete_count", (zend_long) table->delete_count); add_assoc_long(return_value, "available_slice_num", table->get_available_slice_num()); add_assoc_long(return_value, "total_slice_num", table->get_total_slice_num()); } static PHP_METHOD(swoole_table, rewind) { Table *table = table_get_and_check_ptr2(ZEND_THIS); table->rewind(); table->forward(); } static PHP_METHOD(swoole_table, valid) { Table *table = table_get_and_check_ptr2(ZEND_THIS); auto key = table->current(); RETURN_BOOL(key->key_len != 0); } static PHP_METHOD(swoole_table, current) { Table *table = table_get_and_check_ptr2(ZEND_THIS); auto row = table->current(); if (row->key_len == 0) { RETURN_NULL(); } table_row2array(table, row, return_value); } static PHP_METHOD(swoole_table, key) { Table *table = table_get_and_check_ptr2(ZEND_THIS); auto row = table->current(); if (row->key_len == 0) { RETURN_NULL(); } RETVAL_STRINGL(row->key, row->key_len); } static PHP_METHOD(swoole_table, next) { Table *table = table_get_and_check_ptr2(ZEND_THIS); table->forward(); } ================================================ FILE: ext-src/swoole_thread.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_thread.h" #ifdef SW_THREAD #include #include #include BEGIN_EXTERN_C() #include "stubs/php_swoole_thread_arginfo.h" END_EXTERN_C() zend_class_entry *swoole_thread_ce; static zend_object_handlers swoole_thread_handlers; zend_class_entry *swoole_thread_error_ce; static zend_object_handlers swoole_thread_error_handlers; static struct { char *path_translated; zend_string *argv_serialized; int argc; } request_info; TSRMLS_CACHE_EXTERN(); using swoole::Thread; struct PhpThread { std::shared_ptr thread; PhpThread() : thread(std::make_shared()) {} bool join() const { if (!thread->joinable()) { return false; } thread->join(); return true; } }; struct ThreadObject { PhpThread *pt; zend_object std; }; static void thread_register_stdio_file_handles(bool no_close); static thread_local zval thread_argv = {}; static thread_local JMP_BUF *thread_bailout = nullptr; static std::atomic thread_num(1); static sw_inline ThreadObject *thread_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_handlers.offset); } static sw_inline ThreadObject *thread_fetch_object(const zval *zobj) { return thread_fetch_object(Z_OBJ_P(zobj)); } static sw_inline PhpThread *thread_get_php_thread(zend_object *obj) { return thread_fetch_object(obj)->pt; } static sw_inline PhpThread *thread_get_php_thread(const zval *zobj) { return thread_fetch_object(zobj)->pt; } static void thread_free_object(zend_object *object) { auto pt = thread_get_php_thread(object); pt->join(); delete pt; zend_object_std_dtor(object); } static zend_object *thread_create_object(zend_class_entry *ce) { auto to = static_cast(zend_object_alloc(sizeof(ThreadObject), ce)); zend_object_std_init(&to->std, ce); object_properties_init(&to->std, ce); to->pt = new PhpThread(); to->std.handlers = &swoole_thread_handlers; return &to->std; } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread, __construct); static PHP_METHOD(swoole_thread, isAlive); static PHP_METHOD(swoole_thread, join); static PHP_METHOD(swoole_thread, joinable); static PHP_METHOD(swoole_thread, getExitStatus); static PHP_METHOD(swoole_thread, detach); static PHP_METHOD(swoole_thread, getArguments); static PHP_METHOD(swoole_thread, getId); static PHP_METHOD(swoole_thread, getInfo); static PHP_METHOD(swoole_thread, activeCount); static PHP_METHOD(swoole_thread, yield); static PHP_METHOD(swoole_thread, setName); #ifdef HAVE_CPU_AFFINITY static PHP_METHOD(swoole_thread, setAffinity); static PHP_METHOD(swoole_thread, getAffinity); #endif static PHP_METHOD(swoole_thread, setPriority); static PHP_METHOD(swoole_thread, getPriority); static PHP_METHOD(swoole_thread, getNativeId); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_thread_methods[] = { PHP_ME(swoole_thread, __construct, arginfo_class_Swoole_Thread___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, isAlive, arginfo_class_Swoole_Thread_isAlive, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, join, arginfo_class_Swoole_Thread_join, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, joinable, arginfo_class_Swoole_Thread_joinable, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, getExitStatus, arginfo_class_Swoole_Thread_getExitStatus, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, detach, arginfo_class_Swoole_Thread_detach, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread, getArguments, arginfo_class_Swoole_Thread_getArguments, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, getId, arginfo_class_Swoole_Thread_getId, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, getInfo, arginfo_class_Swoole_Thread_getInfo, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, activeCount, arginfo_class_Swoole_Thread_activeCount, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, yield, arginfo_class_Swoole_Thread_yield, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, setName, arginfo_class_Swoole_Thread_setName, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #ifdef HAVE_CPU_AFFINITY PHP_ME(swoole_thread, setAffinity, arginfo_class_Swoole_Thread_setAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, getAffinity, arginfo_class_Swoole_Thread_getAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #endif PHP_ME(swoole_thread, setPriority, arginfo_class_Swoole_Thread_setPriority, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, getPriority, arginfo_class_Swoole_Thread_getPriority, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_thread, getNativeId, arginfo_class_Swoole_Thread_getNativeId, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void php_swoole_thread_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread, "Swoole\\Thread", nullptr, swoole_thread_methods); swoole_thread_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread, thread_create_object, thread_free_object, ThreadObject, std); zend_declare_property_long(swoole_thread_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_class_constant_long( swoole_thread_ce, ZEND_STRL("HARDWARE_CONCURRENCY"), std::thread::hardware_concurrency()); zend_declare_class_constant_string(swoole_thread_ce, ZEND_STRL("API_NAME"), tsrm_api_name()); zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_OTHER"), SCHED_OTHER); zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_FIFO"), SCHED_FIFO); zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_RR"), SCHED_RR); #ifdef SCHED_BATCH zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_BATCH"), SCHED_BATCH); #endif #ifdef SCHED_ISO zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_ISO"), SCHED_ISO); #endif #ifdef SCHED_IDLE zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_IDLE"), SCHED_IDLE); #endif #ifdef SCHED_DEADLINE zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_DEADLINE"), SCHED_DEADLINE); #endif SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_thread_error, "Swoole\\Thread\\Error"); zend_declare_property_long(swoole_thread_error_ce, ZEND_STRL("code"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); } static PHP_METHOD(swoole_thread, __construct) { char *script_file; size_t l_script_file; zval *args; int argc = 0; ZendArray *argv = nullptr; ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_STRING(script_file, l_script_file) Z_PARAM_VARIADIC('+', args, argc) ZEND_PARSE_PARAMETERS_END(); if (l_script_file < 1) { zend_throw_exception(swoole_exception_ce, "exec file name is empty", SW_ERROR_INVALID_PARAMS); return; } auto pt = thread_get_php_thread(ZEND_THIS); zend_string *file = zend_string_init(script_file, l_script_file, true); if (argc > 0) { argv = new ZendArray(); for (int i = 0; i < argc; i++) { argv->append(&args[i]); } } try { pt->thread->start([file, argv, pt]() { php_swoole_thread_start(pt->thread, file, argv); }); } catch (const std::exception &e) { zend_throw_exception(swoole_exception_ce, e.what(), SW_ERROR_SYSTEM_CALL_FAIL); return; } zend::object_set(ZEND_THIS, ZEND_STRL("id"), (zend_long) pt->thread->get_id()); } static PHP_METHOD(swoole_thread, isAlive) { auto pt = thread_get_php_thread(ZEND_THIS); RETURN_BOOL(pt->thread->is_alive()); } static PHP_METHOD(swoole_thread, join) { auto pt = thread_get_php_thread(ZEND_THIS); RETURN_BOOL(pt->join()); } static PHP_METHOD(swoole_thread, joinable) { auto pt = thread_get_php_thread(ZEND_THIS); RETURN_BOOL(pt->thread->joinable()); } static PHP_METHOD(swoole_thread, detach) { auto pt = thread_get_php_thread(ZEND_THIS); if (!pt->thread->joinable()) { RETURN_FALSE; } pt->thread->detach(); RETURN_TRUE; } static PHP_METHOD(swoole_thread, getArguments) { if (Z_TYPE(thread_argv) == IS_ARRAY) { RETURN_ZVAL(&thread_argv, 1, 0); } } static PHP_METHOD(swoole_thread, getId) { RETURN_LONG((zend_long) pthread_self()); } static PHP_METHOD(swoole_thread, getExitStatus) { auto pt = thread_get_php_thread(ZEND_THIS); RETURN_LONG(pt->thread->get_exit_status()); } static PHP_METHOD(swoole_thread, setName) { char *name; size_t l_name; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(name, l_name) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(swoole_thread_set_name(name)); } #ifdef HAVE_CPU_AFFINITY static PHP_METHOD(swoole_thread, setAffinity) { zval *array; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(array) ZEND_PARSE_PARAMETERS_END(); cpu_set_t cpu_set; if (!php_swoole_array_to_cpu_set(array, &cpu_set)) { RETURN_FALSE; } if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set) < 0) { php_swoole_error(E_WARNING, "pthread_setaffinity_np() failed"); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_thread, getAffinity) { cpu_set_t cpu_set; if (pthread_getaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set) < 0) { php_swoole_error(E_WARNING, "pthread_getaffinity_np() failed"); RETURN_FALSE; } php_swoole_cpu_set_to_array(return_value, &cpu_set); } #endif static PHP_METHOD(swoole_thread, setPriority) { zend_long priority, policy = -1; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(priority) Z_PARAM_OPTIONAL Z_PARAM_LONG(policy) ZEND_PARSE_PARAMETERS_END(); struct sched_param param; if (policy == -1) { pthread_setschedparam(pthread_self(), policy, ¶m); } param.sched_priority = priority; int retval = pthread_setschedparam(pthread_self(), policy, ¶m); if (retval == 0) { RETURN_TRUE; } else { php_swoole_sys_error(E_WARNING, "pthread_setschedparam() failed"); RETURN_FALSE; } } static PHP_METHOD(swoole_thread, getPriority) { struct sched_param param; int policy; if (pthread_getschedparam(pthread_self(), &policy, ¶m) != 0) { php_swoole_error(E_WARNING, "pthread_getschedparam() failed"); RETURN_FALSE; } array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("policy"), policy); add_assoc_long_ex(return_value, ZEND_STRL("priority"), param.sched_priority); } static PHP_METHOD(swoole_thread, getNativeId) { RETURN_LONG((zend_long) swoole_thread_get_native_id()); } void php_swoole_thread_rinit() { if (tsrm_is_main_thread()) { if (SG(request_info).path_translated) { request_info.path_translated = strdup(SG(request_info).path_translated); } // Return reference zval *global_argv = zend_hash_find_ind(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV)); if (global_argv) { request_info.argv_serialized = php_swoole_serialize(global_argv); request_info.argc = SG(request_info).argc; } } } void php_swoole_thread_rshutdown() { zval_dtor(&thread_argv); if (!tsrm_is_main_thread()) { return; } if (sw_active_thread_count() > 1) { swoole_warning("Fatal Error: %zu active threads are running, cannot exit safely.", sw_active_thread_count()); exit(200); } if (request_info.path_translated) { free((void *) request_info.path_translated); request_info.path_translated = nullptr; } if (request_info.argv_serialized) { zend_string_release(request_info.argv_serialized); request_info.argv_serialized = nullptr; } } static void thread_register_stdio_file_handles(bool no_close) { php_stream *s_in, *s_out, *s_err; php_stream_context *sc_in = nullptr, *sc_out = nullptr, *sc_err = nullptr; zend_constant ic, oc, ec; s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in); s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out); s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err); if (s_in == nullptr || s_out == nullptr || s_err == nullptr) { if (s_in) php_stream_close(s_in); if (s_out) php_stream_close(s_out); if (s_err) php_stream_close(s_err); return; } if (no_close) { s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE; s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE; s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE; } php_stream_to_zval(s_in, &ic.value); php_stream_to_zval(s_out, &oc.value); php_stream_to_zval(s_err, &ec.value); ZEND_CONSTANT_SET_FLAGS(&ic, CONST_CS, 0); ic.name = zend_string_init_interned("STDIN", sizeof("STDIN") - 1, false); zend_register_constant(&ic); ZEND_CONSTANT_SET_FLAGS(&oc, CONST_CS, 0); oc.name = zend_string_init_interned("STDOUT", sizeof("STDOUT") - 1, false); zend_register_constant(&oc); ZEND_CONSTANT_SET_FLAGS(&ec, CONST_CS, 0); ec.name = zend_string_init_interned("STDERR", sizeof("STDERR") - 1, false); zend_register_constant(&ec); } void php_swoole_thread_start(std::shared_ptr thread, zend_string *file, ZendArray *argv) { thread_num.fetch_add(1); thread->enter(); ts_resource(0); #if defined(COMPILE_DL_SWOOLE) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif zend_file_handle file_handle{}; zval global_argc, global_argv; PG(expose_php) = false; PG(auto_globals_jit) = true; PG(enable_dl) = false; swoole_thread_init(false); if (php_request_startup() != SUCCESS) { EG(exit_status) = 1; goto _startup_error; } PG(during_request_startup) = false; SG(sapi_started) = false; SG(headers_sent) = 1; SG(request_info).no_headers = true; SG(request_info).path_translated = request_info.path_translated; SG(request_info).argc = request_info.argc; #if PHP_VERSION_ID >= 80500 refresh_memory_manager(); #endif zend_stream_init_filename(&file_handle, ZSTR_VAL(file)); file_handle.primary_script = true; zend_first_try { thread_bailout = EG(bailout); if (request_info.argv_serialized) { php_swoole_unserialize(request_info.argv_serialized, &global_argv); ZVAL_LONG(&global_argc, request_info.argc); zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV), &global_argv); zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGC), &global_argc); } if (argv) { argv->to_array(&thread_argv); argv->del_ref(); } thread_register_stdio_file_handles(true); php_execute_script(&file_handle); } zend_end_try(); zend_destroy_file_handle(&file_handle); php_request_shutdown(nullptr); file_handle.filename = nullptr; _startup_error: zend_string_release(file); thread->exit(EG(exit_status)); ts_free_thread(); swoole_thread_clean(false); thread_num.fetch_sub(1); } size_t sw_active_thread_count() { return thread_num.load(); } void php_swoole_thread_bailout() { if (thread_bailout) { EG(bailout) = thread_bailout; } zend_bailout(); } int php_swoole_thread_stream_cast(zval *zstream) { php_stream *stream; int sockfd; int cast_flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL; if ((php_stream_from_zval_no_verify(stream, zstream))) { if (php_stream_cast(stream, cast_flags, (void **) &sockfd, 1) == SUCCESS && sockfd >= 0) { return dup(sockfd); } } return -1; } int php_swoole_thread_co_socket_cast(zval *zvalue, swSocketType *type) { SocketImpl *socket = php_swoole_get_socket(zvalue); if (!socket) { return -1; } int sockfd = socket->get_fd(); if (sockfd < 0) { return -1; } int newfd = dup(sockfd); if (newfd < 0) { return -1; } *type = socket->get_type(); return newfd; } void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd) { std::string path = "php://fd/" + std::to_string(sockfd); // The file descriptor will be duplicated once here php_stream *stream = php_stream_open_wrapper_ex(path.c_str(), "", 0, NULL, NULL); if (stream) { php_stream_to_zval(stream, return_value); } else { object_init_ex(return_value, swoole_thread_error_ce); zend::object_set(return_value, ZEND_STRL("code"), errno); } } void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type) { int newfd = dup(sockfd); if (newfd < 0) { _error: object_init_ex(return_value, swoole_thread_error_ce); zend::object_set(return_value, ZEND_STRL("code"), errno); return; } zend_object *sockobj = php_swoole_create_socket_from_fd(newfd, type); if (sockobj) { ZVAL_OBJ(return_value, sockobj); } else { goto _error; } } #ifdef SWOOLE_SOCKETS_SUPPORT void php_swoole_thread_php_socket_create(zval *return_value, zend_long sockfd) { int newfd = dup(sockfd); if (newfd < 0) { _error: object_init_ex(return_value, swoole_thread_error_ce); zend::object_set(return_value, ZEND_STRL("code"), errno); return; } object_init_ex(return_value, socket_ce); auto retsock = Z_SOCKET_P(return_value); if (!socket_import_file_descriptor(newfd, retsock)) { goto _error; } } #endif static PHP_METHOD(swoole_thread, getInfo) { array_init(return_value); add_assoc_bool(return_value, "is_main_thread", tsrm_is_main_thread()); add_assoc_bool(return_value, "is_shutdown", tsrm_is_shutdown()); add_assoc_long(return_value, "thread_num", thread_num.load()); } static PHP_METHOD(swoole_thread, activeCount) { RETURN_LONG(thread_num.load()); } static PHP_METHOD(swoole_thread, yield) { std::this_thread::yield(); } #define CAST_OBJ_TO_RESOURCE(_name, _type) \ else if (instanceof_function(Z_OBJCE_P(zvalue), swoole_thread_##_name##_ce)) { \ value.resource = php_swoole_thread_##_name##_cast(zvalue); \ value.resource->add_ref(); \ type = _type; \ break; \ } void ArrayItem::store(zval *zvalue) { type = Z_TYPE_P(zvalue); switch (type) { case IS_LONG: value.lval = zval_get_long(zvalue); break; case IS_DOUBLE: value.dval = zval_get_double(zvalue); break; case IS_STRING: { value.str = zend_string_init(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue), true); break; } case IS_TRUE: case IS_FALSE: case IS_NULL: break; case IS_RESOURCE: { value.socket.fd = php_swoole_thread_stream_cast(zvalue); type = IS_STREAM_SOCKET; if (value.socket.fd == -1) { zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno); } break; } case IS_ARRAY: { type = zend_array_is_list(Z_ARRVAL_P(zvalue)) ? IS_ARRAYLIST : IS_MAP; value.resource = ZendArray::from(Z_ARRVAL_P(zvalue)); break; } case IS_OBJECT: { if (sw_zval_is_co_socket(zvalue)) { value.socket.fd = php_swoole_thread_co_socket_cast(zvalue, &value.socket.type); type = IS_CO_SOCKET; if (value.socket.fd == -1) { zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno); } break; } #ifdef SWOOLE_SOCKETS_SUPPORT else if (sw_zval_is_php_socket(zvalue)) { php_socket *php_sock = SW_Z_SOCKET_P(zvalue); if (php_sock->bsd_socket == -1) { zend_throw_exception(swoole_exception_ce, "invalid socket fd", EBADF); break; } value.socket.fd = dup(php_sock->bsd_socket); if (value.socket.fd == -1) { zend_throw_exception(swoole_exception_ce, "failed to dup socket fd", errno); } type = IS_PHP_SOCKET; break; } #endif CAST_OBJ_TO_RESOURCE(arraylist, IS_ARRAYLIST) CAST_OBJ_TO_RESOURCE(map, IS_MAP) CAST_OBJ_TO_RESOURCE(queue, IS_QUEUE) CAST_OBJ_TO_RESOURCE(lock, IS_LOCK) CAST_OBJ_TO_RESOURCE(atomic, IS_ATOMIC) CAST_OBJ_TO_RESOURCE(atomic_long, IS_ATOMIC_LONG) CAST_OBJ_TO_RESOURCE(barrier, IS_BARRIER) } /* no break */ default: { auto _serialized_object = php_swoole_serialize(zvalue); if (!_serialized_object) { type = IS_UNDEF; break; } else { type = IS_SERIALIZED_OBJECT; value.serialized_object = _serialized_object; } break; } } } bool ArrayItem::equals(const zval *zvalue) const { if (Z_TYPE_P(zvalue) != type) { return false; } switch (type) { case IS_LONG: return Z_LVAL_P(zvalue) == value.lval; case IS_DOUBLE: return Z_DVAL_P(zvalue) == value.dval; case IS_TRUE: case IS_FALSE: case IS_NULL: return true; case IS_STRING: return zend_string_equals(value.str, Z_STR_P(zvalue)); default: return false; } } #define TYPE_PAIR(t1, t2) (((t1) << 4) | (t2)) #define ITEM_TYPE(item) (item->type) #define ITEM_LVAL(item) (item->value.lval) #define ITEM_DVAL(item) (item->value.dval) #define ITEM_STR(item) (item->value.str) static int compare_long_to_string(zend_long lval, const zend_string *str) /* {{{ */ { zend_long str_lval; double str_dval; zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, false); if (type == IS_LONG) { return lval > str_lval ? 1 : lval < str_lval ? -1 : 0; } if (type == IS_DOUBLE) { double diff = (double) lval - str_dval; return ZEND_NORMALIZE_BOOL(diff); } zend_string *lval_as_str = zend_long_to_str(lval); int cmp_result = zend_binary_strcmp(ZSTR_VAL(lval_as_str), ZSTR_LEN(lval_as_str), ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(lval_as_str); return ZEND_NORMALIZE_BOOL(cmp_result); } /* }}} */ static int compare_double_to_string(double dval, const zend_string *str) /* {{{ */ { zend_long str_lval; double str_dval; zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, false); if (type == IS_LONG) { double diff = dval - (double) str_lval; return ZEND_NORMALIZE_BOOL(diff); } if (type == IS_DOUBLE) { if (dval == str_dval) { return 0; } return ZEND_NORMALIZE_BOOL(dval - str_dval); } zend_string *dval_as_str = zend_double_to_str(dval); int cmp_result = zend_binary_strcmp(ZSTR_VAL(dval_as_str), ZSTR_LEN(dval_as_str), ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(dval_as_str); return ZEND_NORMALIZE_BOOL(cmp_result); } /* }}} */ int ArrayItem::compare(Bucket *a, Bucket *b) { const ArrayItem *op1 = static_cast(Z_PTR(a->val)); const ArrayItem *op2 = static_cast(Z_PTR(b->val)); switch (TYPE_PAIR(ITEM_TYPE(op1), ITEM_TYPE(op2))) { case TYPE_PAIR(IS_LONG, IS_LONG): return ITEM_LVAL(op1) > ITEM_LVAL(op2) ? 1 : (ITEM_LVAL(op1) < ITEM_LVAL(op2) ? -1 : 0); case TYPE_PAIR(IS_DOUBLE, IS_LONG): return ZEND_NORMALIZE_BOOL(ITEM_DVAL(op1) - (double) ITEM_LVAL(op2)); case TYPE_PAIR(IS_LONG, IS_DOUBLE): return ZEND_NORMALIZE_BOOL((double) ITEM_LVAL(op1) - ITEM_DVAL(op2)); case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): if (ITEM_DVAL(op1) == ITEM_DVAL(op2)) { return 0; } else { return ZEND_NORMALIZE_BOOL(ITEM_DVAL(op1) - ITEM_DVAL(op2)); } case TYPE_PAIR(IS_NULL, IS_NULL): case TYPE_PAIR(IS_NULL, IS_FALSE): case TYPE_PAIR(IS_FALSE, IS_NULL): case TYPE_PAIR(IS_FALSE, IS_FALSE): case TYPE_PAIR(IS_TRUE, IS_TRUE): return 0; case TYPE_PAIR(IS_NULL, IS_TRUE): return -1; case TYPE_PAIR(IS_TRUE, IS_NULL): return 1; case TYPE_PAIR(IS_STRING, IS_STRING): if (ITEM_STR(op1) == ITEM_STR(op2)) { return 0; } return zendi_smart_strcmp(ITEM_STR(op1), ITEM_STR(op2)); case TYPE_PAIR(IS_NULL, IS_STRING): return Z_STRLEN_P(op2) == 0 ? 0 : -1; case TYPE_PAIR(IS_STRING, IS_NULL): return Z_STRLEN_P(op1) == 0 ? 0 : 1; case TYPE_PAIR(IS_LONG, IS_STRING): return compare_long_to_string(ITEM_LVAL(op1), ITEM_STR(op2)); case TYPE_PAIR(IS_STRING, IS_LONG): return -compare_long_to_string(ITEM_LVAL(op2), ITEM_STR(op1)); case TYPE_PAIR(IS_DOUBLE, IS_STRING): if (zend_isnan(ITEM_DVAL(op1))) { return 1; } return compare_double_to_string(ITEM_DVAL(op1), ITEM_STR(op2)); case TYPE_PAIR(IS_STRING, IS_DOUBLE): if (zend_isnan(ITEM_DVAL(op2))) { return 1; } return -compare_double_to_string(ITEM_DVAL(op2), ITEM_STR(op1)); case TYPE_PAIR(IS_OBJECT, IS_NULL): return 1; case TYPE_PAIR(IS_NULL, IS_OBJECT): return -1; default: zend_throw_error(nullptr, "Unsupported operand types"); return 1; } } void ArrayItem::fetch(zval *return_value) const { switch (type) { case IS_LONG: RETVAL_LONG(value.lval); break; case IS_DOUBLE: RETVAL_DOUBLE(value.dval); break; case IS_TRUE: RETVAL_TRUE; break; case IS_FALSE: RETVAL_FALSE; break; case IS_STRING: RETVAL_NEW_STR(zend_string_init(ZSTR_VAL(value.str), ZSTR_LEN(value.str), 0)); break; case IS_ARRAYLIST: value.resource->add_ref(); php_swoole_thread_arraylist_create(return_value, value.resource); break; case IS_QUEUE: value.resource->add_ref(); php_swoole_thread_queue_create(return_value, value.resource); break; case IS_LOCK: value.resource->add_ref(); php_swoole_thread_lock_create(return_value, value.resource); break; case IS_MAP: value.resource->add_ref(); php_swoole_thread_map_create(return_value, value.resource); break; case IS_BARRIER: value.resource->add_ref(); php_swoole_thread_barrier_create(return_value, value.resource); break; case IS_ATOMIC: value.resource->add_ref(); php_swoole_thread_atomic_create(return_value, value.resource); break; case IS_ATOMIC_LONG: value.resource->add_ref(); php_swoole_thread_atomic_long_create(return_value, value.resource); break; case IS_STREAM_SOCKET: php_swoole_thread_stream_create(return_value, value.socket.fd); break; case IS_CO_SOCKET: php_swoole_thread_co_socket_create(return_value, value.socket.fd, value.socket.type); break; #ifdef SWOOLE_SOCKETS_SUPPORT case IS_PHP_SOCKET: php_swoole_thread_php_socket_create(return_value, value.socket.fd); break; #endif case IS_SERIALIZED_OBJECT: php_swoole_unserialize(value.serialized_object, return_value); break; default: break; } } void ArrayItem::release() { if (type == IS_STRING) { zend_string_release(value.str); value.str = nullptr; } else if (type == IS_STREAM_SOCKET || type == IS_CO_SOCKET || type == IS_PHP_SOCKET) { close(value.socket.fd); value.socket.fd = -1; } else if (type == IS_SERIALIZED_OBJECT) { zend_string_release(value.serialized_object); value.serialized_object = nullptr; } else if (type >= IS_ARRAYLIST && type <= IS_ATOMIC_LONG) { value.resource->del_ref(); value.resource = nullptr; } } #define INIT_DECR_VALUE(v) \ zval rvalue = *v; \ if (Z_TYPE_P(v) == IS_DOUBLE) { \ rvalue.value.dval = -rvalue.value.dval; \ } else { \ ZVAL_LONG(&rvalue, -zval_get_long(v)); \ } void ZendArray::incr_update(ArrayItem *item, zval *zvalue, zval *return_value) { if (item->type == IS_DOUBLE) { item->value.dval += zval_get_double(zvalue); RETVAL_DOUBLE(item->value.dval); } else { item->value.lval += zval_get_long(zvalue); RETVAL_LONG(item->value.lval); } } ArrayItem *ZendArray::incr_create(zval *zvalue, zval *return_value) { zval rvalue = *zvalue; if (Z_TYPE_P(zvalue) == IS_DOUBLE) { RETVAL_DOUBLE(rvalue.value.dval); } else { ZVAL_LONG(&rvalue, zval_get_long(zvalue)); RETVAL_LONG(rvalue.value.lval); } return new ArrayItem(&rvalue); } void ZendArray::strkey_incr(zval *zkey, zval *zvalue, zval *return_value) { zend::String skey(zkey); lock_.lock(); auto *item = static_cast(zend_hash_find_ptr(&ht, skey.get())); if (item) { incr_update(item, zvalue, return_value); } else { item = incr_create(zvalue, return_value); item->setKey(skey); zend_hash_update_ptr(&ht, item->key, item); } lock_.unlock(); } void ZendArray::intkey_incr(zend_long index, zval *zvalue, zval *return_value) { lock_.lock(); auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); if (item) { incr_update(item, zvalue, return_value); } else { item = incr_create(zvalue, return_value); item = new ArrayItem(zvalue); zend_hash_index_update_ptr(&ht, index, item); } lock_.unlock(); } void ZendArray::strkey_decr(zval *zkey, zval *zvalue, zval *return_value) { INIT_DECR_VALUE(zvalue); strkey_incr(zkey, &rvalue, return_value); } void ZendArray::intkey_decr(zend_long index, zval *zvalue, zval *return_value) { INIT_DECR_VALUE(zvalue); intkey_incr(index, &rvalue, return_value); } void ZendArray::strkey_add(zval *zkey, zval *zvalue, zval *return_value) { zend::String skey(zkey); lock_.lock(); if (strkey_exists(skey)) { RETVAL_FALSE; } else { add(skey, zvalue); RETVAL_TRUE; } lock_.unlock(); } void ZendArray::intkey_add(zend_long index, zval *zvalue, zval *return_value) { lock_.lock(); if (intkey_exists(index)) { RETVAL_FALSE; } else { add(index, zvalue); RETVAL_TRUE; } lock_.unlock(); } void ZendArray::strkey_update(zval *zkey, zval *zvalue, zval *return_value) { zend::String skey(zkey); lock_.lock(); if (!strkey_exists(skey)) { RETVAL_FALSE; } else { auto item = new ArrayItem(zvalue); item->setKey(skey); zend_hash_update_ptr(&ht, item->key, item); RETVAL_TRUE; } lock_.unlock(); } void ZendArray::intkey_update(zend_long index, zval *zvalue, zval *return_value) { lock_.lock(); if (!intkey_exists(index)) { RETVAL_FALSE; } else { auto item = new ArrayItem(zvalue); zend_hash_index_update_ptr(&ht, index, item); RETVAL_TRUE; } lock_.unlock(); } bool ZendArray::index_offsetGet(zend_long index, zval *return_value) { bool out_of_range = true; lock_.lock_rd(); if (index_exists(index)) { out_of_range = false; auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); if (item) { item->fetch(return_value); } } lock_.unlock(); return !out_of_range; } bool ZendArray::index_offsetSet(zend_long index, zval *zvalue) { auto item = new ArrayItem(zvalue); bool success = true; lock_.lock(); if (index > zend_hash_num_elements(&ht)) { success = false; delete item; } else if (index == -1 || index == zend_hash_num_elements(&ht)) { zend_hash_next_index_insert_ptr(&ht, item); } else { zend_hash_index_update_ptr(&ht, index, item); } lock_.unlock(); return success; } void ZendArray::append(zval *zvalue) { zend_hash_next_index_insert_ptr(&ht, new ArrayItem(zvalue)); } bool ZendArray::index_incr(zval *zkey, zval *zvalue, zval *return_value) { zend_long index = ZVAL_IS_NULL(zkey) ? -1 : zval_get_long(zkey); bool success = true; lock_.lock(); if (index > zend_hash_num_elements(&ht)) { success = false; } else if (index == -1 || index == zend_hash_num_elements(&ht)) { auto item = incr_create(zvalue, return_value); zend_hash_next_index_insert_ptr(&ht, item); } else { auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); incr_update(item, zvalue, return_value); } lock_.unlock(); return success; } void ZendArray::index_offsetExists(zend_long index, zval *return_value) { lock_.lock_rd(); RETVAL_BOOL(index_exists(index)); lock_.unlock(); } void ZendArray::index_offsetUnset(zend_long index) { lock_.lock(); zend_long i = index; zend_long n = zend_hash_num_elements(&ht); HT_FLAGS(&ht) |= HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS; auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); delete item; while (i < n - 1) { Z_PTR(ht.arPacked[i]) = Z_PTR(ht.arPacked[i + 1]); i++; } ht.nNumUsed--; ht.nNumOfElements--; ht.nNextFreeElement--; lock_.unlock(); } bool ZendArray::index_decr(zval *zkey, zval *zvalue, zval *return_value) { INIT_DECR_VALUE(zvalue); return index_incr(zkey, &rvalue, return_value); } void ZendArray::keys(zval *return_value) { lock_.lock_rd(); zend_ulong elem_count = zend_hash_num_elements(&ht); array_init_size(return_value, elem_count); zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); zend_ulong num_idx; zend_string *str_idx; zval *entry; ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { if (HT_IS_PACKED(&ht) && HT_IS_WITHOUT_HOLES(&ht)) { /* Optimistic case: range(0..n-1) for vector-like packed array */ zend_ulong lval = 0; for (; lval < elem_count; ++lval) { ZEND_HASH_FILL_SET_LONG(lval); ZEND_HASH_FILL_NEXT(); } } else { /* Go through input array and add keys to the return array */ ZEND_HASH_FOREACH_KEY_VAL(&ht, num_idx, str_idx, entry) { if (str_idx) { ZEND_HASH_FILL_SET_STR(zend_string_init(str_idx->val, str_idx->len, 0)); } else { ZEND_HASH_FILL_SET_LONG(num_idx); } ZEND_HASH_FILL_NEXT(); } ZEND_HASH_FOREACH_END(); } (void) entry; } ZEND_HASH_FILL_END(); lock_.unlock(); } void ZendArray::values(zval *return_value) { lock_.lock_rd(); zend_ulong elem_count = zend_hash_num_elements(&ht); array_init_size(return_value, elem_count); void *tmp; ZEND_HASH_FOREACH_PTR(&ht, tmp) { zval value; auto item = static_cast(tmp); item->fetch(&value); zend_hash_next_index_insert_new(Z_ARR_P(return_value), &value); } ZEND_HASH_FOREACH_END(); lock_.unlock(); } void ZendArray::to_array(zval *return_value) { lock_.lock_rd(); zend_ulong elem_count = zend_hash_num_elements(&ht); array_init_size(return_value, elem_count); zend_string *key; zend_ulong index; void *tmp; ZEND_HASH_FOREACH_KEY_PTR(&ht, index, key, tmp) { zval value; const auto item = static_cast(tmp); item->fetch(&value); if (key) { zend_hash_str_add(Z_ARR_P(return_value), ZSTR_VAL(key), ZSTR_LEN(key), &value); } else { zend_hash_index_add(Z_ARR_P(return_value), index, &value); } } ZEND_HASH_FOREACH_END(); lock_.unlock(); } void ZendArray::find(const zval *search, zval *return_value) { lock_.lock_rd(); zend_string *key; zend_ulong index; void *tmp; ZEND_HASH_FOREACH_KEY_PTR(&ht, index, key, tmp) { const auto item = static_cast(tmp); if (item->equals(search)) { if (key) { RETVAL_STRINGL(ZSTR_VAL(key), ZSTR_LEN(key)); } else { RETVAL_LONG(index); } break; } } ZEND_HASH_FOREACH_END(); lock_.unlock(); } void ZendArray::sort(bool renumber) { lock_.lock(); zend_hash_sort(&ht, ArrayItem::compare, renumber); lock_.unlock(); } ZendArray *ZendArray::from(zend_array *src) { zend_string *key; zend_ulong index; zval *tmp; auto *result = new ZendArray(); ZEND_HASH_FOREACH_KEY_VAL(src, index, key, tmp) { ZVAL_DEREF(tmp); if (key) { result->add(key, tmp); } else { result->add(index, tmp); } } ZEND_HASH_FOREACH_END(); return result; } #endif ================================================ FILE: ext-src/swoole_thread_arraylist.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #ifdef SW_THREAD #include "php_swoole_thread.h" SW_EXTERN_C_BEGIN #include "stubs/php_swoole_thread_arraylist_arginfo.h" SW_EXTERN_C_END zend_class_entry *swoole_thread_arraylist_ce; static zend_object_handlers swoole_thread_arraylist_handlers; struct ThreadArrayListObject { ZendArray *list; zend_object std; }; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_arraylist, __construct); static PHP_METHOD(swoole_thread_arraylist, offsetGet); static PHP_METHOD(swoole_thread_arraylist, offsetExists); static PHP_METHOD(swoole_thread_arraylist, offsetSet); static PHP_METHOD(swoole_thread_arraylist, offsetUnset); static PHP_METHOD(swoole_thread_arraylist, find); static PHP_METHOD(swoole_thread_arraylist, count); static PHP_METHOD(swoole_thread_arraylist, incr); static PHP_METHOD(swoole_thread_arraylist, decr); static PHP_METHOD(swoole_thread_arraylist, clean); static PHP_METHOD(swoole_thread_arraylist, toArray); static PHP_METHOD(swoole_thread_arraylist, sort); SW_EXTERN_C_END static sw_inline ThreadArrayListObject *arraylist_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_arraylist_handlers.offset); } static void arraylist_free_object(zend_object *object) { auto ao = arraylist_fetch_object(object); if (ao->list) { ao->list->del_ref(); ao->list = nullptr; } zend_object_std_dtor(object); } static zend_object *arraylist_create_object(zend_class_entry *ce) { auto ao = static_cast(zend_object_alloc(sizeof(ThreadArrayListObject), ce)); zend_object_std_init(&ao->std, ce); object_properties_init(&ao->std, ce); ao->std.handlers = &swoole_thread_arraylist_handlers; return &ao->std; } static ThreadArrayListObject *arraylist_fetch_object_check(const zval *zobject) { auto ao = arraylist_fetch_object(Z_OBJ_P(zobject)); if (!ao->list) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return ao; } ThreadResource *php_swoole_thread_arraylist_cast(const zval *zobject) { return arraylist_fetch_object_check(zobject)->list; } void php_swoole_thread_arraylist_create(zval *return_value, ThreadResource *resource) { auto obj = arraylist_create_object(swoole_thread_arraylist_ce); auto ao = arraylist_fetch_object(obj); ao->list = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } // clang-format off static const zend_function_entry swoole_thread_arraylist_methods[] = { PHP_ME(swoole_thread_arraylist, __construct, arginfo_class_Swoole_Thread_ArrayList___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, offsetGet, arginfo_class_Swoole_Thread_ArrayList_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, offsetExists, arginfo_class_Swoole_Thread_ArrayList_offsetExists, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, offsetSet, arginfo_class_Swoole_Thread_ArrayList_offsetSet, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, offsetUnset, arginfo_class_Swoole_Thread_ArrayList_offsetUnset, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, find, arginfo_class_Swoole_Thread_ArrayList_find, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, incr, arginfo_class_Swoole_Thread_ArrayList_incr, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, decr, arginfo_class_Swoole_Thread_ArrayList_decr, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, clean, arginfo_class_Swoole_Thread_ArrayList_clean, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, count, arginfo_class_Swoole_Thread_ArrayList_count, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, toArray, arginfo_class_Swoole_Thread_ArrayList_toArray, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_arraylist, sort, arginfo_class_Swoole_Thread_ArrayList_sort, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_arraylist_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_arraylist, "Swoole\\Thread\\ArrayList", nullptr, swoole_thread_arraylist_methods); swoole_thread_arraylist_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_arraylist, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_arraylist, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_thread_arraylist, arraylist_create_object, arraylist_free_object, ThreadArrayListObject, std); zend_class_implements(swoole_thread_arraylist_ce, 2, zend_ce_arrayaccess, zend_ce_countable); zend_declare_property_long(swoole_thread_arraylist_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); } static PHP_METHOD(swoole_thread_arraylist, __construct) { zend_array *array = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_HT_OR_NULL(array) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object(Z_OBJ_P(ZEND_THIS)); if (ao->list != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); return; } if (array) { if (!zend_array_is_list(array)) { zend_throw_error(nullptr, "the parameter $array must be an array of type list"); return; } ao->list = ZendArray::from(array); } else { ao->list = new ZendArray(); } } static PHP_METHOD(swoole_thread_arraylist, offsetGet) { zend_long index; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(index) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object_check(ZEND_THIS); if (!ao->list->index_offsetGet(index, return_value)) { zend_throw_exception(swoole_exception_ce, "out of range", -1); } } static PHP_METHOD(swoole_thread_arraylist, offsetExists) { zend_long index; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(index) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->index_offsetExists(index, return_value); } static PHP_METHOD(swoole_thread_arraylist, offsetSet) { zval *zkey; zval *zvalue; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zkey) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object_check(ZEND_THIS); zend_long index = ZVAL_IS_NULL(zkey) ? -1 : zval_get_long(zkey); if (!ao->list->index_offsetSet(index, zvalue)) { zend_throw_exception(swoole_exception_ce, "out of range", -1); } } static PHP_METHOD(swoole_thread_arraylist, incr) { INIT_ARRAY_INCR_PARAMS auto ao = arraylist_fetch_object_check(ZEND_THIS); if (!ao->list->index_incr(zkey, zvalue, return_value)) { zend_throw_exception(swoole_exception_ce, "out of range", -1); } } static PHP_METHOD(swoole_thread_arraylist, decr) { INIT_ARRAY_INCR_PARAMS auto ao = arraylist_fetch_object_check(ZEND_THIS); if (!ao->list->index_decr(zkey, zvalue, return_value)) { zend_throw_exception(swoole_exception_ce, "out of range", -1); } } static PHP_METHOD(swoole_thread_arraylist, offsetUnset) { zend_long index; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(index) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->index_offsetUnset(index); } static PHP_METHOD(swoole_thread_arraylist, find) { zval *zvalue; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->find(zvalue, return_value); } static PHP_METHOD(swoole_thread_arraylist, count) { auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->count(return_value); } static PHP_METHOD(swoole_thread_arraylist, clean) { auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->clean(); } static PHP_METHOD(swoole_thread_arraylist, toArray) { auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->to_array(return_value); } static PHP_METHOD(swoole_thread_arraylist, sort) { auto ao = arraylist_fetch_object_check(ZEND_THIS); ao->list->sort(true); } #endif ================================================ FILE: ext-src/swoole_thread_atomic.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_thread.h" #ifdef SW_THREAD BEGIN_EXTERN_C() #include "stubs/php_swoole_thread_atomic_arginfo.h" END_EXTERN_C() zend_class_entry *swoole_thread_atomic_ce; static zend_object_handlers swoole_thread_atomic_handlers; zend_class_entry *swoole_thread_atomic_long_ce; static zend_object_handlers swoole_thread_atomic_long_handlers; struct AtomicResource : ThreadResource { sw_atomic_t value; explicit AtomicResource(zend_long _value) { value = _value; } ~AtomicResource() override = default; }; struct AtomicObject { AtomicResource *atomic; zend_object std; }; static sw_inline AtomicObject *atomic_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_atomic_handlers.offset); } static sw_atomic_t *atomic_get_ptr(const zval *zobject) { return &atomic_fetch_object(Z_OBJ_P(zobject))->atomic->value; } static void atomic_free_object(zend_object *object) { AtomicObject *o = atomic_fetch_object(object); if (o->atomic) { o->atomic->del_ref(); o->atomic = nullptr; } zend_object_std_dtor(object); } static zend_object *atomic_create_object(zend_class_entry *ce) { const auto atomic = static_cast(zend_object_alloc(sizeof(AtomicObject), ce)); zend_object_std_init(&atomic->std, ce); object_properties_init(&atomic->std, ce); atomic->std.handlers = &swoole_thread_atomic_handlers; return &atomic->std; } struct AtomicLongResource : ThreadResource { sw_atomic_long_t value; explicit AtomicLongResource(zend_long _value) { value = _value; } ~AtomicLongResource() override = default; }; struct AtomicLongObject { AtomicLongResource *atomic; zend_object std; }; static sw_inline AtomicLongObject *atomic_long_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_atomic_long_handlers.offset); } static sw_atomic_long_t *atomic_long_get_ptr(const zval *zobject) { return &atomic_long_fetch_object(Z_OBJ_P(zobject))->atomic->value; } static void atomic_long_free_object(zend_object *object) { AtomicLongObject *o = atomic_long_fetch_object(object); if (o->atomic) { o->atomic->del_ref(); o->atomic = nullptr; } zend_object_std_dtor(object); } static zend_object *atomic_long_create_object(zend_class_entry *ce) { auto atomic_long = static_cast(zend_object_alloc(sizeof(AtomicLongObject), ce)); zend_object_std_init(&atomic_long->std, ce); object_properties_init(&atomic_long->std, ce); atomic_long->std.handlers = &swoole_thread_atomic_long_handlers; return &atomic_long->std; } ThreadResource *php_swoole_thread_atomic_cast(const zval *zobject) { return atomic_fetch_object(Z_OBJ_P(zobject))->atomic; } ThreadResource *php_swoole_thread_atomic_long_cast(const zval *zobject) { return atomic_long_fetch_object(Z_OBJ_P(zobject))->atomic; } void php_swoole_thread_atomic_create(zval *return_value, ThreadResource *resource) { auto obj = atomic_create_object(swoole_thread_atomic_ce); auto ao = atomic_fetch_object(obj); ao->atomic = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } void php_swoole_thread_atomic_long_create(zval *return_value, ThreadResource *resource) { auto obj = atomic_long_create_object(swoole_thread_atomic_long_ce); auto ao = atomic_long_fetch_object(obj); ao->atomic = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_atomic, __construct); static PHP_METHOD(swoole_thread_atomic, add); static PHP_METHOD(swoole_thread_atomic, sub); static PHP_METHOD(swoole_thread_atomic, get); static PHP_METHOD(swoole_thread_atomic, set); static PHP_METHOD(swoole_thread_atomic, cmpset); static PHP_METHOD(swoole_thread_atomic, wait); static PHP_METHOD(swoole_thread_atomic, wakeup); static PHP_METHOD(swoole_thread_atomic_long, __construct); static PHP_METHOD(swoole_thread_atomic_long, add); static PHP_METHOD(swoole_thread_atomic_long, sub); static PHP_METHOD(swoole_thread_atomic_long, get); static PHP_METHOD(swoole_thread_atomic_long, set); static PHP_METHOD(swoole_thread_atomic_long, cmpset); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_thread_atomic_methods[] = { PHP_ME(swoole_thread_atomic, __construct, arginfo_class_Swoole_Thread_Atomic___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, add, arginfo_class_Swoole_Thread_Atomic_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, sub, arginfo_class_Swoole_Thread_Atomic_sub, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, get, arginfo_class_Swoole_Thread_Atomic_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, set, arginfo_class_Swoole_Thread_Atomic_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, wait, arginfo_class_Swoole_Thread_Atomic_wait, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, wakeup, arginfo_class_Swoole_Thread_Atomic_wakeup, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic, cmpset, arginfo_class_Swoole_Thread_Atomic_cmpset, ZEND_ACC_PUBLIC) PHP_FE_END }; static const zend_function_entry swoole_thread_atomic_long_methods[] = { PHP_ME(swoole_thread_atomic_long, __construct, arginfo_class_Swoole_Thread_Atomic_Long___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic_long, add, arginfo_class_Swoole_Thread_Atomic_Long_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic_long, sub, arginfo_class_Swoole_Thread_Atomic_Long_sub, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic_long, get, arginfo_class_Swoole_Thread_Atomic_Long_get, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic_long, set, arginfo_class_Swoole_Thread_Atomic_Long_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_atomic_long, cmpset, arginfo_class_Swoole_Thread_Atomic_Long_cmpset, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_atomic_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_atomic, "Swoole\\Thread\\Atomic", nullptr, swoole_thread_atomic_methods); swoole_thread_atomic_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_atomic, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_atomic, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_atomic, atomic_create_object, atomic_free_object, AtomicObject, std); SW_INIT_CLASS_ENTRY( swoole_thread_atomic_long, "Swoole\\Thread\\Atomic\\Long", nullptr, swoole_thread_atomic_long_methods); swoole_thread_atomic_long_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_atomic_long, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_atomic_long, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_thread_atomic_long, atomic_long_create_object, atomic_long_free_object, AtomicLongObject, std); } PHP_METHOD(swoole_thread_atomic, __construct) { auto o = atomic_fetch_object(Z_OBJ_P(ZEND_THIS)); zend_long value = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (o->atomic) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); return; } o->atomic = new AtomicResource(value); } PHP_METHOD(swoole_thread_atomic, add) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(add_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_add_fetch(atomic, (uint32_t) add_value)); } PHP_METHOD(swoole_thread_atomic, sub) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(sub_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_sub_fetch(atomic, (uint32_t) sub_value)); } PHP_METHOD(swoole_thread_atomic, get) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); RETURN_LONG(*atomic); } PHP_METHOD(swoole_thread_atomic, set) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic = (uint32_t) set_value; } PHP_METHOD(swoole_thread_atomic, cmpset) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(cmp_value) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(sw_atomic_cmp_set(atomic, (sw_atomic_t) cmp_value, (sw_atomic_t) set_value)); } PHP_METHOD(swoole_thread_atomic, wait) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); double timeout = 1.0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_RETURN(sw_atomic_futex_wait(atomic, timeout)); } PHP_METHOD(swoole_thread_atomic, wakeup) { sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long n = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(n) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); SW_CHECK_RETURN(sw_atomic_futex_wakeup(atomic, (int) n)); } PHP_METHOD(swoole_thread_atomic_long, __construct) { auto o = atomic_long_fetch_object(Z_OBJ_P(ZEND_THIS)); zend_long value = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (o->atomic) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } o->atomic = new AtomicLongResource(value); } PHP_METHOD(swoole_thread_atomic_long, add) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(add_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_add_fetch(atomic_long, (sw_atomic_long_t) add_value)); } PHP_METHOD(swoole_thread_atomic_long, sub) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(sub_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_LONG(sw_atomic_sub_fetch(atomic_long, (sw_atomic_long_t) sub_value)); } PHP_METHOD(swoole_thread_atomic_long, get) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); RETURN_LONG(*atomic_long); } PHP_METHOD(swoole_thread_atomic_long, set) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); *atomic_long = (sw_atomic_long_t) set_value; } PHP_METHOD(swoole_thread_atomic_long, cmpset) { sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_LONG(cmp_value) Z_PARAM_LONG(set_value) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); RETURN_BOOL(sw_atomic_cmp_set(atomic_long, (sw_atomic_long_t) cmp_value, (sw_atomic_long_t) set_value)); } #endif ================================================ FILE: ext-src/swoole_thread_barrier.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "php_swoole_thread.h" #include "swoole_lock.h" #ifdef SW_THREAD BEGIN_EXTERN_C() #include "stubs/php_swoole_thread_barrier_arginfo.h" END_EXTERN_C() using swoole::Barrier; zend_class_entry *swoole_thread_barrier_ce; static zend_object_handlers swoole_thread_barrier_handlers; struct BarrierResource : ThreadResource { Barrier barrier_; explicit BarrierResource(int count) { barrier_.init(false, count); } void wait() { barrier_.wait(); } ~BarrierResource() override { barrier_.destroy(); } }; struct BarrierObject { BarrierResource *barrier; zend_object std; }; static sw_inline BarrierObject *barrier_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_barrier_handlers.offset); } static BarrierResource *barrier_get_ptr(const zval *zobject) { return barrier_fetch_object(Z_OBJ_P(zobject))->barrier; } static BarrierResource *barrier_get_and_check_ptr(const zval *zobject) { BarrierResource *barrier = barrier_get_ptr(zobject); if (UNEXPECTED(!barrier)) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return barrier; } static void barrier_free_object(zend_object *object) { auto bo = barrier_fetch_object(object); if (bo->barrier) { bo->barrier->del_ref(); bo->barrier = nullptr; } zend_object_std_dtor(object); } static zend_object *barrier_create_object(zend_class_entry *ce) { auto bo = static_cast(zend_object_alloc(sizeof(BarrierObject), ce)); zend_object_std_init(&bo->std, ce); object_properties_init(&bo->std, ce); bo->std.handlers = &swoole_thread_barrier_handlers; return &bo->std; } ThreadResource *php_swoole_thread_barrier_cast(const zval *zobject) { return barrier_fetch_object(Z_OBJ_P(zobject))->barrier; } void php_swoole_thread_barrier_create(zval *return_value, ThreadResource *resource) { auto obj = barrier_create_object(swoole_thread_barrier_ce); auto bo = barrier_fetch_object(obj); bo->barrier = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_barrier, __construct); static PHP_METHOD(swoole_thread_barrier, wait); SW_EXTERN_C_END // clang-format off static constexpr zend_function_entry swoole_thread_barrier_methods[] = { PHP_ME(swoole_thread_barrier, __construct, arginfo_class_Swoole_Thread_Barrier___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_barrier, wait, arginfo_class_Swoole_Thread_Barrier_wait, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_barrier_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_barrier, "Swoole\\Thread\\Barrier", nullptr, swoole_thread_barrier_methods); swoole_thread_barrier_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_barrier, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_barrier, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_barrier, barrier_create_object, barrier_free_object, BarrierObject, std); } static PHP_METHOD(swoole_thread_barrier, __construct) { auto bo = barrier_fetch_object(Z_OBJ_P(ZEND_THIS)); if (bo->barrier != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); return; } zend_long count; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(count) ZEND_PARSE_PARAMETERS_END(); if (count < 2) { zend_throw_exception( swoole_exception_ce, "The parameter $count must be greater than 1", SW_ERROR_INVALID_PARAMS); return; } bo->barrier = new BarrierResource(count); } static PHP_METHOD(swoole_thread_barrier, wait) { BarrierResource *barrier = barrier_get_and_check_ptr(ZEND_THIS); if (barrier) { barrier->wait(); } } #endif ================================================ FILE: ext-src/swoole_thread_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_private.h" #include "php_swoole_thread.h" #include "swoole_lock.h" #include "swoole_timer.h" #ifdef SW_THREAD BEGIN_EXTERN_C() #include "stubs/php_swoole_thread_lock_arginfo.h" END_EXTERN_C() using swoole::Lock; using swoole::Mutex; #ifdef HAVE_SPINLOCK using swoole::SpinLock; #endif #ifdef HAVE_RWLOCK using swoole::RWLock; #endif zend_class_entry *swoole_thread_lock_ce; static zend_object_handlers swoole_thread_lock_handlers; struct LockResource : public ThreadResource { Lock *lock_; LockResource(int type) : ThreadResource() { switch (type) { #ifdef HAVE_SPINLOCK case Lock::SPIN_LOCK: lock_ = new SpinLock(false); break; #endif #ifdef HAVE_RWLOCK case Lock::RW_LOCK: lock_ = new RWLock(false); break; #endif case Lock::MUTEX: default: lock_ = new Mutex(false); break; } } ~LockResource() override { delete lock_; } }; struct ThreadLockObject { LockResource *lock; zend_object std; }; static sw_inline ThreadLockObject *thread_lock_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_lock_handlers.offset); } static Lock *thread_lock_get_ptr(const zval *zobject) { return thread_lock_fetch_object(Z_OBJ_P(zobject))->lock->lock_; } static Lock *thread_lock_get_and_check_ptr(const zval *zobject) { Lock *lock = thread_lock_get_ptr(zobject); if (!lock) { php_swoole_fatal_error(E_ERROR, "must call constructor first"); } return lock; } static void thread_lock_free_object(zend_object *object) { ThreadLockObject *o = thread_lock_fetch_object(object); if (o->lock) { o->lock->del_ref(); o->lock = nullptr; } zend_object_std_dtor(object); } static zend_object *thread_lock_create_object(zend_class_entry *ce) { auto lock = static_cast(zend_object_alloc(sizeof(ThreadLockObject), ce)); zend_object_std_init(&lock->std, ce); object_properties_init(&lock->std, ce); lock->std.handlers = &swoole_thread_lock_handlers; return &lock->std; } ThreadResource *php_swoole_thread_lock_cast(const zval *zobject) { return thread_lock_fetch_object(Z_OBJ_P(zobject))->lock; } void php_swoole_thread_lock_create(zval *return_value, ThreadResource *resource) { auto obj = thread_lock_create_object(swoole_thread_lock_ce); auto lo = thread_lock_fetch_object(obj); lo->lock = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_lock, __construct); static PHP_METHOD(swoole_thread_lock, lock); static PHP_METHOD(swoole_thread_lock, unlock); SW_EXTERN_C_END // clang-format off static constexpr zend_function_entry swoole_thread_lock_methods[] = { PHP_ME(swoole_thread_lock, __construct, arginfo_class_Swoole_Thread_Lock___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_lock, lock, arginfo_class_Swoole_Thread_Lock_lock, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_lock, unlock, arginfo_class_Swoole_Thread_Lock_unlock, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_lock_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_lock, "Swoole\\Thread\\Lock", nullptr, swoole_thread_lock_methods); swoole_thread_lock_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_lock, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_lock, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT( swoole_thread_lock, thread_lock_create_object, thread_lock_free_object, ThreadLockObject, std); zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("MUTEX"), Lock::MUTEX); #ifdef HAVE_RWLOCK zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("RWLOCK"), Lock::RW_LOCK); #endif #ifdef HAVE_SPINLOCK zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("SPINLOCK"), Lock::SPIN_LOCK); #endif zend_declare_property_long(swoole_thread_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); } static PHP_METHOD(swoole_thread_lock, __construct) { auto o = thread_lock_fetch_object(Z_OBJ_P(ZEND_THIS)); if (o->lock != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend_long type = Lock::MUTEX; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END(); o->lock = new LockResource(type); } static PHP_METHOD(swoole_thread_lock, lock) { zend_long operation = LOCK_EX; double timeout = -1; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_LONG(operation) Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Lock *lock = thread_lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->lock(operation, swoole::sec2msec(timeout))); } static PHP_METHOD(swoole_thread_lock, unlock) { Lock *lock = thread_lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->unlock()); } #endif ================================================ FILE: ext-src/swoole_thread_map.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #ifdef SW_THREAD #include "php_swoole_thread.h" SW_EXTERN_C_BEGIN #include "stubs/php_swoole_thread_map_arginfo.h" SW_EXTERN_C_END zend_class_entry *swoole_thread_map_ce; static zend_object_handlers swoole_thread_map_handlers; struct ThreadMapObject { ZendArray *map; zend_object std; }; static sw_inline ThreadMapObject *map_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_map_handlers.offset); } static void map_free_object(zend_object *object) { auto mo = map_fetch_object(object); if (mo->map) { mo->map->del_ref(); mo->map = nullptr; } zend_object_std_dtor(object); } static zend_object *map_create_object(zend_class_entry *ce) { auto mo = static_cast(zend_object_alloc(sizeof(ThreadMapObject), ce)); zend_object_std_init(&mo->std, ce); object_properties_init(&mo->std, ce); mo->std.handlers = &swoole_thread_map_handlers; return &mo->std; } static ThreadMapObject *map_fetch_object_check(const zval *zobject) { ThreadMapObject *map = map_fetch_object(Z_OBJ_P(zobject)); if (!map->map) { php_swoole_fatal_error(E_ERROR, "must call constructor first"); } return map; } ThreadResource *php_swoole_thread_map_cast(const zval *zobject) { return map_fetch_object(Z_OBJ_P(zobject))->map; } void php_swoole_thread_map_create(zval *return_value, ThreadResource *resource) { auto obj = map_create_object(swoole_thread_map_ce); auto mo = map_fetch_object(obj); mo->map = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_map, __construct); static PHP_METHOD(swoole_thread_map, offsetGet); static PHP_METHOD(swoole_thread_map, offsetExists); static PHP_METHOD(swoole_thread_map, offsetSet); static PHP_METHOD(swoole_thread_map, offsetUnset); static PHP_METHOD(swoole_thread_map, find); static PHP_METHOD(swoole_thread_map, count); static PHP_METHOD(swoole_thread_map, keys); static PHP_METHOD(swoole_thread_map, values); static PHP_METHOD(swoole_thread_map, incr); static PHP_METHOD(swoole_thread_map, decr); static PHP_METHOD(swoole_thread_map, add); static PHP_METHOD(swoole_thread_map, update); static PHP_METHOD(swoole_thread_map, clean); static PHP_METHOD(swoole_thread_map, toArray); static PHP_METHOD(swoole_thread_map, sort); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_thread_map_methods[] = { PHP_ME(swoole_thread_map, __construct, arginfo_class_Swoole_Thread_Map___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, offsetGet, arginfo_class_Swoole_Thread_Map_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, offsetExists, arginfo_class_Swoole_Thread_Map_offsetExists, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, offsetSet, arginfo_class_Swoole_Thread_Map_offsetSet, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, offsetUnset, arginfo_class_Swoole_Thread_Map_offsetUnset, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, find, arginfo_class_Swoole_Thread_Map_find, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, count, arginfo_class_Swoole_Thread_Map_count, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, incr, arginfo_class_Swoole_Thread_Map_incr, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, decr, arginfo_class_Swoole_Thread_Map_decr, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, add, arginfo_class_Swoole_Thread_Map_add, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, update, arginfo_class_Swoole_Thread_Map_update, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, clean, arginfo_class_Swoole_Thread_Map_clean, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, keys, arginfo_class_Swoole_Thread_Map_keys, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, values, arginfo_class_Swoole_Thread_Map_values, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, toArray, arginfo_class_Swoole_Thread_Map_toArray, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_map, sort, arginfo_class_Swoole_Thread_Map_sort, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_map_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_map, "Swoole\\Thread\\Map", nullptr, swoole_thread_map_methods); swoole_thread_map_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_map, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_map, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_map, map_create_object, map_free_object, ThreadMapObject, std); zend_class_implements(swoole_thread_map_ce, 2, zend_ce_arrayaccess, zend_ce_countable); } static PHP_METHOD(swoole_thread_map, __construct) { zend_array *array = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_HT_OR_NULL(array) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object(Z_OBJ_P(ZEND_THIS)); if (mo->map != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); return; } if (array) { mo->map = ZendArray::from(array); } else { mo->map = new ZendArray(); } } static int handle_array_key(const zval *key, zend_ulong *idx) { switch (Z_TYPE_P(key)) { case IS_STRING: return _zend_handle_numeric_str(Z_STRVAL_P(key), Z_STRLEN_P(key), idx) ? IS_LONG : IS_STRING; case IS_LONG: *idx = Z_LVAL_P(key); return IS_LONG; case IS_NULL: return IS_NULL; case IS_DOUBLE: *idx = zend_dval_to_lval_safe(Z_DVAL_P(key)); return IS_LONG; case IS_FALSE: *idx = 0; return IS_LONG; case IS_TRUE: *idx = 1; return IS_LONG; case IS_RESOURCE: zend_use_resource_as_offset(key); *idx = Z_RES_HANDLE_P(key); return IS_LONG; default: zend_argument_type_error(1, "Illegal offset type"); return IS_UNDEF; } } #define ZEND_ARRAY_CALL_METHOD(array, method, zkey, ...) \ zend_ulong idx; \ int type_of_key = handle_array_key(zkey, &idx); \ if (type_of_key == IS_LONG) { \ array->intkey_##method(idx, ##__VA_ARGS__); \ } else if (type_of_key == IS_STRING) { \ array->strkey_##method(zkey, ##__VA_ARGS__); \ } else if (type_of_key == IS_NULL) { \ zval empty_str; \ ZVAL_EMPTY_STRING(&empty_str); \ array->strkey_##method(&empty_str, ##__VA_ARGS__); \ } else { \ zend_type_error("Illegal offset type"); \ } static PHP_METHOD(swoole_thread_map, offsetGet) { zval *zkey; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zkey) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, offsetGet, zkey, return_value); } static PHP_METHOD(swoole_thread_map, offsetExists) { zval *zkey; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zkey) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, offsetExists, zkey, return_value); } static PHP_METHOD(swoole_thread_map, offsetSet) { zval *zkey; zval *zvalue; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zkey) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, offsetSet, zkey, zvalue); } static PHP_METHOD(swoole_thread_map, incr) { INIT_ARRAY_INCR_PARAMS auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, incr, zkey, zvalue, return_value); } static PHP_METHOD(swoole_thread_map, decr) { INIT_ARRAY_INCR_PARAMS auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, decr, zkey, zvalue, return_value); } static PHP_METHOD(swoole_thread_map, add) { zval *zkey; zval *zvalue; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zkey) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, add, zkey, zvalue, return_value); } static PHP_METHOD(swoole_thread_map, update) { zval *zkey; zval *zvalue; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(zkey) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, update, zkey, zvalue, return_value); } static PHP_METHOD(swoole_thread_map, offsetUnset) { zval *zkey; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zkey) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); ZEND_ARRAY_CALL_METHOD(mo->map, offsetUnset, zkey); } static PHP_METHOD(swoole_thread_map, find) { zval *zvalue; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zvalue) ZEND_PARSE_PARAMETERS_END(); auto mo = map_fetch_object_check(ZEND_THIS); mo->map->find(zvalue, return_value); } static PHP_METHOD(swoole_thread_map, count) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->count(return_value); } static PHP_METHOD(swoole_thread_map, keys) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->keys(return_value); } static PHP_METHOD(swoole_thread_map, values) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->values(return_value); } static PHP_METHOD(swoole_thread_map, toArray) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->to_array(return_value); } static PHP_METHOD(swoole_thread_map, clean) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->clean(); } static PHP_METHOD(swoole_thread_map, sort) { auto mo = map_fetch_object_check(ZEND_THIS); mo->map->sort(false); } #endif ================================================ FILE: ext-src/swoole_thread_queue.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #ifdef SW_THREAD #include "php_swoole_thread.h" #include "stubs/php_swoole_thread_queue_arginfo.h" #include #include zend_class_entry *swoole_thread_queue_ce; static zend_object_handlers swoole_thread_queue_handlers; struct Queue : ThreadResource { std::queue queue; std::mutex lock_; std::condition_variable cv_; enum { NOTIFY_NONE = 0, NOTIFY_ONE = 1, NOTIFY_ALL = 2, }; Queue() = default; ~Queue() override { clean(); } void push(zval *zvalue) { auto item = new ArrayItem(zvalue); lock_.lock(); queue.push(item); lock_.unlock(); } void pop(zval *return_value) { ArrayItem *item = nullptr; lock_.lock(); if (!queue.empty()) { item = queue.front(); queue.pop(); } lock_.unlock(); if (item) { item->fetch(return_value); delete item; } } void push_notify(zval *zvalue, bool notify_all) { push(zvalue); if (notify_all) { cv_.notify_all(); } else { cv_.notify_one(); } } void pop_wait(zval *return_value, double timeout) { ArrayItem *item = nullptr; std::unique_lock _lock(lock_); if (timeout > 0) { cv_.wait_for(_lock, std::chrono::duration(timeout), [this] { return !queue.empty(); }); } else { cv_.wait(_lock, [this] { return !queue.empty(); }); } if (!queue.empty()) { item = queue.front(); queue.pop(); } else { // All threads have been awakened, // but the data has already been acquired by other thread, returning NULL. RETVAL_NULL(); swoole_set_last_error(SW_ERROR_NO_PAYLOAD); } if (item) { item->fetch(return_value); delete item; } } void count(zval *return_value) { lock_.lock(); RETVAL_LONG(queue.size()); lock_.unlock(); } void clean() { lock_.lock(); while (!queue.empty()) { const ArrayItem *item = queue.front(); delete item; queue.pop(); } lock_.unlock(); } }; struct ThreadQueueObject { Queue *queue; zend_object std; }; static sw_inline ThreadQueueObject *queue_fetch_object(zend_object *obj) { return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_queue_handlers.offset); } static void queue_free_object(zend_object *object) { ThreadQueueObject *qo = queue_fetch_object(object); if (qo->queue) { qo->queue->del_ref(); qo->queue = nullptr; } zend_object_std_dtor(object); } static zend_object *queue_create_object(zend_class_entry *ce) { const auto qo = static_cast(zend_object_alloc(sizeof(ThreadQueueObject), ce)); zend_object_std_init(&qo->std, ce); object_properties_init(&qo->std, ce); qo->std.handlers = &swoole_thread_queue_handlers; return &qo->std; } ThreadQueueObject *queue_fetch_object_check(const zval *zobject) { ThreadQueueObject *qo = queue_fetch_object(Z_OBJ_P(zobject)); if (!qo->queue) { php_swoole_fatal_error(E_ERROR, "must call constructor first"); } return qo; } ThreadResource *php_swoole_thread_queue_cast(const zval *zobject) { return queue_fetch_object(Z_OBJ_P(zobject))->queue; } void php_swoole_thread_queue_create(zval *return_value, ThreadResource *resource) { auto obj = queue_create_object(swoole_thread_queue_ce); auto qo = queue_fetch_object(obj); qo->queue = dynamic_cast(resource); ZVAL_OBJ(return_value, obj); } SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_thread_queue, __construct); static PHP_METHOD(swoole_thread_queue, push); static PHP_METHOD(swoole_thread_queue, pop); static PHP_METHOD(swoole_thread_queue, count); static PHP_METHOD(swoole_thread_queue, clean); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_thread_queue_methods[] = { PHP_ME(swoole_thread_queue, __construct, arginfo_class_Swoole_Thread_Queue___construct, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_queue, push, arginfo_class_Swoole_Thread_Queue_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_queue, pop, arginfo_class_Swoole_Thread_Queue_pop, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_queue, clean, arginfo_class_Swoole_Thread_Queue_clean, ZEND_ACC_PUBLIC) PHP_ME(swoole_thread_queue, count, arginfo_class_Swoole_Thread_Queue_count, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on void php_swoole_thread_queue_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_thread_queue, "Swoole\\Thread\\Queue", nullptr, swoole_thread_queue_methods); swoole_thread_queue_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; SW_SET_CLASS_CLONEABLE(swoole_thread_queue, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_queue, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_queue, queue_create_object, queue_free_object, ThreadQueueObject, std); zend_class_implements(swoole_thread_queue_ce, 1, zend_ce_countable); zend_declare_class_constant_long(swoole_thread_queue_ce, ZEND_STRL("NOTIFY_ONE"), Queue::NOTIFY_ONE); zend_declare_class_constant_long(swoole_thread_queue_ce, ZEND_STRL("NOTIFY_ALL"), Queue::NOTIFY_ALL); } static PHP_METHOD(swoole_thread_queue, __construct) { auto qo = queue_fetch_object(Z_OBJ_P(ZEND_THIS)); if (qo->queue != nullptr) { zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); return; } qo->queue = new Queue(); } static PHP_METHOD(swoole_thread_queue, push) { zval *zvalue; zend_long notify_which = 0; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(zvalue) Z_PARAM_OPTIONAL Z_PARAM_LONG(notify_which) ZEND_PARSE_PARAMETERS_END(); auto qo = queue_fetch_object_check(ZEND_THIS); if (notify_which > 0) { qo->queue->push_notify(zvalue, notify_which == Queue::NOTIFY_ALL); } else { qo->queue->push(zvalue); } } static PHP_METHOD(swoole_thread_queue, pop) { double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END(); auto qo = queue_fetch_object_check(ZEND_THIS); if (timeout == 0) { qo->queue->pop(return_value); } else { qo->queue->pop_wait(return_value, timeout); } } static PHP_METHOD(swoole_thread_queue, count) { auto qo = queue_fetch_object_check(ZEND_THIS); qo->queue->count(return_value); } static PHP_METHOD(swoole_thread_queue, clean) { auto qo = queue_fetch_object_check(ZEND_THIS); qo->queue->clean(); } #endif ================================================ FILE: ext-src/swoole_timer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_process.h" #include "swoole_server.h" #include "ext/spl/spl_array.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_timer_arginfo.h" END_EXTERN_C() using swoole::Timer; using swoole::TimerNode; using zend::Function; zend_class_entry *swoole_timer_ce; static zend_object_handlers swoole_timer_handlers; static zend_class_entry *swoole_timer_iterator_ce; SW_EXTERN_C_BEGIN static PHP_FUNCTION(swoole_timer_after); static PHP_FUNCTION(swoole_timer_tick); static PHP_FUNCTION(swoole_timer_exists); static PHP_FUNCTION(swoole_timer_info); static PHP_FUNCTION(swoole_timer_stats); static PHP_FUNCTION(swoole_timer_list); static PHP_FUNCTION(swoole_timer_clear); static PHP_FUNCTION(swoole_timer_clear_all); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_timer_methods[] = { ZEND_FENTRY(tick, ZEND_FN(swoole_timer_tick), arginfo_swoole_timer_tick, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(after, ZEND_FN(swoole_timer_after), arginfo_swoole_timer_after, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(exists, ZEND_FN(swoole_timer_exists), arginfo_swoole_timer_exists, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(info, ZEND_FN(swoole_timer_info), arginfo_swoole_timer_info, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(stats, ZEND_FN(swoole_timer_stats), arginfo_swoole_timer_stats, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(list, ZEND_FN(swoole_timer_list), arginfo_swoole_timer_list, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(clear, ZEND_FN(swoole_timer_clear), arginfo_swoole_timer_clear, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(clearAll, ZEND_FN(swoole_timer_clear_all), arginfo_swoole_timer_clear_all, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void php_swoole_timer_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_timer, "Swoole\\Timer", nullptr, swoole_timer_methods); SW_SET_CLASS_CREATE(swoole_timer, sw_zend_create_object_deny); SW_INIT_CLASS_ENTRY_BASE(swoole_timer_iterator, "Swoole\\Timer\\Iterator", nullptr, nullptr, spl_ce_ArrayIterator); SW_FUNCTION_ALIAS( &swoole_timer_ce->function_table, "after", CG(function_table), "swoole_timer_after", arginfo_swoole_timer_tick); SW_FUNCTION_ALIAS( &swoole_timer_ce->function_table, "tick", CG(function_table), "swoole_timer_tick", arginfo_swoole_timer_after); SW_FUNCTION_ALIAS( &swoole_timer_ce->function_table, "info", CG(function_table), "swoole_timer_info", arginfo_swoole_timer_info); SW_FUNCTION_ALIAS( &swoole_timer_ce->function_table, "list", CG(function_table), "swoole_timer_list", arginfo_swoole_timer_list); SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "exists", CG(function_table), "swoole_timer_exists", arginfo_swoole_timer_exists); SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "stats", CG(function_table), "swoole_timer_stats", arginfo_swoole_timer_stats); SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "clear", CG(function_table), "swoole_timer_clear", arginfo_swoole_timer_clear); SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "clearAll", CG(function_table), "swoole_timer_clear_all", arginfo_swoole_timer_clear_all); SW_REGISTER_LONG_CONSTANT("SWOOLE_TIMER_MIN_MS", SW_TIMER_MIN_MS); SW_REGISTER_DOUBLE_CONSTANT("SWOOLE_TIMER_MIN_SEC", SW_TIMER_MIN_SEC); SW_REGISTER_LONG_CONSTANT("SWOOLE_TIMER_MAX_MS", SW_TIMER_MAX_MS); SW_REGISTER_DOUBLE_CONSTANT("SWOOLE_TIMER_MAX_SEC", SW_TIMER_MAX_SEC); } void php_swoole_timer_rshutdown() { php_swoole_timer_clear_all(); } static void timer_dtor(TimerNode *tnode) { auto *fci = static_cast(tnode->data); sw_zend_fci_params_discard(&fci->fci); sw_zend_fci_cache_discard(&fci->fci_cache); efree(fci); } bool php_swoole_timer_clear(TimerNode *tnode) { return swoole_timer_del(tnode); } bool php_swoole_timer_clear_all() { if (UNEXPECTED(!swoole_timer_is_available())) { return false; } size_t num = sw_timer()->count(), index = 0; auto **list = static_cast(emalloc(num * sizeof(TimerNode *))); for (auto &kv : sw_timer()->get_map()) { TimerNode *tnode = kv.second; if (tnode->type == TimerNode::TYPE_PHP) { list[index++] = tnode; } } while (index--) { swoole_timer_del(list[index]); } efree(list); return true; } static void timer_callback(Timer *timer, TimerNode *tnode) { auto *fci = static_cast(tnode->data); if (UNEXPECTED(!fci->call(nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onTimeout handler error", ZSTR_VAL(swoole_timer_ce->name)); } } static bool timer_if_use_reactor() { auto server = sw_server(); if (server) { return server->is_user_worker() || (server->is_task_worker() && server->task_enable_coroutine); } auto process_pool = sw_process_pool(); if (process_pool) { return !process_pool->is_master(); } return true; } static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) { zend_long ms; auto *fci = static_cast(ecalloc(1, sizeof(Function))); TimerNode *tnode; ZEND_PARSE_PARAMETERS_START(2, -1) Z_PARAM_LONG(ms) Z_PARAM_FUNC(fci->fci, fci->fci_cache) Z_PARAM_VARIADIC('*', fci->fci.params, fci->fci.param_count) ZEND_PARSE_PARAMETERS_END_EX(goto _failed); if (UNEXPECTED(ms < SW_TIMER_MIN_MS)) { php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_MS)); _failed: efree(fci); RETURN_FALSE; } if (UNEXPECTED(!sw_reactor() && timer_if_use_reactor())) { php_swoole_check_reactor(); } /** * In certain systems, such as macOS, zend_long is the long long type, * and it must be explicitly converted to long. */ tnode = swoole_timer_add((long) ms, persistent, timer_callback, fci); if (UNEXPECTED(!tnode)) { php_swoole_fatal_error(E_WARNING, "add timer failed"); goto _failed; } tnode->type = TimerNode::TYPE_PHP; tnode->destructor = timer_dtor; if (persistent) { if (fci->fci.param_count > 0) { auto params = static_cast(ecalloc(fci->fci.param_count + 1, sizeof(zval))); for (uint32_t i = 0; i < fci->fci.param_count; i++) { ZVAL_COPY(¶ms[i + 1], &fci->fci.params[i]); } fci->fci.params = params; } else { fci->fci.params = static_cast(emalloc(sizeof(zval))); } fci->fci.param_count += 1; ZVAL_LONG(fci->fci.params, tnode->id); } else { sw_zend_fci_params_persist(&fci->fci); } sw_zend_fci_cache_persist(&fci->fci_cache); RETURN_LONG(tnode->id); } static PHP_FUNCTION(swoole_timer_after) { timer_add(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_FUNCTION(swoole_timer_tick) { timer_add(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_FUNCTION(swoole_timer_exists) { if (UNEXPECTED(!swoole_timer_is_available())) { RETURN_FALSE; } zend_long id; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *tnode = swoole_timer_get(id); RETURN_BOOL(tnode && !tnode->removed); } static PHP_FUNCTION(swoole_timer_info) { if (UNEXPECTED(!swoole_timer_is_available())) { RETURN_FALSE; } zend_long id; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto *tnode = swoole_timer_get(id); if (UNEXPECTED(!tnode)) { RETURN_NULL(); } array_init(return_value); add_assoc_long(return_value, "exec_msec", tnode->exec_msec); add_assoc_long(return_value, "exec_count", tnode->exec_count); add_assoc_long(return_value, "interval", tnode->interval); add_assoc_long(return_value, "round", tnode->round); add_assoc_bool(return_value, "removed", tnode->removed); } static PHP_FUNCTION(swoole_timer_stats) { array_init(return_value); if (swoole_timer_is_available()) { add_assoc_bool(return_value, "initialized", 1); add_assoc_long(return_value, "num", SwooleTG.timer->count()); add_assoc_long(return_value, "round", SwooleTG.timer->get_round()); } else { add_assoc_bool(return_value, "initialized", 0); add_assoc_long(return_value, "num", 0); add_assoc_long(return_value, "round", 0); } } static PHP_FUNCTION(swoole_timer_list) { zval zlist; array_init(&zlist); if (EXPECTED(SwooleTG.timer)) { for (auto &kv : SwooleTG.timer->get_map()) { TimerNode *tnode = kv.second; if (tnode->type == TimerNode::TYPE_PHP) { add_next_index_long(&zlist, tnode->id); } } } object_init_ex(return_value, swoole_timer_iterator_ce); sw_zend_call_method_with_1_params( return_value, swoole_timer_iterator_ce, &swoole_timer_iterator_ce->constructor, "__construct", nullptr, &zlist); zval_ptr_dtor(&zlist); } static PHP_FUNCTION(swoole_timer_clear) { if (UNEXPECTED(!SwooleTG.timer)) { RETURN_FALSE; } else { zend_long id; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); TimerNode *tnode = swoole_timer_get(id); if (!tnode || tnode->type != TimerNode::TYPE_PHP) { RETURN_FALSE; } RETURN_BOOL(swoole_timer_del(tnode)); } } static PHP_FUNCTION(swoole_timer_clear_all) { RETURN_BOOL(php_swoole_timer_clear_all()); } ================================================ FILE: ext-src/swoole_tracer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_cxx.h" #include "php_swoole_api.h" #include "php_swoole_coroutine.h" #include "swoole_thread.h" #include #include #include #include #include #include #include using swoole::Coroutine; using swoole::PHPContext; using swoole::PHPCoroutine; BEGIN_EXTERN_C() #include "zend_builtin_functions.h" #include "zend_observer.h" #include "ext/standard/php_math.h" #include "ext/json/php_json.h" END_EXTERN_C() struct Prof { std::string fname; std::string file_pos; long began_at; long cid; }; struct ProfContext { std::array profs; uint8_t call_stack_level; }; struct AllocPoint { size_t size; size_t count; std::string at; }; struct AllocStat { size_t total_bytes; size_t count; }; struct BlockingDetectionSpan { zend_long began_at; size_t switch_count; PHPContext::SwapCallback swap_callback; }; static SW_THREAD_LOCAL struct { std::unordered_map points; std::unordered_set debug_points; std::unordered_map backtraces; std::unordered_map counters; size_t loop; zend_long threshold; bool profiling; std::unordered_map co_prof; ProfContext main_co_prof; std::string prof_root_path; zval prof_events; size_t fcall_count; size_t return_count; pid_t pid; } TracerG; #define DEBUG 0 #define DEBUG_LINE 16 constexpr int blocking_detection_func_reserve_index = 4; #if PHP_VERSION_ID < 80400 #define MM_LINE_DC #define MM_LINE_ORIG_DC #define MM_LINE_CC #define MM_LINE_ORIG_RELAY_CC #else #define MM_LINE_DC ZEND_FILE_LINE_DC #define MM_LINE_ORIG_DC ZEND_FILE_LINE_ORIG_DC #define MM_LINE_CC ZEND_FILE_LINE_RELAY_CC #define MM_LINE_ORIG_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC #endif static void *(*ori_malloc)(size_t MM_LINE_DC MM_LINE_ORIG_DC); static void (*ori_free)(void *MM_LINE_DC MM_LINE_ORIG_DC); static void *(*ori_realloc)(void *, size_t MM_LINE_DC MM_LINE_ORIG_DC); static void *tracer_malloc(size_t size MM_LINE_DC MM_LINE_ORIG_DC); static void tracer_free(void *ptr MM_LINE_DC MM_LINE_ORIG_DC); static void *tracer_realloc(void *ptr, size_t size MM_LINE_DC MM_LINE_ORIG_DC); static void hook_emalloc(); static void unhook_emalloc(); static long tracer_get_time_us() { return swoole::time(true); } static std::string zstr_to_std_string(zend_string *zs) { return std::string(ZSTR_VAL(zs), ZSTR_LEN(zs)); } static bool str_starts_with(const std::string &str, const std::string &prefix) { return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; } static bool str_ends_with(const std::string &str, const std::string &suffix) { return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } static void debug(const char *label, void *ptr, size_t size, uint32_t lineno) { #if DEBUG auto iter = TrackerG.points.find(ptr); if (iter != TrackerG.points.end() && str_ends_with(iter->second.at, ":" + std::to_string(lineno))) { printf("[%s]\tptr=%p, size=%lu, count=%lu, lineno=%u, at=%s\n", label, ptr, size, iter->second.count, lineno, iter->second.at.c_str()); if (strcmp(label, "update")) { TrackerG.debug_points.insert(ptr); } } if (strcmp(label, "free") && TrackerG.debug_points.find(ptr) != TrackerG.debug_points.end()) { TrackerG.debug_points.erase(ptr); printf("free ptr=%p\n", ptr); } #endif } static uint32_t tracer_get_executed_lineno(void) { zend_execute_data *ex = EG(current_execute_data); while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) { ex = ex->prev_execute_data; } if (ex && ex->opline) { if (EG(exception) && ex->opline->opcode == ZEND_HANDLE_EXCEPTION && ex->opline->lineno == 0 && EG(opline_before_exception)) { return EG(opline_before_exception)->lineno; } return ex->opline->lineno; } else { return 0; } } static zend_string *tracer_get_file_and_line() { const char *file = zend_get_executed_filename(); uint32_t lineno = tracer_get_executed_lineno(); char file_line_buf[1024]; if (lineno == 0 && strcmp(file, "[no active file]") == 0) { return nullptr; } size_t file_len = strlen(file); if (UNEXPECTED(file_len + 100 > sizeof(file_line_buf))) { char *tmp_buf = (char *) malloc(file_len + 100); if (!tmp_buf) { php_printf("tracker out of memory\n"); zend_bailout(); } sprintf(tmp_buf, "%s:%u", file, lineno); zend_string *ret = zend_string_init(tmp_buf, strlen(tmp_buf), 1); free(tmp_buf); return ret; } else { sprintf(file_line_buf, "%s:%u", file, lineno); return zend_string_init(file_line_buf, strlen(file_line_buf), 1); } } static zend_string *tracer_get_backtrace() { zval backtrace; zend_fetch_debug_backtrace(&backtrace, 0, 0, 0); auto backtrace_str = zend_trace_to_string(Z_ARRVAL(backtrace), false); zval_ptr_dtor(&backtrace); return backtrace_str; } static long tracer_get_pid() { return swoole_thread_get_native_id(); } static long tracer_get_tid() { return swoole_coroutine_get_id(); } static ProfContext &tracer_get_ctx(long tid) { if (tid == TracerG.pid) { return TracerG.main_co_prof; } else { if (TracerG.co_prof.find(tid) == TracerG.co_prof.end()) { TracerG.co_prof[tid] = {}; } return TracerG.co_prof[tid]; } } static zend_string *tracer_format_number(long num) { #if PHP_VERSION_ID >= 80300 return _php_math_number_format_long(num, 0, ".", 1, ",", 1); #else return _php_math_number_format((double) num, 0, '.', ','); #endif } static std::string format_bytes(uint64_t size) { const char *units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; int unitIndex = 0; double adjustedSize = static_cast(size); while (adjustedSize >= 1024.0 && unitIndex < 8) { adjustedSize /= 1024.0; unitIndex++; } std::ostringstream oss; oss << std::fixed << std::setprecision(2) << adjustedSize << " " << units[unitIndex]; return oss.str(); } static void tracer_leak_clear_stat(const std::string &at) { for (auto iter = TracerG.points.begin(); iter != TracerG.points.end(); iter++) { if (iter->second.at == at) { iter = TracerG.points.erase(iter); if (iter == TracerG.points.end()) { break; } } } } static void save_backtrace(const std::string &alloc_at) { if (TracerG.backtraces.find(alloc_at) == TracerG.backtraces.end()) { zval bt; zend_fetch_debug_backtrace(&bt, 0, DEBUG_BACKTRACE_IGNORE_ARGS, 0); TracerG.backtraces[alloc_at] = bt; } } static void add_point(void *ptr, size_t size, zend_string *current_file_lineno) { std::string alloc_at = zstr_to_std_string(current_file_lineno); TracerG.points[ptr] = { size, 1, alloc_at, }; auto iter = TracerG.counters.find(alloc_at); if (iter == TracerG.counters.end()) { TracerG.counters[alloc_at] = 1; } else { iter->second++; if (iter->second >= TracerG.threshold - 1) { save_backtrace(alloc_at); } } debug("new", ptr, size, DEBUG_LINE); } static void update_point(AllocPoint &point, void *new_ptr, size_t new_size) { TracerG.points[new_ptr] = { new_size, point.count + 1, point.at, }; auto counter_iter = TracerG.counters.find(point.at); if (counter_iter != TracerG.counters.end()) { counter_iter->second++; if (counter_iter->second >= TracerG.threshold - 1) { save_backtrace(point.at); } } debug("update", new_ptr, new_size, DEBUG_LINE); } static void del_point(void *ptr, decltype(TracerG.points)::iterator &iter) { debug("free", ptr, 0, DEBUG_LINE); const auto &alloc_at = iter->second.at; auto counter_iter = TracerG.counters.find(alloc_at); if (counter_iter != TracerG.counters.end()) { counter_iter->second--; } TracerG.points.erase(iter); } static void *tracer_malloc(size_t size MM_LINE_DC MM_LINE_ORIG_DC) { void *ptr; if (ori_malloc) { ptr = ori_malloc(size MM_LINE_CC MM_LINE_ORIG_RELAY_CC); } else { zend_mm_heap *heap = zend_mm_get_heap(); ptr = zend_mm_alloc(heap, size); } unhook_emalloc(); zend_string *current_file_lineno = tracer_get_file_and_line(); if (current_file_lineno) { add_point(ptr, size, current_file_lineno); zend_string_release(current_file_lineno); } hook_emalloc(); return ptr; } static void *tracer_realloc(void *ptr, size_t size MM_LINE_DC MM_LINE_ORIG_DC) { void *new_ptr; if (ori_realloc) { new_ptr = ori_realloc(ptr, size MM_LINE_CC MM_LINE_ORIG_RELAY_CC); } else { zend_mm_heap *heap = zend_mm_get_heap(); new_ptr = zend_mm_realloc(heap, ptr, size); } unhook_emalloc(); zend_string *current_file_lineno = tracer_get_file_and_line(); if (current_file_lineno) { auto iter = TracerG.points.find(ptr); if (iter != TracerG.points.end()) { update_point(iter->second, new_ptr, size); if (new_ptr != ptr) { TracerG.points.erase(iter); } } else { add_point(new_ptr, size, current_file_lineno); } zend_string_release(current_file_lineno); } hook_emalloc(); return new_ptr; } static void tracer_free(void *ptr MM_LINE_DC MM_LINE_ORIG_DC) { if (ori_free) { ori_free(ptr MM_LINE_CC MM_LINE_ORIG_RELAY_CC); } else { zend_mm_heap *heap = zend_mm_get_heap(); zend_mm_free(heap, ptr); } auto iter = TracerG.points.find(ptr); if (iter != TracerG.points.end()) { del_point(ptr, iter); } } static void hook_emalloc() { zend_mm_heap *heap = zend_mm_get_heap(); zend_mm_get_custom_handlers(heap, &ori_malloc, &ori_free, &ori_realloc); zend_mm_set_custom_handlers(heap, &tracer_malloc, &tracer_free, &tracer_realloc); } static void unhook_emalloc() { zend_mm_heap *heap = zend_mm_get_heap(); if (ori_malloc || ori_free || ori_realloc) { zend_mm_set_custom_handlers(heap, ori_malloc, ori_free, ori_realloc); ori_malloc = NULL; ori_free = NULL; ori_realloc = NULL; } else { *((int *) heap) = 0; } } static void profiling_begin(zend_string *root_symbol, zend_execute_data *execute_data) { zend_function *fbc = execute_data->func; auto type = fbc->type; if (!TracerG.profiling || type == ZEND_INTERNAL_FUNCTION) { return; } auto ts = tracer_get_time_us(); zend_string *fn_name = fbc->common.function_name; std::string fn; std::string file_pos; if (fbc->common.scope) { zend_string *class_name = fbc->common.scope->name; fn = std::string(ZSTR_VAL(class_name)) + "::" + std::string(ZSTR_VAL(fn_name)); } else { fn = std::string(ZSTR_VAL(fn_name)); } #if DEBUG printf("fn=%s, level=%d \n", fn.c_str(), TracerG.call_stack_level); #endif zend_string *current_file_lineno = tracer_get_file_and_line(); if (current_file_lineno) { file_pos = zstr_to_std_string(current_file_lineno); if (str_starts_with(file_pos, TracerG.prof_root_path)) { file_pos = file_pos.substr(TracerG.prof_root_path.length(), file_pos.length() - TracerG.prof_root_path.length()); } zend_string_release(current_file_lineno); } auto tid = tracer_get_tid(); Prof prof{ fn, file_pos, ts, tid, }; auto &ctx = tracer_get_ctx(tid); ctx.profs[ctx.call_stack_level++] = prof; } static void profiling_clear() { zval_ptr_dtor(&TracerG.prof_events); ZVAL_NULL(&TracerG.prof_events); TracerG.main_co_prof = {}; TracerG.co_prof.clear(); TracerG.profiling = false; } static void profiling_end() { auto tid = tracer_get_tid(); auto &ctx = tracer_get_ctx(tid); assert(ctx.call_stack_level > 0); ctx.call_stack_level--; auto &prof = ctx.profs[ctx.call_stack_level]; #if DEBUG printf("return level=%d, count=%zu, func=%p\n", TracerG.call_stack_level, array_count(&TracerG.prof_events), prof.fname.c_str()); #endif auto te = tracer_get_time_us(); zval event; array_init(&event); std::string name; if (prof.file_pos.length() > 0) { name = prof.fname + " (" + prof.file_pos + ")"; } else { name = prof.fname; } add_assoc_stringl_ex(&event, ZEND_STRL("name"), name.c_str(), name.length()); add_assoc_string_ex(&event, ZEND_STRL("ph"), "X"); add_assoc_string_ex(&event, ZEND_STRL("cat"), "FEE"); add_assoc_double_ex(&event, ZEND_STRL("ts"), prof.began_at); add_assoc_double_ex(&event, ZEND_STRL("dur"), te - prof.began_at); add_assoc_long_ex(&event, ZEND_STRL("pid"), tracer_get_pid()); add_assoc_long_ex(&event, ZEND_STRL("tid"), tid); add_next_index_zval(&TracerG.prof_events, &event); #if DEBUG printf("fn=%s, begun_at=%f, dr=%f, at=%s\n", prof.fname.c_str(), prof.began_at, te - prof.began_at, prof.file_pos.c_str()); #endif } static void blocking_detection_begin(zend_execute_data *execute_data) { auto ctx = (PHPContext *) swoole::Coroutine::get_current_task(); if (!ctx) { return; } auto span = new BlockingDetectionSpan; span->began_at = tracer_get_time_us(); span->switch_count = ctx->switch_count = 0; span->swap_callback = [](PHPContext *ctx) { ctx->switch_count++; }; ctx->on_resume = &span->swap_callback; ctx->on_yield = &span->swap_callback; execute_data->func->internal_function.reserved[blocking_detection_func_reserve_index] = span; } static void blocking_detection_end(zend_execute_data *execute_data) { PHPContext *ctx = PHPCoroutine::get_context(); if (!ctx) { return; } ctx->on_resume = nullptr; ctx->on_yield = nullptr; auto fn = &execute_data->func->internal_function; auto span = (BlockingDetectionSpan *) fn->reserved[blocking_detection_func_reserve_index]; if (!span) { return; } fn->reserved[blocking_detection_func_reserve_index] = nullptr; auto now = tracer_get_time_us(); auto duration = now - span->began_at; if (span->switch_count == ctx->switch_count && duration > SWOOLE_G(blocking_threshold)) { auto duration_str = tracer_format_number(duration); auto backtrace_str = tracer_get_backtrace(); const char *scope = nullptr; if (execute_data->func->common.scope) { scope = ZSTR_VAL(execute_data->func->common.scope->name); } sw_printf(" >>> [Detected blocking I/O in Coroutine#%ld, internal function `%s%s%s()` blocked for %s us]\n%s", PHPCoroutine::get_cid(), scope ? scope : "", scope ? "::" : "", fn->function_name->val, duration_str->val, backtrace_str->val); zend_string_release(duration_str); zend_string_release(backtrace_str); } swoole_event_defer( [](void *ptr) { auto span = (BlockingDetectionSpan *) ptr; delete span; }, span); } static bool tracer_observer_if_enable_profile(zend_execute_data *execute_data) { return SWOOLE_G(profile) && TracerG.profiling && execute_data->func->type != ZEND_INTERNAL_FUNCTION; } static bool tracer_observer_if_enable_blocking_detection(zend_execute_data *execute_data) { return SWOOLE_G(blocking_detection) && execute_data->func->type == ZEND_INTERNAL_FUNCTION; } static void tracer_observer_begin(zend_execute_data *execute_data) { if (tracer_observer_if_enable_profile(execute_data)) { profiling_begin(NULL, execute_data); } if (tracer_observer_if_enable_blocking_detection(execute_data)) { blocking_detection_begin(execute_data); } } static void tracer_observer_end(zend_execute_data *execute_data, zval *return_value) { if (tracer_observer_if_enable_profile(execute_data)) { profiling_end(); } if (tracer_observer_if_enable_blocking_detection(execute_data)) { blocking_detection_end(execute_data); } } static zend_observer_fcall_handlers tracer_observer(zend_execute_data *execute_data) { zend_observer_fcall_handlers empty_handlers = {nullptr, nullptr}; if (!execute_data->func || !execute_data->func->common.function_name) { return empty_handlers; } if (!tracer_observer_if_enable_profile(execute_data) && !tracer_observer_if_enable_blocking_detection(execute_data)) { return empty_handlers; } return {tracer_observer_begin, tracer_observer_end}; } PHP_FUNCTION(swoole_tracer_leak_detect) { if (!SWOOLE_G(leak_detection)) { return; } zend_long threshold = 64; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(threshold) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); std::unordered_map stats; unhook_emalloc(); for (auto &point : TracerG.points) { auto iter = stats.find(point.second.at); if (iter == stats.end()) { stats[point.second.at] = { point.second.size, point.second.count, }; } else { iter->second.count += point.second.count; iter->second.total_bytes += point.second.size; } } for (auto &stat : stats) { if (stat.second.count >= (size_t) threshold) { php_printf("[Round#%lu] leak %s bytes, alloc %lu times at %s\n", TracerG.loop, format_bytes(stat.second.total_bytes).c_str(), stat.second.count, stat.first.c_str()); auto bt_iter = TracerG.backtraces.find(stat.first); if (bt_iter != TracerG.backtraces.end()) { zend_string *str = zend_trace_to_string(Z_ARR(bt_iter->second), false); ZEND_WRITE(ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); } tracer_leak_clear_stat(stat.first); php_printf("\n"); } } TracerG.threshold = threshold; TracerG.loop++; hook_emalloc(); } PHP_FUNCTION(swoole_tracer_prof_begin) { if (!SWOOLE_G(profile) || TracerG.profiling) { RETURN_FALSE; } array_init(&TracerG.prof_events); TracerG.profiling = true; zval *options = NULL; /* optional array arg: for future use */ ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_ARRAY(options) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (options) { zval *pzval = zend_hash_str_find(Z_ARRVAL_P(options), ZEND_STRL("root_path")); if (pzval) { auto tmp = zval_get_string(pzval); TracerG.prof_root_path = zstr_to_std_string(tmp); if (TracerG.prof_root_path.at(TracerG.prof_root_path.length() - 1) != '/') { TracerG.prof_root_path.append("/"); } zend_string_release(tmp); } } TracerG.pid = getpid(); RETURN_TRUE; } PHP_FUNCTION(swoole_tracer_prof_end) { if (!SWOOLE_G(profile) || !TracerG.profiling) { RETURN_FALSE; } zend_string *file; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_STR(file) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval json; array_init(&json); zend::array_set(&json, ZEND_STRL("traceEvents"), &TracerG.prof_events); zval metadata; array_init(&metadata); zend::array_set(&metadata, ZEND_STRL("version"), "0.17.1"); zend::array_set(&metadata, ZEND_STRL("overflow"), false); zend::array_set(&json, ZEND_STRL("viztracer_metadata"), &metadata); smart_str buf = {}; if (php_json_encode(&buf, &json, 0) == FAILURE) { _fail: zval_ptr_dtor(&TracerG.prof_events); RETURN_FALSE; } std::ofstream outputFile(ZSTR_VAL(file), std::ios::binary); if (!outputFile.is_open()) { goto _fail; } outputFile.write(buf.s->val, buf.s->len); outputFile.close(); zval_ptr_dtor(&metadata); zval_ptr_dtor(&json); smart_str_free(&buf); profiling_clear(); RETURN_TRUE; } void php_swoole_tracer_minit(int module_number) { if (SWOOLE_G(blocking_detection) || SWOOLE_G(profile)) { zend_observer_fcall_register(tracer_observer); SWOOLE_G(enable_fiber_mock) = true; } } void php_swoole_tracer_rinit() { if (SWOOLE_G(leak_detection)) { hook_emalloc(); } } void php_swoole_tracer_rshutdown() { if (SWOOLE_G(leak_detection)) { unhook_emalloc(); } for (auto &iter : TracerG.backtraces) { zval_ptr_dtor(&iter.second); } profiling_clear(); } ================================================ FILE: ext-src/swoole_websocket_server.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole_http_server.h" #include "php_swoole_websocket.h" SW_EXTERN_C_BEGIN #include "ext/standard/sha1.h" #include "stubs/php_swoole_websocket_arginfo.h" SW_EXTERN_C_END #include "swoole_base64.h" using swoole::Connection; using swoole::ListenPort; using swoole::make_string; using swoole::RecvData; using swoole::Server; using swoole::SessionId; using swoole::String; using swoole::WebSocketSettings; using swoole::websocket::Frame; using swoole::websocket::FrameObject; using HttpContext = swoole::http::Context; namespace WebSocket = swoole::websocket; zend_class_entry *swoole_websocket_server_ce; static zend_object_handlers swoole_websocket_server_handlers; zend_class_entry *swoole_websocket_frame_ce; static zend_object_handlers swoole_websocket_frame_handlers; static zend_class_entry *swoole_websocket_closeframe_ce; static zend_object_handlers swoole_websocket_closeframe_handlers; SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_websocket_server, push); static PHP_METHOD(swoole_websocket_server, isEstablished); static PHP_METHOD(swoole_websocket_server, pack); static PHP_METHOD(swoole_websocket_server, unpack); static PHP_METHOD(swoole_websocket_server, disconnect); static PHP_METHOD(swoole_websocket_server, ping); static PHP_METHOD(swoole_websocket_frame, __toString); SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_websocket_server_methods[] = { PHP_ME(swoole_websocket_server, push, arginfo_class_Swoole_WebSocket_Server_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, disconnect, arginfo_class_Swoole_WebSocket_Server_disconnect, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, ping, arginfo_class_Swoole_WebSocket_Server_ping, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, isEstablished, arginfo_class_Swoole_WebSocket_Server_isEstablished, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, pack, arginfo_class_Swoole_WebSocket_Server_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_websocket_server, unpack, arginfo_class_Swoole_WebSocket_Server_unpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; static constexpr zend_function_entry swoole_websocket_frame_methods[] = { PHP_ME(swoole_websocket_frame, __toString, arginfo_class_Swoole_WebSocket_Frame___toString, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, pack, arginfo_class_Swoole_WebSocket_Frame_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_websocket_server, unpack, arginfo_class_Swoole_WebSocket_Frame_unpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on void WebSocket::construct_frame(zval *zframe, zend_long opcode, zval *zpayload, uint8_t flags) { if (opcode == OPCODE_CLOSE) { const char *payload = Z_STRVAL_P(zpayload); size_t payload_length = Z_STRLEN_P(zpayload); object_init_ex(zframe, swoole_websocket_closeframe_ce); if (payload_length >= SW_WEBSOCKET_CLOSE_CODE_LEN) { // WebSocket Close code zend_update_property_long(swoole_websocket_closeframe_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("code"), (payload[0] << 8) ^ (payload[1] & 0xFF)); if (payload_length > SW_WEBSOCKET_CLOSE_CODE_LEN) { // WebSocket Close reason message zend_update_property_stringl(swoole_websocket_closeframe_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("reason"), payload + SW_WEBSOCKET_CLOSE_CODE_LEN, payload_length - SW_WEBSOCKET_CLOSE_CODE_LEN); } } } else { object_init_ex(zframe, swoole_websocket_frame_ce); zend_update_property(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("data"), zpayload); } if (flags & FLAG_RSV1) { flags |= FLAG_COMPRESS; } zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("opcode"), opcode); zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("flags"), flags); zend_update_property_bool(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("finish"), flags & FLAG_FIN); } bool FrameObject::uncompress(zval *zpayload, const char *data, size_t length) { #ifndef SW_HAVE_ZLIB swoole_warning("The compressed websocket data frame is received, the `zlib` supports is required"); return false; #else String zlib_buffer(length + SW_WEBSOCKET_DEFAULT_PAYLOAD_SIZE, sw_zend_string_allocator()); if (sw_likely(WebSocket::message_uncompress(&zlib_buffer, data, length))) { zend::assign_zend_string_by_val(zpayload, zlib_buffer.str, zlib_buffer.length); zlib_buffer.release(); return true; } else { return false; } #endif } bool FrameObject::pack(String *buffer) { const char *ptr = nullptr; size_t len = 0; if (sw_unlikely(opcode > SW_WEBSOCKET_OPCODE_MAX)) { php_swoole_fatal_error(E_WARNING, "the maximum value of opcode is %d", SW_WEBSOCKET_OPCODE_MAX); return false; } zend::String str_zdata; if (data && !ZVAL_IS_NULL(data)) { str_zdata = data; ptr = str_zdata.val(); len = str_zdata.len(); } #ifndef SW_HAVE_ZLIB swoole_warning("Unable to compress websocket data frame, the `zlib` supports is required"); return false; #else bool need_compress = ((flags & WebSocket::FLAG_COMPRESS) && len > 0); if (opcode == WebSocket::OPCODE_CLOSE || opcode == WebSocket::OPCODE_PING || opcode == WebSocket::OPCODE_PONG) { sw_unset_bit(flags, WebSocket::FLAG_COMPRESS | WebSocket::FLAG_RSV1); sw_set_bit(flags, WebSocket::FLAG_FIN); need_compress = false; } else if (opcode == WebSocket::OPCODE_CONTINUATION || !(flags & WebSocket::FLAG_FIN)) { // Continuous frames and WebSocket message frames without the FLAG_FIN flag do not require compression. need_compress = false; } if (need_compress) { String *zlib_buffer = sw_tg_buffer(); zlib_buffer->clear(); if (WebSocket::message_compress(zlib_buffer, ptr, len, Z_DEFAULT_COMPRESSION)) { ptr = zlib_buffer->str; len = zlib_buffer->length; sw_set_bit(flags, WebSocket::FLAG_RSV1); } else { sw_unset_bit(flags, WebSocket::FLAG_RSV1); } } #endif buffer->clear(); if (UNEXPECTED(opcode == WebSocket::OPCODE_CLOSE)) { return WebSocket::pack_close_frame(buffer, code, ptr, len, flags); } else { return WebSocket::encode(buffer, ptr, len, opcode, flags); } } FrameObject::FrameObject(zval *zdata, zend_long _opcode, zend_long _flags, zend_long _code) { if (Z_TYPE_P(zdata) == IS_OBJECT && instanceof_function(Z_OBJCE_P(zdata), swoole_websocket_frame_ce)) { zval *ztmp = nullptr; if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_OPCODE), 1))) { opcode = zval_get_long(ztmp); } else { opcode = OPCODE_TEXT; } if (opcode == OPCODE_CLOSE) { if ((ztmp = sw_zend_read_property_not_null_ex( swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_CODE), 1))) { code = zval_get_long(ztmp); } else { code = CLOSE_NORMAL; } data = sw_zend_read_property_not_null_ex( swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_REASON), 1); } else { data = sw_zend_read_property_not_null_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 1); } if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_FLAGS), 1))) { flags = zval_get_long(ztmp) & FLAGS_ALL; } else { flags = 0; } if ((ztmp = sw_zend_read_property_not_null_ex( swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_FINISH), 1))) { if (zval_is_true(ztmp)) { sw_set_bit(flags, WebSocket::FLAG_FIN); } else { sw_unset_bit(flags, WebSocket::FLAG_FIN); } } } else { opcode = _opcode; flags = _flags & FLAGS_ALL; code = _code; data = zdata; } } void swoole_websocket_onBeforeHandshakeResponse(Server *serv, int server_fd, HttpContext *ctx) { auto cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onBeforeHandshakeResponse); if (cb) { zval args[3]; args[0] = *php_swoole_server_zval_ptr(serv); args[1] = *ctx->request.zobject; args[2] = *ctx->response.zobject; if (UNEXPECTED(!zend::function::call(cb, 3, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error( E_WARNING, "%s->onBeforeHandshakeResponse handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(ctx->fd, false); } } } void swoole_websocket_onOpen(Server *serv, const HttpContext *ctx) { Connection *conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd); return; } auto cb = php_swoole_server_get_callback(serv, conn->server_fd, SW_SERVER_CB_onOpen); if (cb) { zval args[2]; args[0] = *php_swoole_server_zval_ptr(serv); args[1] = *ctx->request.zobject; if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onOpen handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(ctx->fd, false); } } } /** * default onRequest callback */ void swoole_websocket_onRequest(HttpContext *ctx) { const char *bad_request = "HTTP/1.1 400 Bad Request\r\n" "Connection: close\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Cache-Control: must-revalidate,no-cache,no-store\r\n" "Content-Length: 83\r\n" "Server: " SW_HTTP_SERVER_SOFTWARE "\r\n\r\n" "

HTTP 400 Bad Request


Powered by Swoole"; ctx->send(ctx, bad_request, strlen(bad_request)); ctx->end_ = 1; ctx->close(ctx); } void php_swoole_sha1(const char *str, int _len, unsigned char *digest) { PHP_SHA1_CTX context; PHP_SHA1Init(&context); PHP_SHA1Update(&context, (unsigned char *) str, _len); PHP_SHA1Final(digest, &context); } bool swoole_websocket_handshake(HttpContext *ctx) { char sec_buf[128]; zval *header = ctx->request.zheader; HashTable *ht = Z_ARRVAL_P(header); zval *pData; zval retval; if (!((pData = zend_hash_str_find(ht, ZEND_STRL("sec-websocket-key"))))) { _bad_request: ctx->response.status = SW_HTTP_BAD_REQUEST; ctx->end(nullptr, &retval); return false; } zend::String str_pData(pData); if (str_pData.len() != BASE64_ENCODE_OUT_SIZE(SW_WEBSOCKET_SEC_KEY_LEN)) { goto _bad_request; } char sha1_str[20]; // sec_websocket_accept memcpy(sec_buf, str_pData.val(), str_pData.len()); memcpy(sec_buf + str_pData.len(), SW_WEBSOCKET_GUID, sizeof(SW_WEBSOCKET_GUID) - 1); // sha1 sec_websocket_accept php_swoole_sha1(sec_buf, str_pData.len() + sizeof(SW_WEBSOCKET_GUID) - 1, (unsigned char *) sha1_str); // base64 encode int sec_len = swoole::base64_encode((unsigned char *) sha1_str, sizeof(sha1_str), sec_buf); ctx->set_header(ZEND_STRL("Upgrade"), ZEND_STRL("websocket"), false); ctx->set_header(ZEND_STRL("Connection"), ZEND_STRL("Upgrade"), false); ctx->set_header(ZEND_STRL("Sec-WebSocket-Accept"), sec_buf, sec_len, false); ctx->set_header(ZEND_STRL("Sec-WebSocket-Version"), ZEND_STRL(SW_WEBSOCKET_VERSION), false); Server *serv = nullptr; Connection *conn = nullptr; if (!ctx->is_co_socket()) { serv = ctx->get_async_server(); conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd); return false; } } if (conn) { conn->websocket_status = WebSocket::STATUS_ACTIVE; ListenPort *port = serv->get_port_by_server_fd(conn->server_fd); if (port && !port->websocket_settings.protocol.empty()) { ctx->set_header(ZEND_STRL("Sec-WebSocket-Protocol"), port->websocket_settings.protocol, false); } swoole_websocket_onBeforeHandshakeResponse(serv, conn->server_fd, ctx); } else { auto sock = ctx->get_co_socket(); sock->open_length_check = true; sock->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN; sock->protocol.package_length_offset = 0; sock->protocol.package_body_offset = 0; sock->protocol.get_package_length = WebSocket::get_package_length; } ctx->response.status = SW_HTTP_SWITCHING_PROTOCOLS; ctx->upgrade = 1; ctx->end(nullptr, &retval); return Z_TYPE(retval) == IS_TRUE; } #ifdef SW_HAVE_ZLIB bool WebSocket::message_uncompress(String *buffer, const char *in, size_t in_len) { z_stream zstream; int status; bool ret = false; memset(&zstream, 0, sizeof(zstream)); zstream.zalloc = php_zlib_alloc; zstream.zfree = php_zlib_free; // gzip_stream.total_out = 0; status = inflateInit2(&zstream, SW_ZLIB_ENCODING_RAW); if (status != Z_OK) { swoole_warning("inflateInit2() failed by %s", zError(status)); return false; } zstream.next_in = (Bytef *) in; zstream.avail_in = in_len; zstream.total_in = 0; while (true) { zstream.avail_out = buffer->size - buffer->length; zstream.next_out = (Bytef *) (buffer->str + buffer->length); status = inflate(&zstream, Z_SYNC_FLUSH); if (status >= 0) { buffer->length = zstream.total_out; } if (status == Z_STREAM_END || (status == Z_OK && zstream.avail_in == 0)) { ret = true; break; } if (status != Z_OK) { break; } if (buffer->length + (SW_BUFFER_SIZE_STD / 2) >= buffer->size) { buffer->extend(); } } inflateEnd(&zstream); if (!ret) { php_swoole_fatal_error(E_WARNING, "inflate() failed, Error: %s[%d]", zError(status), status); return false; } return true; } bool WebSocket::message_compress(String *buffer, const char *data, size_t length, int level) { // ==== ZLIB ==== if (level == Z_NO_COMPRESSION) { level = Z_DEFAULT_COMPRESSION; } else if (level > Z_BEST_COMPRESSION) { level = Z_BEST_COMPRESSION; } z_stream zstream = {}; int status; zstream.zalloc = php_zlib_alloc; zstream.zfree = php_zlib_free; status = deflateInit2(&zstream, level, Z_DEFLATED, SW_ZLIB_ENCODING_RAW, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (status != Z_OK) { php_swoole_fatal_error(E_WARNING, "deflateInit2() failed, Error: [%d]", status); return false; } zstream.next_in = (Bytef *) data; zstream.avail_in = length; zstream.next_out = (Bytef *) buffer->str; size_t max_length = deflateBound(&zstream, length); if (max_length > buffer->size) { buffer->extend(max_length); } size_t bytes_written = 0; uchar in_sync_flush; int result; do { size_t write_remaining; if (zstream.avail_out == 0) { size_t write_position; zstream.avail_out = max_length; write_position = buffer->length; buffer->length = max_length; zstream.next_out = (Bytef *) buffer->str + write_position; /* Use a fixed value for buffer increments */ max_length = 4096; } write_remaining = buffer->length - bytes_written; in_sync_flush = zstream.avail_in == 0; result = deflate(&zstream, in_sync_flush ? Z_SYNC_FLUSH : Z_NO_FLUSH); bytes_written += write_remaining - zstream.avail_out; } while (result == Z_OK); deflateEnd(&zstream); if (result != Z_BUF_ERROR || bytes_written < 4) { php_swoole_fatal_error(E_WARNING, "Failed to compress outgoing frame"); return false; } if (status != Z_OK) { php_swoole_fatal_error(E_WARNING, "deflate() failed, Error: [%d]", status); return false; } buffer->length = bytes_written - 4; return true; } #endif int swoole_websocket_onMessage(Server *serv, RecvData *req) { SessionId fd = req->info.fd; uchar flags = 0; uchar opcode = 0; auto port = serv->get_port_by_session_id(fd); if (!port) { return SW_ERR; } zval zdata; php_swoole_get_recv_data(serv, &zdata, req); WebSocket::parse_ext_flags(req->info.ext_flags, &opcode, &flags); if ((opcode == WebSocket::OPCODE_CLOSE && !port->websocket_settings.open_close_frame) || (opcode == WebSocket::OPCODE_PING && !port->websocket_settings.open_ping_frame) || (opcode == WebSocket::OPCODE_PONG && !port->websocket_settings.open_pong_frame)) { if (opcode == WebSocket::OPCODE_PING) { String frame(SW_WEBSOCKET_FRAME_HEADER_SIZE + req->info.len, sw_php_allocator()); WebSocket::encode(&frame, req->data, req->info.len, WebSocket::OPCODE_PONG, WebSocket::FLAG_FIN); serv->send(fd, frame.str, frame.length); } zval_ptr_dtor(&zdata); return SW_OK; } // RFC 7692: uncompress websocket data // See https://datatracker.ietf.org/doc/html/rfc7692 if (flags & WebSocket::FLAG_RSV1) { zval zpayload; auto rs = FrameObject::uncompress(&zpayload, Z_STRVAL(zdata), Z_STRLEN(zdata)); zval_ptr_dtor(&zdata); if (!rs) { return SW_OK; } zdata = zpayload; } auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onMessage); zval args[2]; args[0] = *php_swoole_server_zval_ptr(serv); WebSocket::construct_frame(&args[1], opcode, &zdata, flags); zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(&args[1]), ZEND_STRL("fd"), fd); if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onMessage handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(fd, false); } zval_ptr_dtor(&zdata); zval_ptr_dtor(&args[1]); return SW_OK; } int swoole_websocket_onHandshake(Server *serv, ListenPort *port, HttpContext *ctx) { SessionId fd = ctx->fd; bool success = swoole_websocket_handshake(ctx); if (success) { swoole_websocket_onOpen(serv, ctx); } else { serv->close(fd, true); } return SW_OK; } void php_swoole_websocket_server_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX(swoole_websocket_server, "Swoole\\WebSocket\\Server", nullptr, swoole_websocket_server_methods, swoole_http_server); #ifndef SW_THREAD SW_SET_CLASS_NOT_SERIALIZABLE(swoole_websocket_server); #endif SW_SET_CLASS_CLONEABLE(swoole_websocket_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_websocket_server, sw_zend_class_unset_property_deny); SW_INIT_CLASS_ENTRY(swoole_websocket_frame, R"(Swoole\WebSocket\Frame)", nullptr, swoole_websocket_frame_methods); zend_class_implements(swoole_websocket_frame_ce, 1, zend_ce_stringable); zend_declare_property_long(swoole_websocket_frame_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_websocket_frame_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_websocket_frame_ce, ZEND_STRL("opcode"), WebSocket::OPCODE_TEXT, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_websocket_frame_ce, ZEND_STRL("flags"), WebSocket::FLAG_FIN, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_websocket_frame_ce, ZEND_STRL("finish"), ZEND_ACC_PUBLIC); SW_INIT_CLASS_ENTRY_EX( swoole_websocket_closeframe, "Swoole\\WebSocket\\CloseFrame", nullptr, nullptr, swoole_websocket_frame); zend_declare_property_long( swoole_websocket_closeframe_ce, ZEND_STRL("opcode"), WebSocket::OPCODE_CLOSE, ZEND_ACC_PUBLIC); zend_declare_property_long( swoole_websocket_closeframe_ce, ZEND_STRL("code"), WebSocket::CLOSE_NORMAL, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_websocket_closeframe_ce, ZEND_STRL("reason"), "", ZEND_ACC_PUBLIC); /* {{{ swoole namespace */ // status SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_STATUS_CONNECTION", WebSocket::STATUS_CONNECTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_STATUS_HANDSHAKE", WebSocket::STATUS_HANDSHAKE); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_STATUS_ACTIVE", WebSocket::STATUS_ACTIVE); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_STATUS_CLOSING", WebSocket::STATUS_CLOSING); // all opcodes SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_CONTINUATION", WebSocket::OPCODE_CONTINUATION); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_TEXT", WebSocket::OPCODE_TEXT); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_BINARY", WebSocket::OPCODE_BINARY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_CLOSE", WebSocket::OPCODE_CLOSE); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_PING", WebSocket::OPCODE_PING); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_OPCODE_PONG", WebSocket::OPCODE_PONG); // flags SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_FIN", WebSocket::FLAG_FIN); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_RSV1", WebSocket::FLAG_RSV1); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_RSV2", WebSocket::FLAG_RSV2); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_RSV3", WebSocket::FLAG_RSV3); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_MASK", WebSocket::FLAG_MASK); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_FLAG_COMPRESS", WebSocket::FLAG_COMPRESS); // close error SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_NORMAL", WebSocket::CLOSE_NORMAL); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_GOING_AWAY", WebSocket::CLOSE_GOING_AWAY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_PROTOCOL_ERROR", WebSocket::CLOSE_PROTOCOL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_DATA_ERROR", WebSocket::CLOSE_DATA_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_STATUS_ERROR", WebSocket::CLOSE_STATUS_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_ABNORMAL", WebSocket::CLOSE_ABNORMAL); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_MESSAGE_ERROR", WebSocket::CLOSE_MESSAGE_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_POLICY_ERROR", WebSocket::CLOSE_POLICY_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_MESSAGE_TOO_BIG", WebSocket::CLOSE_MESSAGE_TOO_BIG); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_EXTENSION_MISSING", WebSocket::CLOSE_EXTENSION_MISSING); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_SERVER_ERROR", WebSocket::CLOSE_SERVER_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_CLOSE_SERVICE_RESTART", WebSocket::CLOSE_SERVICE_RESTART); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_TRY_AGAIN_LATER", WebSocket::CLOSE_TRY_AGAIN_LATER); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_BAD_GATEWAY", WebSocket::CLOSE_BAD_GATEWAY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_TLS", WebSocket::CLOSE_TLS); /* swoole namespace }}} */ /* BC */ // status SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_CONNECTION", WebSocket::STATUS_CONNECTION); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_HANDSHAKE", WebSocket::STATUS_HANDSHAKE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_FRAME", WebSocket::STATUS_ACTIVE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_ACTIVE", WebSocket::STATUS_ACTIVE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_CLOSING", WebSocket::STATUS_CLOSING); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_HANDSHAKE_FAILED", WebSocket::STATUS_HANDSHAKE_FAILED); // all opcodes SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_CONTINUATION", WebSocket::OPCODE_CONTINUATION); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_TEXT", WebSocket::OPCODE_TEXT); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_BINARY", WebSocket::OPCODE_BINARY); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_CLOSE", WebSocket::OPCODE_CLOSE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_PING", WebSocket::OPCODE_PING); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_PONG", WebSocket::OPCODE_PONG); // close error SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_NORMAL", WebSocket::CLOSE_NORMAL); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_GOING_AWAY", WebSocket::CLOSE_GOING_AWAY); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_PROTOCOL_ERROR", WebSocket::CLOSE_PROTOCOL_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_DATA_ERROR", WebSocket::CLOSE_DATA_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_STATUS_ERROR", WebSocket::CLOSE_STATUS_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_ABNORMAL", WebSocket::CLOSE_ABNORMAL); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_MESSAGE_ERROR", WebSocket::CLOSE_MESSAGE_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_POLICY_ERROR", WebSocket::CLOSE_POLICY_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_MESSAGE_TOO_BIG", WebSocket::CLOSE_MESSAGE_TOO_BIG); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_EXTENSION_MISSING", WebSocket::CLOSE_EXTENSION_MISSING); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_SERVER_ERROR", WebSocket::CLOSE_SERVER_ERROR); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_CLOSE_SERVICE_RESTART", WebSocket::CLOSE_SERVICE_RESTART); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_TRY_AGAIN_LATER", WebSocket::CLOSE_TRY_AGAIN_LATER); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_BAD_GATEWAY", WebSocket::CLOSE_BAD_GATEWAY); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_TLS", WebSocket::CLOSE_TLS); } void php_swoole_server_set_websocket_option(ListenPort *port, zend_array *vht) { WebSocket::apply_setting(port->websocket_settings, vht, true); } void WebSocket::apply_setting(WebSocketSettings &settings, zend_array *vht, bool in_server) { zval *ztmp; if (php_swoole_array_get_value(vht, "websocket_subprotocol", ztmp)) { settings.protocol = zend::String(ztmp).to_std_string(); } if (php_swoole_array_get_value(vht, "websocket_mask", ztmp)) { settings.mask = zval_is_true(ztmp); } else { settings.mask = !in_server; } #ifdef SW_HAVE_ZLIB if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) { settings.compression = zval_is_true(ztmp); } #endif if (php_swoole_array_get_value(vht, "open_websocket_close_frame", ztmp)) { settings.open_close_frame = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "open_websocket_ping_frame", ztmp)) { settings.open_ping_frame = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "open_websocket_pong_frame", ztmp)) { settings.open_pong_frame = zval_is_true(ztmp); } settings.in_server = in_server; } static sw_inline bool swoole_websocket_server_push(Server *serv, SessionId fd, String *buffer) { if (sw_unlikely(fd <= 0)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is invalid", fd); return false; } Connection *conn = serv->get_connection_by_session_id(fd); if (!conn || conn->websocket_status < WebSocket::STATUS_HANDSHAKE) { swoole_set_last_error(SW_ERROR_WEBSOCKET_UNCONNECTED); php_swoole_fatal_error( E_WARNING, "the connected client of connection[%ld] is not a websocket client or closed", fd); return false; } bool rv = serv->send(fd, buffer->str, buffer->length); if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { auto sdata = zend_string_init(buffer->str, buffer->length, false); rv = php_swoole_server_send_yield(serv, fd, sdata); zend_string_release(sdata); } return rv; } static sw_inline bool swoole_websocket_server_close(Server *serv, SessionId fd, String *buffer, bool real_close) { bool ret = swoole_websocket_server_push(serv, fd, buffer); if (!ret || !real_close) { return ret; } Connection *conn = serv->get_connection_by_session_id(fd); if (conn) { // Change status immediately to avoid double close conn->websocket_status = WebSocket::STATUS_CLOSING; // Server close connection immediately return serv->close(fd, false); } else { return false; } } static inline void swoole_websocket_server_pack(zval *zdata, zend_long opcode, zend_long flags, zval *return_value) { FrameObject frame{zdata, opcode, flags}; String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); if (sw_unlikely(!frame.pack(&buffer))) { RETURN_EMPTY_STRING(); } auto packed_str = zend::fetch_zend_string_by_val(buffer.str); ZSTR_VAL(packed_str)[buffer.length] = '\0'; ZSTR_LEN(packed_str) = buffer.length; buffer.release(); RETURN_STR(packed_str); } static PHP_METHOD(swoole_websocket_server, disconnect) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd = 0; zend_long code = WebSocket::CLOSE_NORMAL; char *data = nullptr; size_t length = 0; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_LONG(fd) Z_PARAM_OPTIONAL Z_PARAM_LONG(code) Z_PARAM_STRING(data, length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + length + 2, sw_zend_string_allocator()); if (!WebSocket::pack_close_frame(&buffer, code, data, length, 0)) { RETURN_FALSE; } RETURN_BOOL(swoole_websocket_server_close(serv, fd, &buffer, 1)); } static PHP_METHOD(swoole_websocket_server, ping) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd = 0; zend_string *zpayload = zend_empty_string; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(fd) Z_PARAM_OPTIONAL Z_PARAM_STR(zpayload) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zval zdata = {}; ZVAL_STR(&zdata, zpayload); FrameObject frame{&zdata, WebSocket::OPCODE_PING, WebSocket::FLAG_FIN}; String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); if (sw_unlikely(!frame.pack(&buffer))) { swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); RETURN_FALSE; } RETURN_BOOL(swoole_websocket_server_push(serv, fd, &buffer)); } static PHP_METHOD(swoole_websocket_server, push) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long fd = 0; zval *zdata = nullptr; zend_long opcode = WebSocket::OPCODE_TEXT; zval *zflags = nullptr; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_LONG(fd) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(opcode) Z_PARAM_ZVAL_EX(zflags, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (zflags != nullptr) { flags = zval_get_long(zflags); } Connection *conn = serv->get_connection_verify(fd); if (sw_unlikely(!conn)) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); php_swoole_fatal_error(E_WARNING, "session#" ZEND_LONG_FMT " does not exists", fd); RETURN_FALSE; } FrameObject frame{zdata, opcode, flags}; if (conn->websocket_compression) { sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); } // WebSocket server must not set data mask sw_unset_bit(frame.flags, WebSocket::FLAG_MASK); String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); if (sw_unlikely(!frame.pack(&buffer))) { swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); RETURN_FALSE; } RETURN_BOOL(swoole_websocket_server_push(serv, fd, &buffer)); } static PHP_METHOD(swoole_websocket_server, pack) { zval *zdata; zend_long opcode = WebSocket::OPCODE_TEXT; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(opcode) Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_websocket_server_pack(zdata, opcode, flags, return_value); } static PHP_METHOD(swoole_websocket_frame, __toString) { swoole_websocket_server_pack(ZEND_THIS, 0, 0, return_value); } static PHP_METHOD(swoole_websocket_server, unpack) { char *data; size_t length; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(data, length) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); WebSocket::Frame frame; if (length < sizeof(frame.header)) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_FALSE; } if (!WebSocket::decode(&frame, data, length)) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_FALSE; } zval zpayload{}; uint8_t flags = frame.get_flags(); if (frame.compressed()) { if (sw_unlikely(!FrameObject::uncompress(&zpayload, frame.payload, frame.payload_length))) { swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_FALSE; } } else { ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); } WebSocket::construct_frame(return_value, frame.header.OPCODE, &zpayload, flags); zval_ptr_dtor(&zpayload); } static PHP_METHOD(swoole_websocket_server, isEstablished) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); RETURN_FALSE; } zend_long session_id; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(session_id) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); // not isEstablished if (!conn || conn->closed || conn->websocket_status < WebSocket::STATUS_ACTIVE) { RETURN_FALSE; } else { RETURN_TRUE; } } ================================================ FILE: gdbinit ================================================ define timer_list if SwooleTG.timer printf "current timer number: %d, round: %d\n", SwooleTG.timer.num,SwooleTG.timer->round set $running = 1 set $i = 1 while $running if $i < SwooleTG.timer->heap->num set $tmp = SwooleTG.timer->heap->nodes[$i] set $node = (swTimer_node *)$tmp->data if $node printf "\t timer[%d] exec_msec:%ld round:%ld\n", $node->id, $node->exec_msec, $node->round end else set $running = 0 end set $i = $i + 1 end else printf "no timer\n" end end define reactor_info if SwooleTG.reactor printf "\t reactor id: %d\n",SwooleTG.reactor->id printf "\t running: %d\n", SwooleTG.reactor->running printf "\t event_num: %d\n", SwooleTG.reactor->event_num printf "\t aio_task_num: %d\n", SwooleTG.aio_task_num printf "\t max_event_num: %d\n", SwooleTG.reactor->max_event_num printf "\t check_timer: %d\n", SwooleTG.reactor->check_timer printf "\t timeout_msec: %d\n", SwooleTG.reactor->timeout_msec end end define sw_hash_map_list set $hmap = $arg0 if $hmap if $hmap->root->hh.tbl->num_items == 0 echo "no content\n" else set $running = 1 set $it = $hmap->iterator if $it == 0 set $it = $hmap->root end while $running set $tmp = (swHashMap_node *)$it->hh.next if $tmp printf "key_int[%d] key_str:%s data:%p\n", $tmp->key_int, $tmp->key_str, $tmp->data set $it = $tmp else set $running = 0 end end end end end define co_list call swoole_coroutine_iterator_reset() set $running = 1 while $running set $co = swoole_coroutine_iterator_each() if $co printf "coroutine %ld ", $co->cid if $co->state == 0 printlnc $GREEN "SW_CORO_INIT" end if $co->state == 1 printlnc $YELLOW "SW_CORO_WAITING" end if $co->state == 2 printlnc $GREEN "SW_CORO_RUNNING" end if $co->state == 3 printlnc $CYAN "SW_CORO_END" end else set $running = 0 end end end define co_bt if swoole_coroutine_count() == 0 printf "no coroutine is running\n" end ____sw_executor_globals if $argc > 0 set $cid = (int) $arg0 else if 'swoole::Coroutine::current' set $cid = (int) 'swoole::Coroutine::current'->cid else set $cid = -1 end end printf "coroutine cid: [%d]\n", $cid if $argc > 0 __co_bt $cid else sw_dump_bt php_swoole_get_executor_globals()->current_execute_data end end document co_bt dump current coroutine or the cid backtrace. useage: co_bt [cid] end define __co_bt set $cid = (int)$arg0 set $co = swoole_coroutine_get($cid) if $co set $task = ('swoole::PHPContext' *) $co->get_task() if $task sw_dump_bt $task->execute_data end else printf "coroutines %d not found\n", $cid end end define co_status printf "\t c_stack_size: %d\n", 'swoole::Coroutine::stack_size' printf "\t active: %d\n", 'swoole::PHPCoroutine::active' printf "\t coro_num: %d\n", swoole_coroutine_count() printf "\t peak_coro_num: %d\n", 'swoole::Coroutine::peak_num' printf "\t config: " print 'swoole::PHPCoroutine::config' end define ____sw_executor_globals set $eg = php_swoole_get_executor_globals() end define ____sw_print_str set $tmp = 0 set $str = $arg0 if $argc > 2 set $maxlen = $arg2 else set $maxlen = 256 end printf "\"" while $tmp < $arg1 && $tmp < $maxlen if $str[$tmp] > 31 && $str[$tmp] < 127 printf "%c", $str[$tmp] else printf "\\%o", $str[$tmp] end set $tmp = $tmp + 1 end if $tmp != $arg1 printf "..." end printf "\"" end define sw_dump_bt set $ex = $arg0 while $ex printf "[%p] ", $ex set $func = $ex->func if $func if $ex->This->value.obj if $func->common.scope printf "%s->", $func->common.scope->name->val else printf "%s->", $ex->This->value.obj->ce.name->val end else if $func->common.scope printf "%s::", $func->common.scope->name->val end end if $func->common.function_name printf "%s(", $func->common.function_name->val else printf "(main" end set $callFrameSize = (sizeof(zend_execute_data) + sizeof(zval) - 1) / sizeof(zval) set $count = $ex->This.u2.num_args set $arg = 0 while $arg < $count if $arg > 0 printf ", " end set $zvalue = (zval *) $ex + $callFrameSize + $arg set $type = $zvalue->u1.v.type if $type == 1 printf "NULL" end if $type == 2 printf "false" end if $type == 3 printf "true" end if $type == 4 printf "%ld", $zvalue->value.lval end if $type == 5 printf "%f", $zvalue->value.dval end if $type == 6 ____sw_print_str $zvalue->value.str->val $zvalue->value.str->len end if $type == 7 printf "array(%d)[%p]", $zvalue->value.arr->nNumOfElements, $zvalue end if $type == 8 printf "object[%p]", $zvalue end if $type == 9 printf "resource(#%d)", $zvalue->value.lval end if $type == 10 printf "reference" end if $type > 10 printf "unknown type %d", $type end set $arg = $arg + 1 end printf ") " else printf "??? " end if $func != 0 if $func->type == 2 printf "%s:%d ", $func->op_array.filename->val, $ex->opline->lineno else printf "[internal function]" end end set $ex = $ex->prev_execute_data printf "\n" end end # ======== color ======== set $BLACK = 0 set $RED = 1 set $GREEN = 2 set $YELLOW = 3 set $BLUE = 4 set $MAGENTA = 5 set $CYAN = 6 set $WHITE = 7 define color if $argc == 0 set $arg = 0 else set $arg = $arg0 + 30 end printf "%c[%dm", 27, $arg end # ======== print ======== define printlnc color $arg0 printf "%s\n", $arg1 color end ================================================ FILE: include/helper/kqueue.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef KQUEUE_IDE_HELPER_H_ #define KQUEUE_IDE_HELPER_H_ #ifdef USE_KQUEUE_IDE_HELPER #include #define EVFILT_READ (-1) #define EVFILT_WRITE (-2) #define EVFILT_SIGNAL (-6) /* kevent system call flags */ #define KEVENT_FLAG_NONE 0x000000 /* no flag value */ #define KEVENT_FLAG_IMMEDIATE 0x000001 /* immediate timeout */ #define KEVENT_FLAG_ERROR_EVENTS 0x000002 /* output events only include change errors */ /* actions */ #define EV_ADD 0x0001 /* add event to kq (implies enable) */ #define EV_DELETE 0x0002 /* delete event from kq */ #define EV_ENABLE 0x0004 /* enable event */ #define EV_DISABLE 0x0008 /* disable event (not reported) */ /* flags */ #define EV_ONESHOT 0x0010 /* only report one occurrence */ #define EV_CLEAR 0x0020 /* clear event state after reporting */ #define EV_RECEIPT 0x0040 /* force immediate event output */ /* ... with or without EV_ERROR */ /* ... use KEVENT_FLAG_ERROR_EVENTS */ /* on syscalls supporting flags */ #define EV_DISPATCH 0x0080 /* disable event after reporting */ #define EV_UDATA_SPECIFIC 0x0100 /* unique kevent per udata value */ #define EV_DISPATCH2 (EV_DISPATCH | EV_UDATA_SPECIFIC) /* ... in combination with EV_DELETE */ /* will defer delete until udata-specific */ /* event enabled. EINPROGRESS will be */ /* returned to indicate the deferral */ #define EV_VANISHED 0x0200 /* report that source has vanished */ /* ... only valid with EV_DISPATCH2 */ #define EV_SYSFLAGS 0xF000 /* reserved by system */ #define EV_FLAG0 0x1000 /* filter-specific flag */ #define EV_FLAG1 0x2000 /* filter-specific flag */ /* returned values */ #define EV_EOF 0x8000 /* EOF detected */ #define EV_ERROR 0x4000 /* error, data contains errno */ /* * Filter specific flags for EVFILT_READ * * The default behavior for EVFILT_READ is to make the "read" determination * relative to the current file descriptor read pointer. * * The EV_POLL flag indicates the determination should be made via poll(2) * semantics. These semantics dictate always returning true for regular files, * regardless of the amount of unread data in the file. * * On input, EV_OOBAND specifies that filter should actively return in the * presence of OOB on the descriptor. It implies that filter will return * if there is OOB data available to read OR when any other condition * for the read are met (for example number of bytes regular data becomes >= * low-watermark). * If EV_OOBAND is not set on input, it implies that the filter should not actively * return for out of band data on the descriptor. The filter will then only return * when some other condition for read is met (ex: when number of regular data bytes * >=low-watermark OR when socket can't receive more data (SS_CANTRCVMORE)). * * On output, EV_OOBAND indicates the presence of OOB data on the descriptor. * If it was not specified as an input parameter, then the data count is the * number of bytes before the current OOB marker, else data count is the number * of bytes beyond OOB marker. */ #define EV_POLL EV_FLAG0 #define EV_OOBAND EV_FLAG1 struct kevent { uintptr_t ident; /* identifier for this event */ int16_t filter; /* filter for event */ uint16_t flags; /* general flags */ uint32_t fflags; /* filter-specific flags */ intptr_t data; /* filter-specific data */ void *udata; /* opaque user data identifier */ }; int kqueue(void); int kevent( int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout ); #define EV_SET(kevp, a, b, c, d, e, f) do { \ struct kevent *__kevp__ = (kevp); \ __kevp__->ident = (a); \ __kevp__->filter = (b); \ __kevp__->flags = (c); \ __kevp__->fflags = (d); \ __kevp__->data = (e); \ __kevp__->udata = (f); \ } while(0) #endif /* USE_KQUEUE_IDE_HELPER */ #endif /* KQUEUE_IDE_HELPER_H_ */ ================================================ FILE: include/swoole.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #ifdef HAVE_CONFIG_H #include "config.h" #elif defined(ENABLE_PHP_SWOOLE) #include "php_config.h" #endif #ifdef __cplusplus #define SW_EXTERN_C_BEGIN extern "C" { #define SW_EXTERN_C_END } #else #define SW_EXTERN_C_BEGIN #define SW_EXTERN_C_END #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef _PTHREAD_PSHARED #define _PTHREAD_PSHARED #endif /*--- C standard library ---*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef unsigned long ulong_t; #if defined(__GNUC__) #if __GNUC__ >= 3 #define sw_inline inline __attribute__((always_inline)) #else #define sw_inline inline #endif #elif defined(_MSC_VER) #define sw_inline __forceinline #else #define sw_inline inline #endif #if defined(__GNUC__) && __GNUC__ >= 4 #define SW_API __attribute__((visibility("default"))) #else #define SW_API #endif #if !defined(__GNUC__) || __GNUC__ < 3 #define __builtin_expect(x, expected_value) (x) #endif #define sw_likely(x) __builtin_expect(!!(x), 1) #define sw_unlikely(x) __builtin_expect(!!(x), 0) #define SW_START_LINE "-------------------------START----------------------------" #define SW_END_LINE "--------------------------END-----------------------------" #define SW_ECHO_RED "\e[31m%s\e[0m" #define SW_ECHO_GREEN "\e[32m%s\e[0m" #define SW_ECHO_YELLOW "\e[33m%s\e[0m" #define SW_ECHO_BLUE "\e[34m%s\e[0m" #define SW_ECHO_MAGENTA "\e[35m%s\e[0m" #define SW_ECHO_CYAN "\e[36m%s\e[0m" #define SW_ECHO_WHITE "\e[37m%s\e[0m" #define SW_ECHO_LEN_RED "\e[31m%.*s\e[0m" #define SW_ECHO_LEN_GREEN "\e[32m%.*s\e[0m" #define SW_ECHO_LEN_YELLOW "\e[33m%.*s\e[0m" #define SW_ECHO_LEN_BLUE "\e[34m%.*s\e[0m" #define SW_ECHO_LEN_MAGENTA "\e[35m%.*s\e[0m" #define SW_ECHO_LEN_CYAN "\e[36m%.*s\e[0m" #define SW_ECHO_LEN_WHITE "\e[37m%.*s\e[0m" #define SW_ECHO_RED_BG "\e[41m%s\e[0m" #define SW_ECHO_GREEN_BG "\e[42m%s\e[0m" #define SW_COLOR_RED 1 #define SW_COLOR_GREEN 2 #define SW_COLOR_YELLOW 3 #define SW_COLOR_BLUE 4 #define SW_COLOR_MAGENTA 5 #define SW_COLOR_CYAN 6 #define SW_COLOR_WHITE 7 #define SW_SPACE ' ' #define SW_CRLF "\r\n" #define SW_CRLF_LEN 2 #define SW_ASCII_CODE_0 64 #define SW_ASCII_CODE_Z 106 /*----------------------------------------------------------------------------*/ #include "swoole_config.h" #include "swoole_version.h" #include "swoole_log.h" #include "swoole_atomic.h" #include "swoole_error.h" #define SW_MAX(A, B) ((A) > (B) ? (A) : (B)) #define SW_MIN(A, B) ((A) < (B) ? (A) : (B)) #define SW_LOOP_N(n) for (decltype(n) i = 0; i < n; i++) #define SW_LOOP for (;;) #ifndef MAYBE_UNUSED #ifdef __GNUC__ #define MAYBE_UNUSED __attribute__((used)) #else #define MAYBE_UNUSED #endif #endif #ifndef MAX #define MAX(A, B) SW_MAX(A, B) #endif #ifndef MIN #define MIN(A, B) SW_MIN(A, B) #endif #define SW_NUM_BILLION (1000 * 1000 * 1000) #define SW_NUM_MILLION (1000 * 1000) #ifdef SW_DEBUG #define SW_ASSERT(e) assert(e) #define SW_ASSERT_1BYTE(v) \ do { \ size_t i = 0, n = 0; \ for (; i < sizeof(v); i++) { \ n += ((v >> i) & 1) ? 1 : 0; \ } \ assert(n == 1); \ } while (0) #else #define SW_ASSERT(e) #define SW_ASSERT_1BYTE(v) #endif #define SW_START_SLEEP usleep(100000) // sleep 0.1s, wait fork and pthread_create #ifdef SW_THREAD #define SW_THREAD_LOCAL thread_local extern std::mutex sw_thread_lock; #else #define SW_THREAD_LOCAL #endif /** * API naming rules * ----------------------------------- * - starts with swoole_, means it is ready or has been used as an external API * - starts with sw_, internal use only */ /*-----------------------------------Memory------------------------------------*/ void *sw_malloc(size_t size); void sw_free(void *ptr); void *sw_calloc(size_t nmemb, size_t size); void *sw_realloc(void *ptr, size_t size); // Evaluates to the number of elements in 'array' #define SW_ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) #define SW_DEFAULT_ALIGNMENT sizeof(unsigned long) #define SW_MEM_ALIGNED_SIZE(size) SW_MEM_ALIGNED_SIZE_EX(size, SW_DEFAULT_ALIGNMENT) #define SW_MEM_ALIGNED_SIZE_EX(size, alignment) (((size) + ((alignment) -1LL)) & ~((alignment) -1LL)) /*-------------------------------Declare Struct--------------------------------*/ namespace swoole { class MemoryPool; class Reactor; class String; class Timer; struct TimerNode; struct Event; class Pipe; class MessageBus; class Server; namespace network { struct Socket; struct Address; } // namespace network class AsyncThreads; #ifdef SW_USE_IOURING class Iouring; #endif namespace async { class ThreadPool; } struct Protocol; struct EventData; struct DataHead; typedef int (*ReactorHandler)(Reactor *reactor, Event *event); typedef std::function Callback; typedef std::function TimerCallback; } // namespace swoole typedef swoole::DataHead swDataHead; /*----------------------------------String-------------------------------------*/ #define SW_STRS(s) s, sizeof(s) #define SW_STRL(s) s, sizeof(s) - 1 #define SW_STREQ(str, len, const_str) swoole_streq(str, len, SW_STRL(const_str)) #define SW_STRCASEEQ(str, len, const_str) swoole_strcaseeq(str, len, SW_STRL(const_str)) #define SW_STR_STARTS_WITH(str, len, const_sub_str) swoole_str_starts_with(str, len, SW_STRL(const_sub_str)) #define SW_STR_ISTARTS_WITH(str, len, const_sub_str) swoole_str_istarts_with(str, len, SW_STRL(const_sub_str)) #if defined(SW_USE_JEMALLOC) || defined(SW_USE_TCMALLOC) #define sw_strdup swoole_strdup #define sw_strndup swoole_strndup #else #define sw_strdup strdup #define sw_strndup strndup #endif /** always return less than size, zero termination */ size_t sw_snprintf(char *buf, size_t size, const char *format, ...) __attribute__((format(printf, 3, 4))); size_t sw_vsnprintf(char *buf, size_t size, const char *format, va_list args); int sw_printf(const char *format, ...); bool sw_wait_for(const std::function &fn, int timeout_ms); static inline long sw_atol(const char *str) { return std::strtol(str, nullptr, 10); } static inline int sw_atoi(const char *str) { return static_cast(sw_atol(str)); } static inline void sw_memset_zero(void *s, size_t n) { memset(s, '\0', n); } #define sw_unset_bit(val, bit) val &= ~bit #define sw_set_bit(val, bit) val |= bit static inline int sw_mem_equal(const void *v1, size_t s1, const void *v2, size_t s2) { return s1 == s2 && memcmp(v1, v2, s2) == 0; } static inline size_t swoole_strlcpy(char *dest, const char *src, size_t size) { const size_t len = strlen(src); if (size != 0) { const size_t n = std::min(len, size - 1); memcpy(dest, src, n); dest[n] = '\0'; } return len; } static inline char *swoole_strdup(const char *s) { size_t l = strlen(s) + 1; char *p = static_cast(sw_malloc(l)); if (sw_likely(p)) { memcpy(p, s, l); } return p; } static inline char *swoole_strndup(const char *s, const size_t n) { char *p = static_cast(sw_malloc(n + 1)); if (sw_likely(p)) { strncpy(p, s, n)[n] = '\0'; } return p; } /* string equal */ static inline unsigned int swoole_streq(const char *str1, size_t len1, const char *str2, size_t len2) { return (len1 == len2) && (strncmp(str1, str2, len1) == 0); } static inline unsigned int swoole_strcaseeq(const char *str1, size_t len1, const char *str2, size_t len2) { return (len1 == len2) && (strncasecmp(str1, str2, len1) == 0); } static inline unsigned int swoole_str_starts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) { return (plen >= slen) && (strncmp(pstr, sstr, slen) == 0); } static inline unsigned int swoole_str_istarts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) { return (plen >= slen) && (strncasecmp(pstr, sstr, slen) == 0); } static inline const char *swoole_strnstr(const char *haystack, uint32_t haystack_length, const char *needle, uint32_t needle_length) { assert(needle_length > 0); if (sw_likely(needle_length <= haystack_length)) { for (uint32_t i = 0; i < haystack_length - needle_length + 1; i++) { if ((haystack[0] == needle[0]) && (0 == memcmp(haystack, needle, needle_length))) { return haystack; } haystack++; } } return nullptr; } static inline const char *swoole_strncasestr(const char *haystack, uint32_t haystack_length, const char *needle, uint32_t needle_length) { assert(needle_length > 0); if (sw_likely(needle_length <= haystack_length)) { for (uint32_t i = 0; i < haystack_length - needle_length + 1; i++) { if ((haystack[0] == needle[0]) && (0 == strncasecmp(haystack, needle, needle_length))) { return haystack; } haystack++; } } return nullptr; } static inline ssize_t swoole_strnpos(const char *haystack, uint32_t haystack_length, const char *needle, uint32_t needle_length) { assert(needle_length > 0); const char *pos = swoole_strnstr(haystack, haystack_length, needle, needle_length); return pos == nullptr ? -1 : pos - haystack; } static inline ssize_t swoole_strrnpos(const char *haystack, const char *needle, uint32_t length) { uint32_t needle_length = strlen(needle); assert(needle_length > 0); haystack += (length - needle_length); for (uint32_t i = length - needle_length; i > 0; i--) { if ((haystack[0] == needle[0]) && (0 == memcmp(haystack, needle, needle_length))) { return i; } haystack--; } return -1; } static inline void swoole_strtolower(char *str, const int length) { char *c = str; const char *e = c + length; while (c < e) { *c = static_cast(tolower(*c)); c++; } } /*--------------------------------Constants------------------------------------*/ enum swResultCode { SW_OK = 0, SW_ERR = -1, }; enum swReturnCode { SW_SUCCESS = 0, SW_CONTINUE = 1, SW_WAIT = 2, SW_CLOSE = 3, SW_ERROR = 4, SW_READY = 5, SW_INVALID = 6, SW_REDUCE_SIZE = 7, }; enum swFdType { SW_FD_SESSION, // server stream session SW_FD_STREAM_SERVER, // server stream port SW_FD_DGRAM_SERVER, // server dgram port SW_FD_PIPE, SW_FD_STREAM, SW_FD_AIO, /** * Coroutine Socket */ SW_FD_CO_SOCKET, /** * socket poll fd [coroutine::socket_poll] */ SW_FD_CO_POLL, /** * event waiter */ SW_FD_CO_EVENT, /** * signalfd */ SW_FD_SIGNAL, SW_FD_DNS_RESOLVER, SW_FD_CARES, /** * io_uring */ SW_FD_IOURING, /** * SW_FD_USER or SW_FD_USER+n: for custom event */ SW_FD_USER = 16, SW_FD_STREAM_CLIENT, SW_FD_DGRAM_CLIENT, }; enum swSocketFlag { SW_SOCK_NONBLOCK = 1 << 2, SW_SOCK_CLOEXEC = 1 << 3, SW_SOCK_SSL = (1u << 9), }; enum swSocketType { SW_SOCK_TCP = 1, SW_SOCK_UDP = 2, SW_SOCK_TCP6 = 3, SW_SOCK_UDP6 = 4, SW_SOCK_UNIX_STREAM = 5, // unix sock stream SW_SOCK_UNIX_DGRAM = 6, // unix sock dgram SW_SOCK_RAW = 7, SW_SOCK_RAW6 = 8, }; enum swTimeoutType { SW_TIMEOUT_DNS = 1 << 0, SW_TIMEOUT_CONNECT = 1 << 1, SW_TIMEOUT_READ = 1 << 2, SW_TIMEOUT_WRITE = 1 << 3, SW_TIMEOUT_RDWR = SW_TIMEOUT_READ | SW_TIMEOUT_WRITE, SW_TIMEOUT_ALL = SW_TIMEOUT_DNS | SW_TIMEOUT_CONNECT | SW_TIMEOUT_RDWR, }; enum swEventType { SW_EVENT_NULL = 0, SW_EVENT_DEAULT = 1u << 8, SW_EVENT_READ = 1u << 9, SW_EVENT_WRITE = 1u << 10, SW_EVENT_RDWR = SW_EVENT_READ | SW_EVENT_WRITE, SW_EVENT_ERROR = 1u << 11, SW_EVENT_ONCE = 1u << 12, }; enum swForkType { SW_FORK_SPAWN = 0, SW_FORK_EXEC = 1 << 1, SW_FORK_DAEMON = 1 << 2, SW_FORK_PRECHECK = 1 << 3, }; enum swTraverseOperation { SW_TRAVERSE_KEEP = 0, SW_TRAVERSE_REMOVE = 1, SW_TRAVERSE_STOP = 2, }; //------------------------------Base-------------------------------- #ifndef uchar typedef unsigned char uchar; #endif #define swoole_tolower(c) (uchar)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c) #define swoole_toupper(c) (uchar)((c >= 'a' && c <= 'z') ? (c & ~0x20) : c) /** * This function appends a '\0' at the end of the string, * so the allocated memory buffer must be len + 1. */ void swoole_random_string(char *buf, size_t len); void swoole_random_string(std::string &str, size_t len); uint64_t swoole_random_int(); size_t swoole_random_bytes(char *buf, size_t size); static inline char *swoole_strlchr(char *p, const char *last, char c) { while (p < last) { if (*p == c) { return p; } p++; } return nullptr; } static inline size_t swoole_size_align(size_t size, int pagesize) { return size + (pagesize - (size % pagesize)); } //------------------------------Base-------------------------------- enum swEventDataFlag { SW_EVENT_DATA_NORMAL, SW_EVENT_DATA_PTR = 1u << 1, SW_EVENT_DATA_CHUNK = 1u << 2, SW_EVENT_DATA_BEGIN = 1u << 3, SW_EVENT_DATA_END = 1u << 4, SW_EVENT_DATA_OBJ_PTR = 1u << 5, SW_EVENT_DATA_POP_PTR = 1u << 6, }; enum swTaskFlag { SW_TASK_TMPFILE = 1, SW_TASK_SERIALIZE = 1u << 1, SW_TASK_NONBLOCK = 1u << 2, SW_TASK_CALLBACK = 1u << 3, SW_TASK_WAITALL = 1u << 4, SW_TASK_COROUTINE = 1u << 5, SW_TASK_PEEK = 1u << 6, SW_TASK_NOREPLY = 1u << 7, }; enum swDNSLookupFlag { SW_DNS_LOOKUP_RANDOM = (1u << 11), }; extern thread_local char sw_error[SW_ERROR_MSG_SIZE]; enum swPipeType { SW_PIPE_WORKER = 0, SW_PIPE_MASTER = 1, SW_PIPE_READ = 0, SW_PIPE_WRITE = 1, SW_PIPE_NONBLOCK = 2, }; //----------------------Tool Function--------------------- uint32_t swoole_common_multiple(uint32_t u, uint32_t v); uint32_t swoole_common_divisor(uint32_t u, uint32_t v); int swoole_itoa(char *buf, long value); bool swoole_mkdir_recursive(const std::string &dir); int swoole_rand(); int swoole_rand(int min, int max); int swoole_system_random(int min, int max); int swoole_version_compare(const char *version1, const char *version2); void swoole_print_backtrace(); void swoole_print_backtrace_on_error(); char *swoole_string_format(size_t n, const char *format, ...); bool swoole_get_env(const char *name, int *value); int swoole_get_systemd_listen_fds(); void swoole_init(); void swoole_clean(); void swoole_exit(int _status); pid_t swoole_fork(int flags); pid_t swoole_fork_exec(const std::function &child_fn); pid_t swoole_waitpid(pid_t _pid, int *_stat_loc, int _options); void swoole_thread_init(bool main_thread = false); void swoole_thread_clean(bool main_thread = false); void swoole_redirect_stdout(int new_fd); void swoole_redirect_stdout(const char *file); int swoole_shell_exec(const char *command, pid_t *pid, bool get_error_stream); int swoole_daemon(int nochdir, int noclose); bool swoole_is_root_user(); void swoole_set_isolation(const std::string &group_, const std::string &user_, const std::string &chroot_); bool swoole_set_task_tmpdir(const std::string &dir); void swoole_set_process_death_signal(int signal); const std::string &swoole_get_task_tmpdir(); int swoole_tmpfile(char *filename); #ifdef HAVE_CPU_AFFINITY #ifdef __FreeBSD__ #include #include #include typedef cpuset_t cpu_set_t; #endif int swoole_set_cpu_affinity(cpu_set_t *set); int swoole_get_cpu_affinity(cpu_set_t *set); #endif namespace swoole { typedef long SessionId; typedef long TaskId; typedef uint8_t ReactorId; typedef uint32_t WorkerId; typedef swEventType EventType; typedef swSocketType SocketType; typedef swTimeoutType TimeoutType; typedef swFdType FdType; typedef swReturnCode ReturnCode; typedef swResultCode ResultCode; struct Event { int fd; int16_t reactor_id; FdType type; network::Socket *socket; }; struct DataHead { SessionId fd; uint64_t msg_id; uint32_t len; int16_t reactor_id; uint8_t type; uint8_t flags; uint16_t server_fd; uint16_t ext_flags; uint32_t reserved; double time; size_t dump(char *buf, size_t len); void print(); }; struct EventData { DataHead info; char data[SW_IPC_BUFFER_SIZE]; uint32_t size() const { return sizeof(info) + len(); } uint32_t len() const { return info.len; } }; struct ThreadGlobal { uint16_t id; uint8_t type; bool main_thread; int32_t error; String *buffer_stack; Reactor *reactor; Timer *timer; MessageBus *message_bus; AsyncThreads *async_threads; #ifdef SW_USE_IOURING Iouring *iouring; #endif bool signal_blocking_all; }; struct Allocator { void *(*malloc)(size_t size); void *(*calloc)(size_t nmemb, size_t size); void *(*realloc)(void *ptr, size_t size); void (*free)(void *ptr); }; struct NameResolver { enum Type { TYPE_KERNEL, TYPE_PHP, TYPE_USER, }; struct Context { int type; double timeout; void *private_data; bool with_port; bool cluster_; bool final_; std::function dtor; ~Context() { if (private_data && dtor) { dtor(this); } } }; std::function resolve; void *private_data; Type type; }; struct DnsServer { std::string host; int port; }; struct Global { uchar init : 1; uchar running : 1; uchar wait_signal : 1; uchar enable_signalfd : 1; /** * Under macOS or FreeBSD, kqueue does not support listening for writable events on pipes. When a large amount of * data is written to a pipe in process A, and the buffer becomes full, listening for writable events will not work. * In process B, even after consuming the data from the pipe, the writable event in process A cannot be triggered. * As a result, the functionality of Task and Process Server cannot be supported, making all scenarios relying on * pipes for inter-process communication unable to function properly. */ uchar enable_kqueue : 1; uchar dns_lookup_random : 1; uchar use_async_resolver : 1; uchar use_name_resolver : 1; uchar enable_coroutine : 1; uchar print_backtrace_on_error : 1; TaskId current_task_id; int signal_fd; bool signal_alarm; bool signal_dispatch; uint32_t signal_listener_num; uint32_t signal_async_listener_num; long trace_flags; void (*fatal_error)(int code, const char *str, ...); void (*print_backtrace)(); //-----------------------[System]-------------------------- uint16_t cpu_num; uint32_t pagesize; struct utsname uname; uint32_t max_sockets; uint32_t max_file_content; //-----------------------[Memory]-------------------------- MemoryPool *memory_pool; Allocator std_allocator; std::string task_tmpfile; //------------------[Single Instance]---------------------- Logger *logger; Server *server; FILE *stdout_; //-----------------------[DNS]----------------------------- DnsServer dns_server; double dns_cache_refresh_time; int dns_tries; std::string dns_resolvconf_path; std::string dns_hosts_path; std::list name_resolvers; //-----------------------[AIO]---------------------------- uint32_t aio_core_worker_num; uint32_t aio_worker_num; #ifdef SW_USE_IOURING uint32_t iouring_entries = 0; uint32_t iouring_workers = 0; uint32_t iouring_flag = 0; #endif double aio_max_wait_time; double aio_max_idle_time; network::Socket *aio_default_socket; //-----------------------[Hook]-------------------------- void *hooks[SW_MAX_HOOK_TYPE]; std::function user_exit_condition; // bug report message std::string bug_report_message; }; std::string dirname(const std::string &file); void hook_add(void **hooks, int type, const Callback &func, int push_back); void hook_call(void **hooks, int type, void *arg); double microtime(); void realtime_get(timespec *time); void realtime_add(timespec *time, int64_t add_msec); } // namespace swoole extern swoole::Global SwooleG; // Local Global Variable extern thread_local swoole::ThreadGlobal SwooleTG; // Thread Global Variable #define SW_CPU_NUM (SwooleG.cpu_num) static inline void swoole_set_last_error(int error) { SwooleTG.error = error; } static inline int swoole_get_last_error() { return SwooleTG.error; } static inline void swoole_clear_last_error() { SwooleTG.error = 0; } void swoole_clear_last_error_msg(); const char *swoole_get_last_error_msg(); static inline int swoole_get_thread_id() { return SwooleTG.id; } static inline int swoole_get_thread_type() { return SwooleTG.type; } static inline void swoole_set_thread_id(uint16_t id) { SwooleTG.id = id; } static inline void swoole_set_thread_type(uint8_t type) { SwooleTG.type = type; } static inline uint32_t swoole_pagesize() { return SwooleG.pagesize; } SW_API const char *swoole_strerror(int code); SW_API void swoole_throw_error(int code); SW_API void swoole_ignore_error(int code); SW_API bool swoole_is_ignored_error(int code); SW_API bool swoole_is_main_thread(); SW_API void swoole_set_log_level(int level); SW_API void swoole_set_log_file(const char *file); SW_API void swoole_set_trace_flags(long flags); SW_API void swoole_set_print_backtrace_on_error(bool enable = true); SW_API void swoole_set_stdout_stream(FILE *fp); SW_API void swoole_set_dns_server(const std::string &server); SW_API void swoole_set_hosts_path(const std::string &hosts_file); SW_API swoole::DnsServer swoole_get_dns_server(); SW_API bool swoole_load_resolv_conf(); SW_API void swoole_name_resolver_add(const swoole::NameResolver &resolver, bool append = true); SW_API void swoole_name_resolver_each( const std::function::iterator &iter)> &fn); SW_API std::string swoole_name_resolver_lookup(const std::string &host_name, swoole::NameResolver::Context *ctx); SW_API int swoole_get_log_level(); SW_API FILE *swoole_get_stdout_stream(); enum swEventInitFlag { SW_EVENTLOOP_WAIT_EXIT = 1, }; /** * manually_trigger: * Once enabled, the timer will no longer be triggered by event polling or the operating system's timer; * instead, it will be managed directly at the user space. */ SW_API swoole::Timer *swoole_timer_create(bool manually_trigger = false); SW_API long swoole_timer_after(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); SW_API long swoole_timer_tick(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); SW_API swoole::TimerNode *swoole_timer_add(double ms, bool persistent, const swoole::TimerCallback &callback, void *private_data = nullptr); SW_API swoole::TimerNode *swoole_timer_add(long ms, bool persistent, const swoole::TimerCallback &callback, void *private_data = nullptr); SW_API bool swoole_timer_del(swoole::TimerNode *tnode); SW_API bool swoole_timer_exists(long timer_id); SW_API void swoole_timer_delay(swoole::TimerNode *tnode, long delay_ms); SW_API swoole::TimerNode *swoole_timer_get(long timer_id); SW_API bool swoole_timer_clear(long timer_id); SW_API void swoole_timer_free(); SW_API void swoole_timer_select(); SW_API int64_t swoole_timer_get_next_msec(); SW_API bool swoole_timer_is_available(); SW_API int swoole_event_init(int flags); SW_API int swoole_event_add(swoole::network::Socket *socket, int events); SW_API int swoole_event_set(swoole::network::Socket *socket, int events); SW_API int swoole_event_add_or_update(swoole::network::Socket *socket, int event); SW_API int swoole_event_del(swoole::network::Socket *socket); SW_API void swoole_event_defer(const swoole::Callback &cb, void *private_data); SW_API ssize_t swoole_event_write(swoole::network::Socket *socket, const void *data, size_t len); SW_API ssize_t swoole_event_writev(swoole::network::Socket *socket, const iovec *iov, size_t iovcnt); SW_API swoole::network::Socket *swoole_event_get_socket(int fd); SW_API int swoole_event_wait(); SW_API int swoole_event_free(); SW_API void swoole_event_set_handler(int fd_type, int event, swoole::ReactorHandler handler); SW_API bool swoole_event_isset_handler(int fd_type, int event); SW_API bool swoole_event_is_available(); SW_API bool swoole_event_is_running(); static sw_inline swoole::String *sw_tg_buffer() { return SwooleTG.buffer_stack; } static sw_inline swoole::MemoryPool *sw_mem_pool() { return SwooleG.memory_pool; } static sw_inline const swoole::Allocator *sw_std_allocator() { return &SwooleG.std_allocator; } static sw_inline swoole::Reactor *sw_reactor() { return SwooleTG.reactor; } static sw_inline swoole::Timer *sw_timer() { return SwooleTG.timer; } ================================================ FILE: include/swoole_api.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef SW_C_API_H_ #define SW_C_API_H_ #ifdef __cplusplus extern "C" { #endif #include "swoole_config.h" enum swGlobalHookType { SW_GLOBAL_HOOK_BEFORE_SERVER_START, SW_GLOBAL_HOOK_BEFORE_CLIENT_START, SW_GLOBAL_HOOK_BEFORE_WORKER_START, SW_GLOBAL_HOOK_ON_CORO_START, SW_GLOBAL_HOOK_ON_CORO_STOP, SW_GLOBAL_HOOK_ON_REACTOR_CREATE, SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN, SW_GLOBAL_HOOK_BEFORE_WORKER_STOP, SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE, SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, SW_GLOBAL_HOOK_AFTER_FORK, SW_GLOBAL_HOOK_USER = 24, SW_GLOBAL_HOOK_END = SW_MAX_HOOK_TYPE - 1, }; typedef void (*swHookFunc)(void *data); void swoole_add_hook(swGlobalHookType type, swHookFunc cb, int push_back); void swoole_call_hook(swGlobalHookType type, void *arg); bool swoole_isset_hook(swGlobalHookType type); const char *swoole_version(void); int swoole_version_id(void); int swoole_api_version_id(void); #ifdef __cplusplus } /* end extern "C" */ #endif #endif ================================================ FILE: include/swoole_asm_context.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef _SW_ASM_CONTEXT_H_ #define _SW_ASM_CONTEXT_H_ #ifdef SW_USE_ASM_CONTEXT SW_EXTERN_C_BEGIN #include #include #include typedef void *fcontext_t; struct transfer_t { fcontext_t fctx; void *data; }; #ifdef __GNUC__ #define SW_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) #else #define SW_GCC_VERSION 0 #endif #if defined(__GNUC__) && SW_GCC_VERSION >= 9000 #define SW_INDIRECT_RETURN __attribute__((__indirect_return__)) #else #define SW_INDIRECT_RETURN #endif #undef SWOOLE_CONTEXT_CALLDECL #if (defined(i386) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i586__) || \ defined(__i686__) || defined(__X86__) || defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || \ defined(__INTEL__) || defined(__IA32__) || defined(_M_IX86) || defined(_I86_)) && \ defined(BOOST_WINDOWS) #define SWOOLE_CONTEXT_CALLDECL __cdecl #else #define SWOOLE_CONTEXT_CALLDECL #endif transfer_t SWOOLE_CONTEXT_CALLDECL swoole_jump_fcontext(fcontext_t to, void *vp); fcontext_t SWOOLE_CONTEXT_CALLDECL swoole_make_fcontext(void *stack, size_t stack_size, void (*fn)(transfer_t)); SW_EXTERN_C_END #endif #endif ================================================ FILE: include/swoole_async.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_socket.h" #include #ifndef O_DIRECT #define O_DIRECT 040000 #endif namespace swoole { enum AsyncFlag { SW_AIO_WRITE_FSYNC = 1u << 1, SW_AIO_EOF = 1u << 2, }; struct AsyncRequest { virtual ~AsyncRequest() = default; }; struct AsyncEvent { size_t task_id; uint8_t canceled; int error; /** * input & output */ std::shared_ptr data; /** * output */ ssize_t retval; /** * internal use only */ network::Socket *pipe_socket; double timestamp; void *object; void (*handler)(AsyncEvent *event); void (*callback)(AsyncEvent *event); bool catch_error() const { return (error == SW_ERROR_AIO_TIMEOUT || error == SW_ERROR_AIO_CANCELED); } }; struct GethostbynameRequest : AsyncRequest { std::string name; int family; std::string addr; GethostbynameRequest(std::string _name, int _family); ~GethostbynameRequest() override = default; }; struct GetaddrinfoRequest : public AsyncRequest { std::string hostname; std::string service; int family; int socktype; int protocol; int error; std::vector results; int count; void parse_result(std::vector &retval) const; GetaddrinfoRequest(std::string _hostname, int _family, int _socktype, int _protocol, std::string _service); ~GetaddrinfoRequest() override = default; }; class AsyncThreads { public: size_t task_num = 0; Pipe *pipe = nullptr; std::shared_ptr pool; network::Socket *read_socket = nullptr; network::Socket *write_socket = nullptr; AsyncThreads(); ~AsyncThreads(); size_t get_task_num() const { return task_num; } size_t get_queue_size() const; size_t get_worker_num() const; void notify_one() const; static int callback(Reactor *reactor, Event *event); }; namespace async { typedef void (*Handler)(AsyncEvent *event); AsyncEvent *dispatch(const AsyncEvent *request); void handler_gethostbyname(AsyncEvent *event); void handler_getaddrinfo(AsyncEvent *event); } // namespace async }; // namespace swoole swoole::AsyncThreads *sw_async_threads(); ================================================ FILE: include/swoole_atomic.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once typedef volatile int32_t sw_atomic_int32_t; typedef volatile uint32_t sw_atomic_uint32_t; typedef volatile int64_t sw_atomic_int64_t; typedef volatile uint64_t sw_atomic_uint64_t; typedef sw_atomic_int64_t sw_atomic_long_t; typedef sw_atomic_uint64_t sw_atomic_ulong_t; typedef sw_atomic_uint32_t sw_atomic_t; #define sw_atomic_cmp_set(lock, old, set) __sync_bool_compare_and_swap(lock, old, set) #define sw_atomic_value_cmp_set(value, expected, set) __sync_val_compare_and_swap(value, expected, set) #define sw_atomic_fetch_add(value, add) __sync_fetch_and_add(value, add) #define sw_atomic_fetch_sub(value, sub) __sync_fetch_and_sub(value, sub) #define sw_atomic_memory_barrier() __sync_synchronize() #define sw_atomic_add_fetch(value, add) __sync_add_and_fetch(value, add) #define sw_atomic_sub_fetch(value, sub) __sync_sub_and_fetch(value, sub) #if defined(__x86_64__) #define sw_atomic_cpu_pause() __asm__ __volatile__("pause") #elif defined(__aarch64__) #define sw_atomic_cpu_pause() __asm__ __volatile__("yield") #else #define sw_atomic_cpu_pause() #endif void sw_spinlock(sw_atomic_t *lock); #define sw_spinlock_release(lock) __sync_lock_release(lock) int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout); int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n); ================================================ FILE: include/swoole_base64.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include #include #define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4) #define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3) namespace swoole { size_t base64_encode(const unsigned char *in, size_t inlen, char *out); size_t base64_decode(const char *in, size_t inlen, char *out); } // namespace swoole ================================================ FILE: include/swoole_buffer.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include namespace swoole { struct BufferChunk { enum Type { TYPE_DATA, TYPE_SENDFILE, TYPE_CLOSE, }; Type type; uint32_t length = 0; uint32_t offset = 0; union { char *str; void *ptr; uint32_t u32; uint64_t u64; } value{}; uint32_t size = 0; BufferChunk(Type type, uint32_t size); ~BufferChunk(); void (*destroy)(BufferChunk *chunk) = nullptr; }; class Buffer { // 0: don't use chunk uint32_t chunk_size; uint32_t total_length = 0; std::queue queue_; public: explicit Buffer(uint32_t _chunk_size); ~Buffer(); BufferChunk *alloc(BufferChunk::Type type, uint32_t size); BufferChunk *front() const { return queue_.front(); } void pop(); void append(const char *data, uint32_t size); void append(const struct iovec *iov, size_t iovcnt, off_t offset); uint32_t length() const { return total_length; } size_t count() const { return queue_.size(); } bool empty() const { return queue_.empty(); } static bool empty(const Buffer *buffer) { return buffer == nullptr || buffer->queue_.empty(); } }; } // namespace swoole ================================================ FILE: include/swoole_channel.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Xinyu Zhu | | shiguangqi | | Twosee | | Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_lock.h" namespace swoole { enum ChannelFlag { SW_CHAN_LOCK = 1u << 1, SW_CHAN_NOTIFY = 1u << 2, SW_CHAN_SHM = 1u << 3, }; struct Channel { off_t head; off_t tail; size_t size; char head_tag; char tail_tag; int num; int max_num; /** * Data length, excluding structure */ size_t bytes; int flags; int maxlen; /** * memory point */ void *mem; Lock *lock; Pipe *notify_pipe; bool empty() const { return num == 0; } bool full() const { return ((head == tail && tail_tag != head_tag) || (bytes + sizeof(int) * num == size)); } int pop(void *out_buf, int buffer_length); int push(const void *in_data, int data_length); int out(void *out_buf, int buffer_length); int in(const void *in_data, int data_length); int peek(void *out, int buffer_length) const; int wait() const; int notify() const; void destroy(); void print() const; int count() const { return num; } size_t get_bytes() const { return bytes; } static Channel *make(size_t size, size_t maxlen, int flags); }; } // namespace swoole ================================================ FILE: include/swoole_client.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_protocol.h" #include "swoole_proxy.h" namespace swoole { namespace network { class Client { int (*connect_)(Client *cli, const char *host, int port, double timeout, int sock_flag) = nullptr; ssize_t (*send_)(Client *cli, const char *data, size_t length, int flags) = nullptr; int (*sendfile_)(Client *cli, const char *filename, off_t offset, size_t length) = nullptr; ssize_t (*recv_)(Client *cli, char *data, size_t length, int flags) = nullptr; public: int id = 0; long timeout_id = 0; // timeout node id int sock_type_ = 0; int sock_domain_ = 0; int sock_flags_ = 0; int protocol_ = 0; FdType fd_type; bool active = false; bool async = false; bool keep = false; bool http2 = false; bool sleep_ = false; bool wait_dns = false; bool dns_completed = false; bool host_preseted = false; bool shutdown_rw = false; bool shutdown_read = false; bool shutdown_write = false; bool remove_delay = false; bool closed = false; bool high_watermark = false; bool async_connect = false; bool onerror_called = false; /** * one package: length check */ bool open_length_check = false; bool open_eof_check = false; Protocol protocol = {}; std::unique_ptr socks5_proxy = nullptr; std::unique_ptr http_proxy = nullptr; uint32_t reuse_count = 0; std::string server_id; std::string server_host; int server_port = 0; void *ptr = nullptr; void *params = nullptr; TimerNode *timer = nullptr; /** * for connect()/sendto() */ Address server_addr = {}; /** * for recvfrom() */ Address remote_addr = {}; Socket *socket; void *object = nullptr; String *buffer = nullptr; uint32_t wait_length = 0; uint32_t input_buffer_size = 0; uint32_t buffer_high_watermark = 0; uint32_t buffer_low_watermark = 0; bool open_ssl = false; bool ssl_wait_handshake = false; std::shared_ptr ssl_context = nullptr; std::function onConnect = nullptr; std::function onError = nullptr; std::function onReceive = nullptr; std::function onClose = nullptr; std::function onBufferFull = nullptr; std::function onBufferEmpty = nullptr; static void init_reactor(Reactor *reactor); Client(SocketType _type, bool async); ~Client(); Socket *get_socket() const { return socket; } bool ready() const { return socket != nullptr; } SocketType get_socket_type() const { return socket->socket_type; } const std::string &get_http_proxy_host_name() const { if (ssl_context && !ssl_context->tls_host_name.empty()) { return ssl_context->tls_host_name; } return http_proxy->target_host; } int connect(const char *_host, int _port, double _timeout = -1, int _sock_flag = 0) { return connect_(this, _host, _port, _timeout, _sock_flag); } ssize_t send(const char *_data, size_t _length, int _flags = 0) { return send_(this, _data, _length, _flags); } int sendfile(const char *_filename, off_t _offset = 0, size_t _length = 0) { return sendfile_(this, _filename, _offset, _length); } ssize_t recv(char *_data, size_t _length, int _flags = 0) { return recv_(this, _data, _length, _flags); } int bind(const std::string &addr, int port) const; int sleep(); int wakeup(); int sendto(const std::string &host, int port, const char *data, size_t len) const; int get_peer_name(Address *addr) const; int shutdown(int _how = SHUT_RDWR); int close(); bool socks5_handshake(const char *recv_data, size_t length); void set_timeout(double timeout, TimeoutType type = SW_TIMEOUT_ALL) const; bool has_timedout() const; void set_socks5_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); void set_http_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); int enable_ssl_encrypt(); #ifdef SW_SUPPORT_DTLS void enable_dtls(); #endif int ssl_handshake() const; int ssl_verify(int allow_self_signed) const; bool set_ssl_key_file(const std::string &file) const { return ssl_context->set_key_file(file); } bool set_ssl_cert_file(const std::string &file) const { return ssl_context->set_cert_file(file); } void set_ssl_cafile(const std::string &file) const { ssl_context->cafile = file; } void set_ssl_capath(const std::string &path) const { ssl_context->capath = path; } void set_ssl_passphrase(const std::string &str) const { ssl_context->passphrase = str; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME void set_tls_host_name(const std::string &str) const { ssl_context->tls_host_name = str; // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); } #endif void set_ssl_dhparam(const std::string &file) const { ssl_context->dhparam = file; } void set_ssl_ecdh_curve(const std::string &str) const { ssl_context->ecdh_curve = str; } void set_ssl_protocols(long protocols) const { ssl_context->protocols = protocols; } void set_ssl_disable_compress(bool value) const { ssl_context->disable_compress = value; } void set_ssl_verify_peer(bool value) const { ssl_context->verify_peer = value; } void set_ssl_allow_self_signed(bool value) const { ssl_context->allow_self_signed = value; } void set_ssl_verify_depth(uint8_t value) const { ssl_context->verify_depth = value; } void set_ssl_ciphers(const std::string &str) const { ssl_context->ciphers = str; } #ifdef OPENSSL_IS_BORINGSSL void set_ssl_grease(uint8_t value) const { ssl_context->grease = value; } #endif const std::string &get_ssl_cert_file() const { return ssl_context->cert_file; } const std::string &get_ssl_key_file() const { return ssl_context->key_file; } }; //----------------------------------------Stream--------------------------------------- class Stream { public: String *buffer = nullptr; Client client; bool connected = false; bool cancel = false; int errCode = 0; void *private_data = nullptr; void *private_data_2 = nullptr; long private_data_fd = -1; std::function response = nullptr; int send(const char *data, size_t length); void set_max_length(uint32_t max_length); ~Stream(); static Stream *create(const char *dst_host, int dst_port, SocketType type); static ssize_t recv_sync(Socket *sock, void *_buf, size_t _len); static void set_protocol(Protocol *protocol); private: Stream(const char *dst_host, int dst_port, SocketType type); }; //----------------------------------------Stream End------------------------------------ class SyncClient { protected: Client client; bool connected = false; bool created; bool async = false; SocketType type; public: explicit SyncClient(SocketType _type, bool _async = false) : client(_type, _async), async(_async), type(_type) { created = client.socket != nullptr; } virtual bool connect(const char *host, int port, double timeout = -1) { if (connected || !created) { return false; } if (client.connect(host, port, timeout, client.socket->is_dgram()) < 0) { return false; } connected = true; return true; } void set_stream_protocol() { client.open_length_check = true; Stream::set_protocol(&client.protocol); } void set_package_max_length(uint32_t max_length) { client.protocol.package_max_length = max_length; } bool enable_ssl_encrypt() { if (client.enable_ssl_encrypt() < 0) { return false; } if (connected) { return client.ssl_handshake() == SW_OK; } else { return true; } } ssize_t send(const std::string &data) { return client.send(data.c_str(), data.length(), 0); } ssize_t send(const char *buf, size_t len) { return client.send(buf, len, 0); } bool sendfile(const char *filename, off_t offset = 0, size_t length = 0) { return client.sendfile(filename, offset, length) == SW_OK; } ssize_t recv(char *buf, size_t len, int flags = 0) { return client.recv(buf, len, flags); } bool close() { if (!created || client.closed) { return false; } client.close(); created = false; return true; } Client *get_client() { return &client; } virtual ~SyncClient() { if (created) { close(); } } }; class AsyncClient : public SyncClient { protected: std::function _onConnect = nullptr; std::function _onError = nullptr; std::function _onClose = nullptr; std::function _onReceive = nullptr; public: explicit AsyncClient(SocketType _type) : SyncClient(_type, true) {} bool connect(const char *host, int port, double timeout = -1) override { client.object = this; client.onConnect = [](Client *cli) { auto *ac = (AsyncClient *) cli->object; ac->_onConnect(ac); }; client.onError = [](Client *cli) { auto *ac = (AsyncClient *) cli->object; ac->_onError(ac); }; client.onClose = [](Client *cli) { auto *ac = (AsyncClient *) cli->object; ac->_onClose(ac); }; client.onReceive = [](Client *cli, const char *data, size_t length) { auto *ac = (AsyncClient *) cli->object; ac->_onReceive(ac, data, length); }; return SyncClient::connect(host, port, timeout); } void on_connect(const std::function &fn) { _onConnect = fn; } void on_error(const std::function &fn) { _onError = fn; } void on_close(const std::function &fn) { _onClose = fn; } void on_receive(const std::function &fn) { _onReceive = fn; } }; } // namespace network } // namespace swoole ================================================ FILE: include/swoole_config.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef SWOOLE_CONFIG_H_ #define SWOOLE_CONFIG_H_ #ifndef __clang__ // gcc version check #if defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) #error "GCC 4.8 or later required" #endif #endif #define SW_MAX_FDTYPE 32 // 32 kinds of event #define SW_MAX_HOOK_TYPE 32 #define SW_ERROR_MSG_SIZE 16384 #define SW_MAX_FILE_CONTENT (64 * 1024 * 1024) // for swoole_file_get_contents #define SW_MAX_LISTEN_PORT 60000 #define SW_MAX_CONNECTION 100000 #define SW_MAX_CONCURRENT_TASK 1024 #define SW_STACK_BUFFER_SIZE 65536 #ifdef HAVE_MALLOC_TRIM #define SW_USE_MALLOC_TRIM 1 #endif #define SW_MALLOC_TRIM_INTERVAL 60 #define SW_MALLOC_TRIM_PAD 0 #define SW_MAX_SOCKETS_DEFAULT 1024 #define SW_SOCKET_BUFFER_SIZE 8388608 #define SW_SOCKET_RETRY_COUNT 10 #define SW_SOCKET_DEFAULT_DNS_TIMEOUT 60 #define SW_SOCKET_DEFAULT_CONNECT_TIMEOUT 10 #define SW_SOCKET_DEFAULT_READ_TIMEOUT 60 #define SW_SOCKET_DEFAULT_WRITE_TIMEOUT 60 #define SW_SOCKET_CORK_MIN_SIZE 65536 #define SW_SYSTEMD_FDS_START 3 #define SW_GLOBAL_MEMORY_PAGESIZE (2 * 1024 * 1024) // global memory page #define SW_MAX_THREAD_NCPU 4 // n * cpu_num #define SW_MAX_WORKER_NCPU 1000 // n * cpu_num #define SW_HOST_MAXSIZE \ sizeof(((struct sockaddr_un *) NULL)->sun_path) // Linux has 108 UNIX_PATH_MAX, but BSD/macOS limit is only 104 #define SW_CLIENT_BUFFER_SIZE 65536 #define SW_CLIENT_CONNECT_TIMEOUT 0.5 // !!!Don't modify.---------------------------------------------------------- #ifdef __MACH__ #define SW_IPC_MAX_SIZE 2048 // MacOS #else #define SW_IPC_MAX_SIZE 8192 // for IPC, dgram and message-queue max size #endif #define SW_IPC_BUFFER_MAX_SIZE (64 * 1024) #define SW_IPC_BUFFER_SIZE (SW_IPC_MAX_SIZE - sizeof(swoole::DataHead)) #define SW_IPC_MSG_MIN (2048 - sizeof(swoole::DataHead)) // !!!End.------------------------------------------------------------------- #define SW_BUFFER_SIZE_STD 8192 #define SW_BUFFER_SIZE_BIG 65536 #define SW_BUFFER_SIZE_UDP 65536 #define SW_SENDFILE_CHUNK_SIZE 65536 #define SW_DATA_EOF "\r\n\r\n" #define SW_DATA_EOF_MAXLEN 8 #define SW_TASKWAIT_TIMEOUT 0.5 #define SW_AIO_THREAD_NUM_MULTIPLE 8 #define SW_AIO_THREAD_MAX_IDLE_TIME 1.0 #define SW_AIO_TASK_MAX_WAIT_TIME 0.001 #define SW_AIO_EVENT_NUM 128 #define SW_WORKER_MAX_WAIT_TIME 3 #define SW_WORKER_MIN_REQUEST 10 #define SW_WORKER_MAX_RECV_CHUNK_COUNT 32 #define SW_REACTOR_MAXEVENTS 4096 #define SW_SESSION_LIST_SIZE (1 * 1024 * 1024) #define SW_MSGMAX 65536 #define SW_MESSAGE_BOX_SIZE 65536 /** * The maximum number of Reactor threads * the number of the CPU cores threads will be started by default * number 8 is the maximum */ #define SW_REACTOR_MAX_THREAD 8 #define SW_INPUT_BUFFER_SIZE (2 * 1024 * 1024) #define SW_BUFFER_MIN_SIZE 65536 #define SW_SEND_BUFFER_SIZE 65536 #define SW_BACKLOG 512 /** * max accept times for single time */ #define SW_ACCEPT_MAX_COUNT 64 #define SW_ACCEPT_RETRY_TIME 1.0 #define SW_TCP_KEEPCOUNT 5 #define SW_TCP_KEEPIDLE 3600 // 1 hour #define SW_TCP_KEEPINTERVAL 60 #define SW_TASK_TMP_PATH_SIZE 256 #define SW_TASK_TMP_DIR "/tmp" #define SW_TASK_TMP_FILE "swoole.task.XXXXXX" #define SW_FILE_CHUNK_SIZE 65536 #define SW_TABLE_CONFLICT_PROPORTION 0.2 // 20% #define SW_TABLE_KEY_SIZE 64 #define SW_SSL_BUFFER_SIZE 16384 #define SW_SSL_CIPHER_LIST "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH" #define SW_SSL_ECDH_CURVE "auto" #define SW_SPINLOCK_LOOP_N 1024 #define SW_FILE_LOCK_DEFAULT_SECOND 0.001 #define SW_FILE_LOCK_MAX_SECOND 0.1 #define SW_SIGNO_MAX 128 #define SW_UNREGISTERED_SIGNAL_FMT "Unable to find callback function for signal %s" #define SW_DNS_HOST_BUFFER_SIZE 16 #define SW_DNS_SERVER_PORT 53 #define SW_DNS_RESOLV_CONF "/etc/resolv.conf" #define SW_Z_BEST_SPEED 1 #define SW_COMPRESSION_MIN_LENGTH_DEFAULT 20 #ifndef IOV_MAX #define IOV_MAX 16 #endif #define SW_IOV_MAX_ERROR_MSG "The maximum of iov count is %d" #define SW_IOURING_QUEUE_SIZE 8192 #define SW_IOURING_SQE_BATCH_SIZE 16 /** * HTTP Protocol */ #define SW_HTTP_SERVER_SOFTWARE "swoole-http-server" #define SW_HTTP_SERVER_BOUNDARY_PREKEY "SwooleBoundary" #define SW_HTTP_SERVER_BOUNDARY_TOTAL_SIZE 39 #define SW_HTTP_SERVER_PART_HEADER 256 #define SW_HTTP_PARAM_MAX_NUM 128 #define SW_HTTP_FORM_KEYLEN 512 #define SW_HTTP_RESPONSE_INIT_SIZE 65536 #define SW_HTTP_HEADER_MAX_SIZE 65536 #define SW_HTTP_HEADER_KEY_SIZE 128 #define SW_HTTP_UPLOAD_TMPDIR_SIZE SW_TASK_TMP_PATH_SIZE #define SW_HTTP_DATE_FORMAT "D, d M Y H:i:s T" #define SW_HTTP_RFC1123_DATE_GMT "%a, %d %b %Y %T GMT" #define SW_HTTP_RFC1123_DATE_UTC "%a, %d %b %Y %T UTC" #define SW_HTTP_RFC850_DATE "%A, %d-%b-%y %T GMT" #define SW_HTTP_ASCTIME_DATE "%a %b %e %T %Y" #define SW_HTTP_UPLOAD_FILE "Swoole-Upload-File" #define SW_HTTP_CHUNK_EOF "0\r\n\r\n" #define SW_HTTP_DEFAULT_CONTENT_TYPE "text/html" #define SW_HTTP_MAX_APPEND_DATA 16384 #define SW_HTTP_100_CONTINUE_PACKET "HTTP/1.1 100 Continue\r\n\r\n" #define SW_HTTP_BAD_REQUEST_PACKET "HTTP/1.1 400 Bad Request\r\n\r\n" #define SW_HTTP_REQUEST_ENTITY_TOO_LARGE_PACKET "HTTP/1.1 413 Request Entity Too Large\r\n\r\n" #define SW_HTTP_SERVICE_UNAVAILABLE_PACKET "HTTP/1.1 503 Service Unavailable\r\n\r\n" #define SW_HTTP_PAGE_CSS \ "" #define SW_HTTP_POWER_BY "
Powered by Swoole
" #define SW_HTTP_PAGE_400 \ "" SW_HTTP_PAGE_CSS "

HTTP 400 Bad Request

" SW_HTTP_POWER_BY "" #define SW_HTTP_PAGE_404 "" SW_HTTP_PAGE_CSS "

HTTP 404 Not Found

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

HTTP 500 Internal Server Error

" SW_HTTP_POWER_BY "" /** * HTTP2 Protocol */ #define SW_HTTP2_DATA_BUFFER_SIZE 8192 #define SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE (1 << 12) #define SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS UINT_MAX #define SW_HTTP2_DEFAULT_ENABLE_PUSH 0 #define SW_HTTP2_DEFAULT_MAX_FRAME_SIZE (1u << 14) #define SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE ((1 << 16) - 1) #define SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE UINT_MAX #define SW_HTTP_CLIENT_USERAGENT "swoole-http-client" #define SW_HTTP_CLIENT_BOUNDARY_PREKEY "----SwooleBoundary" #define SW_HTTP_CLIENT_BOUNDARY_TOTAL_SIZE 39 #define SW_HTTP_FORM_RAW_DATA_FMT "--%.*s\r\nContent-Disposition: form-data; name=\"%.*s\"\r\n\r\n" #define SW_HTTP_FORM_RAW_DATA_FMT_LEN 8 #define SW_HTTP_FORM_FILE_DATA_FMT \ "--%.*s\r\nContent-Disposition: form-data; name=\"%.*s\"; filename=\"%.*s\"\r\nContent-Type: %.*s\r\n\r\n" #define SW_HTTP_FORM_FILE_DATA_FMT_LEN 16 #define SW_WEBSOCKET_VERSION "13" #define SW_WEBSOCKET_KEY_LENGTH 16 #define SW_WEBSOCKET_QUEUE_SIZE 16 #define SW_WEBSOCKET_EXTENSION_DEFLATE "permessage-deflate; client_no_context_takeover; server_no_context_takeover" /** * MySQL Client */ #define SW_MYSQL_DEFAULT_HOST "127.0.0.1" #define SW_MYSQL_DEFAULT_PORT 3306 #define SW_MYSQL_DEFAULT_CHARSET 33 // 0x21, utf8_general_ci /** * PGSQL Client */ #define SW_PGSQL_CONNECT_TIMEOUT 3.0 /** * Coroutine */ #define SW_DEFAULT_C_STACK_SIZE (2 * 1024 * 1024) #define SW_CORO_BAILOUT_EXIT_CODE 1 #if 0 #define SW_CONTEXT_PROTECT_STACK_PAGE 1 #define SW_CONTEXT_DETECT_STACK_USAGE 1 #endif #ifdef SW_DEBUG #ifndef SW_LOG_TRACE_OPEN #define SW_LOG_TRACE_OPEN 1 #endif #endif #endif /* SWOOLE_CONFIG_H_ */ ================================================ FILE: include/swoole_coroutine.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_timer.h" #include "swoole_async.h" #include "swoole_util.h" #include "swoole_coroutine_context.h" #include #include #include #include #ifdef SW_USE_THREAD_CONTEXT #include #endif typedef std::chrono::microseconds seconds_type; #ifdef SW_CORO_TIME #define CALC_EXECUTE_USEC(yield_coroutine, resume_coroutine) calc_execute_usec(yield_coroutine, resume_coroutine) #else #define CALC_EXECUTE_USEC(yield_coroutine, resume_coroutine) #endif namespace swoole { class Coroutine { public: constexpr static int STACK_ALIGNED_SIZE = (4 * 1024); constexpr static int MIN_STACK_SIZE = (64 * 1024); constexpr static int MAX_STACK_SIZE = (16 * 1024 * 1024); constexpr static long MAX_NUM_LIMIT = LONG_MAX; enum State { STATE_INIT = 0, STATE_WAITING, STATE_RUNNING, STATE_END, }; enum Error { ERR_END = 0, ERR_LIMIT = -1, ERR_INVALID = -2, }; enum ResumeCode { RC_OK = 0, RC_TIMEDOUT = -1, RC_CANCELED = -2, }; typedef void (*SwapCallback)(void *); typedef std::function BailoutCallback; typedef std::function CancelFunc; void resume(); void yield(); void yield(CancelFunc *cancel_fn); bool cancel(); bool yield_ex(double timeout = -1); enum State get_state() const { return state; } long get_init_msec() const { return init_msec; } long get_cid() const { return cid; } Coroutine *get_origin() const { return origin; } long get_origin_cid() const { return sw_likely(origin) ? origin->get_cid() : -1; } void *get_task() const { return task; } bool is_end() const { return ctx.is_end(); } bool is_canceled() const { return resume_code_ == RC_CANCELED; } bool is_timedout() const { return resume_code_ == RC_TIMEDOUT; } bool is_suspending() const { return state == STATE_WAITING; } void set_task(void *_task) { task = _task; } void set_cancel_fn(CancelFunc *cancel_fn) { cancel_fn_ = cancel_fn; } long get_execute_usec() const { return time(true) - switch_usec + execute_usec; } coroutine::Context &get_ctx() { return ctx; } static SW_THREAD_LOCAL std::unordered_map coroutines; static void set_on_yield(SwapCallback func); static void set_on_resume(SwapCallback func); static void set_on_close(SwapCallback func); static void bailout(const BailoutCallback &func); static long create(const CoroutineFunc &fn, void *args = nullptr) { #ifdef SW_USE_THREAD_CONTEXT try { return (new Coroutine(fn, args))->run(); } catch (const std::system_error &e) { swoole_set_last_error(e.code().value()); swoole_warning("failed to create coroutine, Error: %s[%d]", e.what(), swoole_get_last_error()); return -1; } #else return (new Coroutine(fn, args))->run(); #endif } static void activate(); static void deactivate(); static Coroutine *get_current() { return current; } static Coroutine *get_current_safe() { if (sw_unlikely(!current)) { swoole_fatal_error(SW_ERROR_CO_OUT_OF_COROUTINE, "API must be called in the coroutine"); } return current; } static void *get_current_task() { return sw_likely(current) ? current->get_task() : nullptr; } static long get_current_cid() { return sw_likely(current) ? current->get_cid() : -1; } static Coroutine *get_by_cid(long cid) { auto i = coroutines.find(cid); return sw_likely(i != coroutines.end()) ? i->second : nullptr; } static void *get_task_by_cid(long cid) { Coroutine *co = get_by_cid(cid); return sw_likely(co) ? co->get_task() : nullptr; } static size_t get_stack_size() { return stack_size; } static void set_stack_size(size_t size) { stack_size = SW_MEM_ALIGNED_SIZE_EX(SW_MAX(MIN_STACK_SIZE, SW_MIN(size, MAX_STACK_SIZE)), STACK_ALIGNED_SIZE); } static void set_socket_bound_cid(long cid) { socket_bound_cid = cid; } static long get_last_cid() { return last_cid; } static long get_socket_bound_cid() { return socket_bound_cid; } static size_t count() { return coroutines.size(); } static uint64_t get_peak_num() { return peak_num; } static long get_elapsed(long cid) { Coroutine *co = cid == 0 ? get_current() : get_by_cid(cid); return sw_likely(co) ? Timer::get_absolute_msec() - co->get_init_msec() : -1; } static long get_execute_time(long cid) { Coroutine *co = cid == 0 ? get_current() : get_by_cid(cid); return sw_likely(co) ? co->get_execute_usec() : -1; } #ifdef SW_CORO_TIME static void calc_execute_usec(Coroutine *yield_coroutine, Coroutine *resume_coroutine); #endif static void print_list(); static void print_socket_bound_error(int sock_fd, const char *event_str, long bound_cid); protected: static SW_THREAD_LOCAL Coroutine *current; static SW_THREAD_LOCAL long last_cid; static SW_THREAD_LOCAL long socket_bound_cid; static SW_THREAD_LOCAL uint64_t peak_num; static SW_THREAD_LOCAL size_t stack_size; static SW_THREAD_LOCAL SwapCallback on_yield; /* before yield */ static SW_THREAD_LOCAL SwapCallback on_resume; /* before resume */ static SW_THREAD_LOCAL SwapCallback on_close; /* before close */ static SW_THREAD_LOCAL BailoutCallback on_bailout; /* when bailout */ static SW_THREAD_LOCAL bool activated; enum State state = STATE_INIT; enum ResumeCode resume_code_ = RC_OK; long cid; long init_msec = Timer::get_absolute_msec(); long switch_usec = time(true); long execute_usec = 0; void *task = nullptr; coroutine::Context ctx; Coroutine *origin = nullptr; CancelFunc *cancel_fn_ = nullptr; Coroutine(const CoroutineFunc &fn, void *private_data); long run(); void check_end(); void close(); }; //------------------------------------------------------------------------------- namespace coroutine { /** * Support for timeouts and cancellations requires the caller to store the memory pointers of * the input and output parameter objects in the `data` pointer of the `AsyncEvent` object. * This field is a `shared_ptr`, which increments the reference count when dispatched to the AIO thread, * collectively managing the `data` pointer. * When the async task is completed, the caller receives the results or cancels or timeouts, * the reference count will reach zero, and the memory will be released. */ bool async(async::Handler handler, AsyncEvent &event, double timeout = -1); /** * This function should be used for asynchronous operations that do not support cancellation and timeouts. * For example, in write/read operations, * asynchronous tasks cannot transfer the memory ownership of wbuf/rbuf to the AIO thread. * In the event of a timeout or cancellation, the memory of wbuf/rbuf will be released by the caller, * which may lead the AIO thread to read from an erroneous memory pointer and consequently crash. */ bool async(const std::function &fn); bool run(const CoroutineFunc &fn, void *arg = nullptr); bool wait_for(const std::function &fn); } // namespace coroutine //------------------------------------------------------------------------------- } // namespace swoole /** * for gdb */ swoole::Coroutine *swoole_coroutine_iterator_each(); void swoole_coroutine_iterator_reset(); swoole::Coroutine *swoole_coroutine_get(long cid); size_t swoole_coroutine_count(); ================================================ FILE: include/swoole_coroutine_api.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef SW_COROUTINE_API_H_ #define SW_COROUTINE_API_H_ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include /** * basic API */ long swoole_coroutine_create(void (*routine)(void *), void *arg); uint8_t swoole_coroutine_is_in(void); long swoole_coroutine_get_id(void); void swoole_coroutine_sleep(int sec); void swoole_coroutine_usleep(int usec); /** * file */ int swoole_coroutine_access(const char *pathname, int mode); int swoole_coroutine_open(const char *pathname, int flags, mode_t mode); ssize_t swoole_coroutine_read(int fd, void *buf, size_t count); ssize_t swoole_coroutine_write(int fd, const void *buf, size_t count); off_t swoole_coroutine_lseek(int fd, off_t offset, int whence); int swoole_coroutine_fstat(int fd, struct stat *statbuf); int swoole_coroutine_stat(const char *path, struct stat *statbuf); int swoole_coroutine_lstat(const char *path, struct stat *statbuf); ssize_t swoole_coroutine_readlink(const char *pathname, char *buf, size_t len); int swoole_coroutine_unlink(const char *pathname); int swoole_coroutine_mkdir(const char *pathname, mode_t mode); int swoole_coroutine_rmdir(const char *pathname); int swoole_coroutine_rename(const char *oldpath, const char *newpath); int swoole_coroutine_flock(int fd, int operation); int swoole_coroutine_statvfs(const char *path, struct statvfs *buf); int swoole_coroutine_close(int fd); int swoole_coroutine_fsync(int fd); int swoole_coroutine_fdatasync(int fd); int swoole_coroutine_ftruncate(int fd, off_t length); /** * stdio */ FILE *swoole_coroutine_fopen(const char *pathname, const char *mode); FILE *swoole_coroutine_fdopen(int fd, const char *mode); FILE *swoole_coroutine_freopen(const char *pathname, const char *mode, FILE *stream); size_t swoole_coroutine_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t swoole_coroutine_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); char *swoole_coroutine_fgets(char *s, int size, FILE *stream); int swoole_coroutine_fputs(const char *s, FILE *stream); int swoole_coroutine_fflush(FILE *stream); int swoole_coroutine_feof(FILE *stream); int swoole_coroutine_fclose(FILE *stream); /** * dir */ DIR *swoole_coroutine_opendir(const char *name); struct dirent *swoole_coroutine_readdir(DIR *dirp); int swoole_coroutine_closedir(DIR *dirp); /** * socket API * The `bind()` and `listen()` functions do not block IO waiting, * so the original functions can be used directly without the need for hooks. */ int swoole_coroutine_socket(int domain, int type, int protocol); int swoole_coroutine_socket_create(int fd); int swoole_coroutine_socket_unwrap(int fd); uint8_t swoole_coroutine_socket_exists(int fd); ssize_t swoole_coroutine_send(int sockfd, const void *buf, size_t len, int flags); ssize_t swoole_coroutine_sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags); ssize_t swoole_coroutine_recvmsg(int sockfd, struct msghdr *msg, int flags); int swoole_coroutine_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int swoole_coroutine_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout); int swoole_coroutine_poll_fake(struct pollfd *fds, nfds_t nfds, int timeout); int swoole_coroutine_socket_set_timeout(int fd, int which, double timeout); int swoole_coroutine_socket_set_connect_timeout(int fd, double timeout); int swoole_coroutine_socket_wait_event(int fd, int event, double timeout); int swoole_coroutine_getaddrinfo(const char *name, const char *service, const struct addrinfo *req, struct addrinfo **pai); struct hostent *swoole_coroutine_gethostbyname(const char *name); /** * wait */ size_t swoole_coroutine_wait_count(void); pid_t swoole_coroutine_waitpid(pid_t _pid, int *_stat_loc, int _options); pid_t swoole_coroutine_wait(int *_stat_loc); #ifdef __cplusplus } /* end extern "C" */ #endif #endif ================================================ FILE: include/swoole_coroutine_channel.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine.h" #include #include #include #include namespace swoole { namespace coroutine { //------------------------------------------------------------------------------- class Channel { public: enum Opcode { PRODUCER = 1, CONSUMER = 2, }; enum ErrorCode { ERROR_OK = 0, ERROR_TIMEOUT = -1, ERROR_CLOSED = -2, ERROR_CANCELED = -3, }; struct TimeoutMessage { Channel *chan; Opcode type; Coroutine *co; bool error; TimerNode *timer; }; void *pop(double timeout = -1); bool push(void *data, double timeout = -1); bool close(); explicit Channel(size_t _capacity = 1) : capacity(_capacity) {} ~Channel() { if (!producer_queue.empty()) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_CO_HAS_BEEN_DISCARDED, "channel is destroyed, %zu producers will be discarded", producer_queue.size()); } if (!consumer_queue.empty()) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_CO_HAS_BEEN_DISCARDED, "channel is destroyed, %zu consumers will be discarded", consumer_queue.size()); } } bool is_closed() const { return closed; } bool is_empty() const { return data_queue.empty(); } bool is_full() const { return data_queue.size() == capacity; } size_t length() const { return data_queue.size(); } size_t consumer_num() const { return consumer_queue.size(); } size_t producer_num() const { return producer_queue.size(); } void *pop_data() { if (data_queue.empty()) { return nullptr; } void *data = data_queue.front(); data_queue.pop(); return data; } int get_error() const { return error_; } protected: size_t capacity = 1; bool closed = false; int error_ = 0; std::list producer_queue; std::list consumer_queue; std::queue data_queue; static void timer_callback(Timer *timer, TimerNode *tnode); void yield(Opcode type); void consumer_remove(Coroutine *co) { consumer_queue.remove(co); } void producer_remove(Coroutine *co) { producer_queue.remove(co); } Coroutine *pop_coroutine(Opcode type) { Coroutine *co; if (type == PRODUCER) { co = producer_queue.front(); producer_queue.pop_front(); swoole_trace_log(SW_TRACE_CHANNEL, "resume producer cid=%ld", co->get_cid()); } else // if (type == CONSUMER) { co = consumer_queue.front(); consumer_queue.pop_front(); swoole_trace_log(SW_TRACE_CHANNEL, "resume consumer cid=%ld", co->get_cid()); } return co; } }; //------------------------------------------------------------------------------- } // namespace coroutine } // namespace swoole ================================================ FILE: include/swoole_coroutine_context.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #ifdef SW_USE_THREAD_CONTEXT #include #include #elif !defined(SW_USE_ASM_CONTEXT) #define USE_UCONTEXT 1 #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif #include #else #define USE_ASM_CONTEXT 1 #include "swoole_asm_context.h" #endif #if defined(HAVE_VALGRIND) && !defined(HAVE_KQUEUE) #define USE_VALGRIND 1 #include #endif #ifdef USE_UCONTEXT typedef ucontext_t coroutine_context_t; #elif defined(USE_ASM_CONTEXT) typedef fcontext_t coroutine_context_t; typedef transfer_t coroutine_transfer_t; #endif #if defined(USE_UCONTEXT) || defined(SW_USE_THREAD_CONTEXT) typedef void *coroutine_transfer_t; #endif typedef std::function CoroutineFunc; namespace swoole { namespace coroutine { class Context { public: Context(size_t stack_size, CoroutineFunc fn, void *private_data); ~Context(); bool swap_in(); bool swap_out(); #if !defined(SW_USE_THREAD_CONTEXT) && defined(SW_CONTEXT_DETECT_STACK_USAGE) ssize_t get_stack_usage(); #endif #ifndef SW_USE_THREAD_CONTEXT char *get_stack() const { return stack_; } size_t get_stack_size() const { return stack_size_; } #endif bool is_end() const { return end_; } protected: CoroutineFunc fn_; #ifdef SW_USE_THREAD_CONTEXT std::thread thread_; std::mutex lock_; std::mutex *swap_lock_; #else coroutine_context_t ctx_; coroutine_context_t swap_ctx_; char *stack_; uint32_t stack_size_; #endif #ifdef USE_VALGRIND uint32_t valgrind_stack_id; #endif void *private_data_; bool end_; static void context_func(coroutine_transfer_t arg); }; } // namespace coroutine } // namespace swoole ================================================ FILE: include/swoole_coroutine_socket.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_socket.h" #include "swoole_coroutine.h" #include "swoole_protocol.h" #include "swoole_proxy.h" #include namespace swoole { namespace coroutine { //------------------------------------------------------------------------------- /** * @return true: continue to wait for events * @return false: stop event waiting and resume coroutine */ using EventBarrier = std::function; class Socket { public: int errCode = 0; const char *errMsg = ""; std::string errString; bool open_length_check = false; bool open_eof_check = false; bool http2 = false; Protocol protocol = {}; std::unique_ptr socks5_proxy = nullptr; std::unique_ptr http_proxy = nullptr; static TimeoutType timeout_type_list[4]; Socket(int domain, int type, int protocol); Socket(int _fd, int _domain, int _type, int _protocol); explicit Socket(SocketType type = SW_SOCK_TCP); Socket(int _fd, SocketType _type); virtual ~Socket(); /** * If SSL is enabled, an SSL handshake will automatically take place during the connect() method. * When connect() returns true, it indicates that the TCP connection has been successfully * established and the SSL handshake has also succeeded. */ bool connect(const std::string &host, int port = 0, int flags = 0); virtual bool connect(const sockaddr *addr, socklen_t addrlen); bool shutdown(int how = SHUT_RDWR); bool cancel(EventType event); bool close(); bool is_connected() const { return connected && !is_closed(); } bool is_closed() const { return sock_fd == SW_BAD_SOCKET; } bool is_port_required() const { return type <= SW_SOCK_UDP6; } bool check_liveness(); ssize_t peek(void *_buf, size_t _n); virtual ssize_t recv(void *_buf, size_t _n); virtual ssize_t send(const void *_buf, size_t _n); ssize_t send(const std::string &buf) { return send(buf.c_str(), buf.length()); } /** * The read()/write()/recvmsg()/sendmsg() functions currently does not support SSL */ virtual ssize_t read(void *_buf, size_t _n); virtual ssize_t write(const void *_buf, size_t _n); virtual ssize_t recvmsg(msghdr *msg, int flags); virtual ssize_t sendmsg(const msghdr *msg, int flags); virtual ssize_t readv(network::IOVector *io_vector); virtual ssize_t readv_all(network::IOVector *io_vector); virtual ssize_t writev(network::IOVector *io_vector); virtual ssize_t writev_all(network::IOVector *io_vector); virtual ssize_t recv_all(void *_buf, size_t _n); virtual ssize_t send_all(const void *_buf, size_t _n); ssize_t recv_packet(double timeout = 0); ssize_t recv_line(void *_buf, size_t maxlen); ssize_t recv_with_buffer(void *_buf, size_t _n); char *pop_packet() const { if (read_buffer->offset == 0) { return nullptr; } else { return read_buffer->pop(buffer_init_size); } } virtual bool poll(EventType _type, double timeout = 0); /** * If the server has SSL enabled, you must explicitly call `ssl_handshake()`, * as it will not be automatically executed within the `accept()` function. * This behavior is inconsistent with `connect()`, which internally executes `ssl_handshake()` automatically, * thus not requiring an explicit call at the application level. * The reason for this design is that `ssl_handshake()` can typically be performed concurrently within a separate * client coroutine. If `ssl_handshake()` were to be automatically executed inside the `accept()` function, * it would block the server's listening coroutine, * causing the `ssl_handshake()` processes to execute sequentially rather than in parallel. */ Socket *accept(double timeout = 0); bool bind(const std::string &address, int port = 0); bool bind(const sockaddr *sa, socklen_t len); bool listen(int backlog = 0); virtual bool sendfile(const char *filename, off_t offset, size_t length); virtual ssize_t sendto(const std::string &host, int port, const void *_buf, size_t _n); ssize_t recvfrom(void *_buf, size_t _n); virtual ssize_t recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen); /** * Operation sequence: * 1. enable_ssl_encrypt() * 2. Set SSL parameters, such as certificate file, key file * 3. ssl_handshake(), to be executed after connect or accept */ bool enable_ssl_encrypt() { if (ssl_context.get()) { return false; } ssl_context = std::make_shared(); return true; } bool ssl_is_enable() const { return get_ssl_context() != nullptr; } SSLContext *get_ssl_context() const { return ssl_context.get(); } virtual bool ssl_handshake(); bool ssl_verify(bool allow_self_signed); std::string ssl_get_peer_cert(); bool set_ssl_key_file(const std::string &file) const { return ssl_context->set_key_file(file); } bool set_ssl_cert_file(const std::string &file) const { return ssl_context->set_cert_file(file); } void set_ssl_cafile(const std::string &file) const { ssl_context->cafile = file; } void set_ssl_capath(const std::string &path) const { ssl_context->capath = path; } void set_ssl_passphrase(const std::string &str) const { ssl_context->passphrase = str; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME void set_tls_host_name(const std::string &str) const { ssl_context->tls_host_name = str; // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); } #endif void set_ssl_dhparam(const std::string &file) const { ssl_context->dhparam = file; } void set_ssl_ecdh_curve(const std::string &str) const { ssl_context->ecdh_curve = str; } void set_ssl_protocols(long protocols) const { ssl_context->protocols = protocols; } void set_ssl_disable_compress(bool value) const { ssl_context->disable_compress = value; } void set_ssl_verify_peer(bool value) const { ssl_context->verify_peer = value; } void set_ssl_allow_self_signed(bool value) const { ssl_context->allow_self_signed = value; } void set_ssl_verify_depth(uint8_t value) const { ssl_context->verify_depth = value; } void set_ssl_ciphers(const std::string &str) const { ssl_context->ciphers = str; } #ifdef OPENSSL_IS_BORINGSSL void set_ssl_grease(uint8_t value) { ssl_context->grease = value; } #endif const std::string &get_ssl_cert_file() const { return ssl_context->cert_file; } const std::string &get_ssl_key_file() const { return ssl_context->key_file; } static inline void init_reactor(Reactor *reactor) { reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_READ, readable_event_callback); reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_WRITE, writable_event_callback); reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_ERROR, error_event_callback); } SocketType get_type() const { return type; } FdType get_fd_type() const { return socket->fd_type; } int get_sock_domain() const { return sock_domain; } int get_sock_type() const { return sock_type; } int get_sock_protocol() const { return sock_protocol; } int get_fd() const { return sock_fd; } network::Socket *get_socket() const { return socket; } bool getsockname() const; bool getpeername(network::Address *sa); const char *get_addr() const { return socket->get_addr(); } int get_port() const { return socket->get_port(); } bool has_bound(const EventType event = SW_EVENT_RDWR) const { return get_bound_co(event) != nullptr; } Coroutine *get_bound_co(const EventType event) const { if (event & SW_EVENT_READ) { if (read_co) { return read_co; } } if (event & SW_EVENT_WRITE) { if (write_co) { return write_co; } } return nullptr; } long get_bound_cid(const EventType event = SW_EVENT_RDWR) const { Coroutine *co = get_bound_co(event); return co ? co->get_cid() : 0; } const char *get_event_str(EventType event) const; void check_bound_co(const EventType event) const { auto bound_cid = get_bound_cid(event); if (sw_unlikely(bound_cid)) { Coroutine::print_socket_bound_error(sock_fd, get_event_str(event), bound_cid); } } void set_err(const int e) { errCode = errno = e; swoole_set_last_error(errCode); errMsg = e ? swoole_strerror(e) : ""; } void set_err() { errCode = swoole_get_last_error() ? swoole_get_last_error() : errno; errMsg = swoole_strerror(errCode); } void set_err(const int e, const char *s) { errCode = errno = e; swoole_set_last_error(errCode); errMsg = s; } void set_err(const int e, const std::string &s) { errCode = errno = e; swoole_set_last_error(errCode); errString = s; errMsg = errString.c_str(); } const char *get_err() { return swoole_strerror(errCode); } /* set connect read write timeout */ void set_timeout(double timeout, int _type = SW_TIMEOUT_ALL) const; void set_timeout(const timeval *timeout, int _type = SW_TIMEOUT_ALL) const { set_timeout((double) timeout->tv_sec + ((double) timeout->tv_usec / 1000 / 1000), _type); } double get_timeout(TimeoutType _type) const; bool get_option(int level, int optname, void *optval, socklen_t *optlen) const; bool get_option(int level, int optname, int *optval) const; bool set_option(int level, int optname, const void *optval, socklen_t optlen) const; bool set_option(int level, int optname, int optval) const; void set_socks5_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); void set_http_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); String *get_read_buffer(); String *get_write_buffer(); String *pop_read_buffer(); String *pop_write_buffer(); void set_resolve_context(NameResolver::Context *ctx) { resolve_context_ = ctx; } void set_dtor(const std::function &dtor) { dtor_ = dtor; } void set_zero_copy(bool enable) { zero_copy = enable; } void set_buffer_allocator(const Allocator *allocator) { buffer_allocator = allocator; } void set_buffer_init_size(size_t size) { if (size == 0) { return; } buffer_init_size = size; } int move_fd() { sock_fd = SW_BAD_SOCKET; return socket->move_fd(); } network::Socket *move_socket() { network::Socket *_socket = socket; socket = nullptr; return _socket; } bool ssl_is_available() const { return socket && ssl_handshaked; } SSL *get_ssl() const { return socket->ssl; } void ssl_close() const; protected: SocketType type; network::Socket *socket = nullptr; int sock_domain = 0; int sock_type = 0; int sock_protocol = 0; int sock_fd = -1; Coroutine *read_co = nullptr; Coroutine *write_co = nullptr; EventType want_event = SW_EVENT_NULL; std::string connect_host; int connect_port = 0; int backlog = 0; TimerNode *read_timer = nullptr; TimerNode *write_timer = nullptr; const Allocator *buffer_allocator = nullptr; size_t buffer_init_size = SW_BUFFER_SIZE_BIG; String *read_buffer = nullptr; String *write_buffer = nullptr; EventBarrier *recv_barrier = nullptr; EventBarrier *send_barrier = nullptr; bool ssl_is_server = false; bool ssl_handshaked = false; std::shared_ptr ssl_context = nullptr; std::string ssl_host_name; bool ssl_context_create(); bool ssl_create(SSLContext *ssl_context); bool connected = false; bool shutdown_read = false; bool shutdown_write = false; bool zero_copy = false; NameResolver::Context *resolve_context_ = nullptr; std::function dtor_; Socket(network::Socket *sock, const Socket *server_sock); static void timer_callback(Timer *timer, TimerNode *tnode); static int readable_event_callback(Reactor *reactor, Event *event); static int writable_event_callback(Reactor *reactor, Event *event); static int error_event_callback(Reactor *reactor, Event *event); void init_sock_type(SocketType _type); bool init_sock(); bool reinit_sock(SocketType _type); bool init_reactor_socket(int fd); void check_return_value(ssize_t retval) { if (retval >= 0) { set_err(0); } else if (errCode == 0) { set_err(errno); } } void init_options() { if (socket->is_tcp()) { set_option(IPPROTO_TCP, TCP_NODELAY, 1); } if (socket->is_udp()) { socket->set_buffer_size(network::Socket::default_buffer_size); } protocol.package_length_type = 'N'; protocol.package_length_size = 4; protocol.package_length_offset = 0; protocol.package_body_offset = 0; protocol.package_max_length = SW_INPUT_BUFFER_SIZE; } bool add_event(EventType event); bool wait_event(EventType event, const void **_buf = nullptr, size_t _n = 0); ssize_t recv_packet_with_length_protocol(); ssize_t recv_packet_with_eof_protocol(); bool is_available(const EventType event) { if (event != SW_EVENT_NULL) { check_bound_co(event); } if (sw_unlikely(is_closed())) { set_err(EBADF); return false; } if (sw_unlikely(socket->close_wait)) { set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); return false; } return true; } bool socks5_handshake(); const std::string &get_http_proxy_host_name() const { if (ssl_context && !ssl_context->tls_host_name.empty()) { return ssl_context->tls_host_name; } return http_proxy->target_host; } bool http_proxy_handshake(); class TimerController { public: TimerController(TimerNode **_timer_pp, double _timeout, Socket *_socket, TimerCallback _callback) : timer_pp(_timer_pp), timeout(_timeout), socket_(_socket), callback(std::move(_callback)) {} bool start(); ~TimerController(); private: bool enabled = false; TimerNode **timer_pp; double timeout; Socket *socket_; TimerCallback callback; }; public: class TimeoutSetter { public: TimeoutSetter(Socket *socket, double _timeout, TimeoutType _type); ~TimeoutSetter(); protected: Socket *socket_; double timeout; TimeoutType type; double original_timeout[sizeof(timeout_type_list)] = {}; }; class TimeoutController : public TimeoutSetter { public: TimeoutController(Socket *_socket, double _timeout, const TimeoutType _type) : TimeoutSetter(_socket, _timeout, _type) {} bool has_timedout(TimeoutType _type); protected: double startup_time = 0; }; }; class ProtocolSwitch { bool ori_open_eof_check; bool ori_open_length_check; Protocol ori_protocol; Socket *socket_; public: explicit ProtocolSwitch(Socket *socket) { ori_open_eof_check = socket->open_eof_check; ori_open_length_check = socket->open_length_check; ori_protocol = socket->protocol; socket_ = socket; } ~ProtocolSwitch() { /* revert protocol settings */ socket_->open_eof_check = ori_open_eof_check; socket_->open_length_check = ori_open_length_check; socket_->protocol = ori_protocol; } }; std::vector dns_lookup(const char *domain, int family = AF_INET, double timeout = 2.0); std::vector dns_lookup_impl_with_socket(const char *domain, int family, double timeout); #ifdef SW_USE_CARES std::vector dns_lookup_impl_with_cares(const char *domain, int family, double timeout); #endif std::string get_ip_by_hosts(const std::string &domain); //------------------------------------------------------------------------------- } // namespace coroutine } // namespace swoole ================================================ FILE: include/swoole_coroutine_system.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine.h" #include namespace swoole { namespace coroutine { //------------------------------------------------------------------------------- struct PollSocket { int events; int16_t revents; void *ptr; network::Socket *socket; PollSocket(int _event, void *_ptr) { events = _event; ptr = _ptr; revents = 0; socket = nullptr; } }; class System { public: static void init_reactor(Reactor *reactor); /* sleep */ static int sleep(double sec); /* file */ static std::shared_ptr read_file(const char *file, bool lock = false); static ssize_t write_file(const char *file, const char *buf, size_t length, bool lock = false, int flags = 0); /* dns */ static std::string gethostbyname(const std::string &hostname, int domain, double timeout = -1); static std::vector getaddrinfo(const std::string &hostname, int family = AF_INET, int socktype = SOCK_STREAM, int protocol = IPPROTO_TCP, const std::string &service = "", double timeout = -1); static void set_dns_cache_expire(time_t expire); static void set_dns_cache_capacity(size_t capacity); static void clear_dns_cache(); static float get_dns_cache_hit_ratio(); /* multiplexing */ static bool socket_poll(std::unordered_map &fds, double timeout); /* wait */ static pid_t wait(int *_stat_loc, double timeout = -1); static pid_t waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout = -1); /** * waitpid_safe() does not deps on the signal * and can be safely used in a multithreaded environment. */ static pid_t waitpid_safe(pid_t _pid, int *_stat_loc, int _options); /* signal */ static int wait_signal(int signal, double timeout = -1); static int wait_signal(const std::vector &signals, double timeout = -1); /* event */ /** * On failure, it returns -1, and you can use swoole_get_last_error() or errno to determine the cause of the * failure. If successful, it returns the event set, such as SW_EVENT_READ | SW_EVENT_WRITE. */ static int wait_event(int fd, int events, double timeout); static bool exec(const char *command, bool get_error_stream, std::shared_ptr buffer, int *status); }; std::string gethostbyname_impl_with_async(const std::string &hostname, int domain, double timeout = -1); //------------------------------------------------------------------------------- } // namespace coroutine } // namespace swoole ================================================ FILE: include/swoole_dtls.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_ssl.h" #ifdef SW_SUPPORT_DTLS #include namespace swoole { namespace dtls { //------------------------------------------------------------------------------- using Socket = network::Socket; int BIO_write(BIO *b, const char *data, int dlen); int BIO_read(BIO *b, char *data, int dlen); long BIO_ctrl(BIO *b, int cmd, long larg, void *pargs); int BIO_create(BIO *b); int BIO_destroy(BIO *b); BIO_METHOD *BIO_get_methods(); void BIO_meth_free(); struct Buffer { uint16_t length; uchar data[0]; }; struct Session { std::shared_ptr ctx; bool listened = false; Socket *socket; std::deque rxqueue; bool peek_mode = false; Session(Socket *_sock, const std::shared_ptr &_ctx) { socket = _sock; ctx = _ctx; } ~Session() { while (!rxqueue.empty()) { Buffer *buffer = rxqueue.front(); rxqueue.pop_front(); sw_free(buffer); } } bool init(); bool listen(); void append(const char *data, ssize_t len); void append(Buffer *buffer) { rxqueue.push_back(buffer); } size_t get_buffer_length() const { size_t total_length = 0; for (const auto i : rxqueue) { total_length += i->length; } return total_length; } }; //------------------------------------------------------------------------------- } // namespace dtls } // namespace swoole #endif ================================================ FILE: include/swoole_error.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once enum swErrorCode { /** * Prevent repetition with errno [syscall error] */ SW_ERROR_BEGIN = 500, /** * common error */ SW_ERROR_MALLOC_FAIL = 501, SW_ERROR_SYSTEM_CALL_FAIL, SW_ERROR_PHP_FATAL_ERROR, SW_ERROR_NAME_TOO_LONG, SW_ERROR_INVALID_PARAMS, SW_ERROR_QUEUE_FULL, SW_ERROR_OPERATION_NOT_SUPPORT, SW_ERROR_PROTOCOL_ERROR, SW_ERROR_WRONG_OPERATION, SW_ERROR_PHP_RUNTIME_NOTICE, // Non-fatal errors, just runtime warnings SW_ERROR_FOR_TEST, SW_ERROR_NO_PAYLOAD = 550, SW_ERROR_UNDEFINED_BEHAVIOR = 600, SW_ERROR_NOT_THREAD_SAFETY, SW_ERROR_FILE_NOT_EXIST = 700, SW_ERROR_FILE_TOO_LARGE, SW_ERROR_FILE_EMPTY, SW_ERROR_DIR_NOT_EXIST, SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST = 710, SW_ERROR_DNSLOOKUP_RESOLVE_FAILED, SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT, SW_ERROR_DNSLOOKUP_UNSUPPORTED, SW_ERROR_DNSLOOKUP_NO_SERVER, SW_ERROR_BAD_IPV6_ADDRESS = 720, SW_ERROR_UNREGISTERED_SIGNAL, SW_ERROR_BAD_HOST_ADDR, SW_ERROR_BAD_PORT, SW_ERROR_BAD_SOCKET_TYPE, // EventLoop SW_ERROR_EVENT_REMOVE_FAILED = 800, SW_ERROR_EVENT_ADD_FAILED, SW_ERROR_EVENT_UPDATE_FAILED, SW_ERROR_EVENT_UNKNOWN_DATA, /** * connection error */ SW_ERROR_SESSION_CLOSED_BY_SERVER = 1001, SW_ERROR_SESSION_CLOSED_BY_CLIENT, SW_ERROR_SESSION_CLOSING, SW_ERROR_SESSION_CLOSED, SW_ERROR_SESSION_NOT_EXIST, SW_ERROR_SESSION_INVALID_ID, SW_ERROR_SESSION_DISCARD_TIMEOUT_DATA, SW_ERROR_SESSION_DISCARD_DATA, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, SW_ERROR_OUTPUT_SEND_YIELD, SW_ERROR_SSL_NOT_READY, SW_ERROR_SSL_CANNOT_USE_SENFILE, SW_ERROR_SSL_EMPTY_PEER_CERTIFICATE, SW_ERROR_SSL_VERIFY_FAILED, SW_ERROR_SSL_BAD_CLIENT, SW_ERROR_SSL_BAD_PROTOCOL, SW_ERROR_SSL_RESET, SW_ERROR_SSL_HANDSHAKE_FAILED, SW_ERROR_SSL_CREATE_CONTEXT_FAILED, SW_ERROR_SSL_CREATE_SESSION_FAILED, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE = 1201, SW_ERROR_PACKAGE_LENGTH_NOT_FOUND, SW_ERROR_DATA_LENGTH_TOO_LARGE, SW_ERROR_PACKAGE_MALFORMED_DATA, /** * task error */ SW_ERROR_TASK_PACKAGE_TOO_BIG = 2001, SW_ERROR_TASK_DISPATCH_FAIL, SW_ERROR_TASK_TIMEOUT, /** * http2 protocol error */ SW_ERROR_HTTP2_STREAM_ID_TOO_BIG = 3001, SW_ERROR_HTTP2_STREAM_NO_HEADER, SW_ERROR_HTTP2_STREAM_NOT_FOUND, SW_ERROR_HTTP2_STREAM_IGNORE, SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED, SW_ERROR_HTTP2_INTERNAL_ERROR, /** * AIO */ SW_ERROR_AIO_BAD_REQUEST = 4001, SW_ERROR_AIO_CANCELED, SW_ERROR_AIO_TIMEOUT, /** * Client */ SW_ERROR_CLIENT_NO_CONNECTION = 5001, /** * Socket */ SW_ERROR_SOCKET_CLOSED = 6001, SW_ERROR_SOCKET_POLL_TIMEOUT, SW_ERROR_SOCKET_NOT_EXISTS, /** * Proxy */ SW_ERROR_SOCKS5_UNSUPPORT_VERSION = 7001, SW_ERROR_SOCKS5_UNSUPPORT_METHOD, SW_ERROR_SOCKS5_AUTH_FAILED, SW_ERROR_SOCKS5_SERVER_ERROR, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, SW_ERROR_SOCKS5_CONNECT_FAILED, SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR = 7101, SW_ERROR_HTTP_INVALID_PROTOCOL, SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED, SW_ERROR_HTTP_PROXY_BAD_RESPONSE, SW_ERROR_HTTP_CONFLICT_HEADER, SW_ERROR_HTTP_CONTEXT_UNAVAILABLE, SW_ERROR_HTTP_COOKIE_UNAVAILABLE, SW_ERROR_WEBSOCKET_BAD_CLIENT = 8501, SW_ERROR_WEBSOCKET_BAD_OPCODE, SW_ERROR_WEBSOCKET_UNCONNECTED, SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED, SW_ERROR_WEBSOCKET_PACK_FAILED, SW_ERROR_WEBSOCKET_UNPACK_FAILED, SW_ERROR_WEBSOCKET_INCOMPLETE_PACKET, /** * server global error */ SW_ERROR_SERVER_MUST_CREATED_BEFORE_CLIENT = 9001, SW_ERROR_SERVER_TOO_MANY_SOCKET, SW_ERROR_SERVER_WORKER_TERMINATED, SW_ERROR_SERVER_INVALID_LISTEN_PORT, SW_ERROR_SERVER_TOO_MANY_LISTEN_PORT, SW_ERROR_SERVER_PIPE_BUFFER_FULL, SW_ERROR_SERVER_NO_IDLE_WORKER, SW_ERROR_SERVER_ONLY_START_ONE, SW_ERROR_SERVER_SEND_IN_MASTER, SW_ERROR_SERVER_INVALID_REQUEST, SW_ERROR_SERVER_CONNECT_FAIL, SW_ERROR_SERVER_INVALID_COMMAND, SW_ERROR_SERVER_IS_NOT_REGULAR_FILE, SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT, SW_ERROR_SERVER_INVALID_CALLBACK, SW_ERROR_SERVER_UNRELATED_THREAD, /** * Process exit timeout, forced to end. */ SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT = 9101, SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA, /** * Coroutine */ SW_ERROR_CO_OUT_OF_COROUTINE = 10001, SW_ERROR_CO_HAS_BEEN_BOUND, SW_ERROR_CO_HAS_BEEN_DISCARDED, SW_ERROR_CO_MUTEX_DOUBLE_UNLOCK, SW_ERROR_CO_BLOCK_OBJECT_LOCKED, SW_ERROR_CO_BLOCK_OBJECT_WAITING, SW_ERROR_CO_YIELD_FAILED, SW_ERROR_CO_GETCONTEXT_FAILED, SW_ERROR_CO_SWAPCONTEXT_FAILED, SW_ERROR_CO_MAKECONTEXT_FAILED, SW_ERROR_CO_IOCPINIT_FAILED, SW_ERROR_CO_PROTECT_STACK_FAILED, SW_ERROR_CO_STD_THREAD_LINK_ERROR, SW_ERROR_CO_DISABLED_MULTI_THREAD, SW_ERROR_CO_CANNOT_CANCEL, SW_ERROR_CO_NOT_EXISTS, SW_ERROR_CO_CANCELED, SW_ERROR_CO_TIMEDOUT, // close failed, there are currently other coroutines holding this socket, // need to wait for the bound coroutine to return from the socket wait_event operation SW_ERROR_CO_SOCKET_CLOSE_WAIT, SW_ERROR_END }; namespace swoole { class Exception final : std::exception { public: int code; const char *msg; explicit Exception(int code) noexcept; }; } // namespace swoole ================================================ FILE: include/swoole_file.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_string.h" #include #include #include namespace swoole { ssize_t file_get_size(FILE *fp); ssize_t file_get_size(int fd); ssize_t file_get_size(const std::string &filename); std::shared_ptr file_get_contents(const std::string &filename); bool file_put_contents(const std::string &filename, const char *content, size_t length); bool file_exists(const std::string &filename); typedef struct stat FileStatus; class File { int fd_; int flags_; std::string path_; public: enum Flag { READ = O_RDONLY, WRITE = O_WRONLY, RW = O_RDWR, CREATE = O_CREAT, EXCL = O_EXCL, APPEND = O_APPEND, }; explicit File(const int fd) { fd_ = fd; flags_ = 0; } File(int fd, const std::string &path) { fd_ = fd; path_ = path; flags_ = 0; } File(const std::string &path, int oflags); File(const std::string &path, int oflags, int mode); ~File(); bool open(const std::string &path, int oflags, int mode = 0); bool close(); bool stat(FileStatus *_stat) const; bool ready() const { return fd_ != -1; } ssize_t write(const void *_buf, size_t _n) const { return ::write(fd_, _buf, _n); } ssize_t write(const std::string &str) const { return ::write(fd_, str.c_str(), str.length()); } ssize_t read(void *_buf, const size_t _n) const { return ::read(fd_, _buf, _n); } ssize_t pwrite(const void *_buf, size_t _n, off_t _offset) const { return ::pwrite(fd_, _buf, _n, _offset); } ssize_t pread(void *_buf, const size_t _n, off_t _offset) const { return ::pread(fd_, _buf, _n, _offset); } size_t write_all(const void *data, size_t len) const; size_t read_all(void *buf, size_t len) const; /** * Read one line of file, reading ends when __n - 1 bytes have been read, * or a newline (which is included in the return value), * or an EOF (read bytes less than __n) * Returns length of line on success, -1 otherwise. * NOTE: `buf` must be ended with zero. */ ssize_t read_line(void *_buf, size_t _n) const; std::shared_ptr read_content() const; bool sync() const { return ::fsync(fd_) == 0; } bool truncate(off_t length) const { return ::ftruncate(fd_, length) == 0; } off_t set_offset(off_t offset) const { return lseek(fd_, offset, SEEK_SET); } off_t get_offset() const { return lseek(fd_, 0, SEEK_CUR); } bool lock(int operation) const { return ::flock(fd_, operation) == 0; } bool unlock() const { return ::flock(fd_, LOCK_UN) == 0; } ssize_t get_size() const { return file_get_size(fd_); } void release() { fd_ = -1; } int get_fd() const { return fd_; } const std::string &get_path() const { return path_; } static bool exists(const std::string &file) { return ::access(file.c_str(), R_OK) == 0; } static bool remove(const std::string &file) { return ::remove(file.c_str()) == 0; } }; File make_tmpfile(); class AsyncFile { private: int fd = -1; int flags_ = 0; mode_t mode_ = 0; std::string path_; public: AsyncFile(const std::string &path, int flags, int mode); ~AsyncFile(); bool open(const std::string &path, int flags, mode_t mode); bool close() const; ssize_t read(void *buf, size_t count) const; ssize_t write(const void *buf, size_t count) const; ssize_t write(const String *buf) const { return write(SW_STRINGL(buf)); } bool sync() const; bool truncate(off_t length) const; bool stat(FileStatus *statbuf) const; off_t get_offset() const; off_t set_offset(off_t offset) const; bool ready() const { return fd != -1; } }; } // namespace swoole ================================================ FILE: include/swoole_file_hook.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef SW_FILE_HOOK_H_ #define SW_FILE_HOOK_H_ #include "swoole_coroutine_api.h" #define open(pathname, flags, mode) swoole_coroutine_open(pathname, flags, mode) #define close(fd) swoole_coroutine_close(fd) #define read(fd, buf, count) swoole_coroutine_read(fd, buf, count) #define write(fd, buf, count) swoole_coroutine_write(fd, buf, count) #define lseek(fd, offset, whence) swoole_coroutine_lseek(fd, offset, whence) #define readlink(fd, buf, size) swoole_coroutine_readlink(fd, buf, size) #define unlink(pathname) swoole_coroutine_unlink(pathname) #define mkdir(pathname, mode) swoole_coroutine_mkdir(pathname, mode) #define rmdir(pathname) swoole_coroutine_rmdir(pathname) #define rename(oldpath, newpath) swoole_coroutine_rename(oldpath, newpath) #define fsync(fd) swoole_coroutine_fsync(fd) #define fdatasync(fd) swoole_coroutine_fdatasync(fd) #define ftruncate(fd, length) swoole_coroutine_ftruncate(fd, length) #define access(pathname, mode) swoole_coroutine_access(pathname, mode) #define fopen(pathname, mode) swoole_coroutine_fopen(pathname, mode) #define fdopen(fd, mode) swoole_coroutine_fdopen(fd, mode) #define freopen(pathname, mode, stream) swoole_coroutine_freopen(pathname, mode, stream) #define fread(ptr, size, nmemb, stream) swoole_coroutine_fread(ptr, size, nmemb, stream) #define fwrite(ptr, size, nmemb, stream) swoole_coroutine_fwrite(ptr, size, nmemb, stream) #define fgets(s, size, stream) swoole_coroutine_fgets(s, size, stream) #define fputs(s, stream) swoole_coroutine_fputs(s, stream) #define feof(stream) swoole_coroutine_feof(stream) #define fflush(stream) swoole_coroutine_fflush(stream) #define fclose(stream) swoole_coroutine_fclose(stream) #define opendir(name) swoole_coroutine_opendir(name) #define readdir(dir) swoole_coroutine_readdir(dir) #define closedir(dir) swoole_coroutine_closedir(dir) #endif ================================================ FILE: include/swoole_hash.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include uint64_t swoole_hash_jenkins(const char *key, size_t keylen); uint64_t swoole_hash_php(const char *key, size_t len); uint64_t swoole_hash_austin(const char *key, size_t keylen); uint32_t swoole_crc32(const char *data, size_t size); ================================================ FILE: include/swoole_heap.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include namespace swoole { struct HeapNode { uint64_t priority; uint32_t position; void *data; }; class Heap { public: enum Type { MIN_HEAP, MAX_HEAP, }; Heap(size_t _n, Type _type); ~Heap(); size_t count() const { return num - 1; } HeapNode *push(uint64_t priority, void *data); void *pop(); void change_priority(uint64_t new_priority, HeapNode *ptr) const; void remove(HeapNode *node); void *peek() const; void print() const; int compare(uint64_t a, uint64_t b) const; HeapNode *top() const { if (num == 1) { return nullptr; } return nodes[1]; } private: uint32_t num; uint32_t size; enum Type type; HeapNode **nodes; void bubble_up(uint32_t i) const; uint32_t maxchild(uint32_t i) const; void percolate_down(uint32_t i) const; }; } // namespace swoole ================================================ FILE: include/swoole_http.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_protocol.h" #include enum swHttpVersion { SW_HTTP_VERSION_10 = 1, SW_HTTP_VERSION_11, SW_HTTP_VERSION_2, SW_HTTP_VERSION_3, }; enum swHttpMethod { SW_HTTP_DELETE = 1, SW_HTTP_GET, SW_HTTP_HEAD, SW_HTTP_POST, SW_HTTP_PUT, SW_HTTP_PATCH, /* pathological */ SW_HTTP_CONNECT, SW_HTTP_OPTIONS, SW_HTTP_TRACE, /* webdav */ SW_HTTP_COPY, SW_HTTP_LOCK, SW_HTTP_MKCOL, SW_HTTP_MOVE, SW_HTTP_PROPFIND, SW_HTTP_PROPPATCH, SW_HTTP_UNLOCK, /* subversion */ SW_HTTP_REPORT, SW_HTTP_MKACTIVITY, SW_HTTP_CHECKOUT, SW_HTTP_MERGE, /* upnp */ SW_HTTP_MSEARCH, SW_HTTP_NOTIFY, SW_HTTP_SUBSCRIBE, SW_HTTP_UNSUBSCRIBE, /* proxy */ SW_HTTP_PURGE, /* Http2 */ SW_HTTP_PRI, }; enum swHttpStatusCode { SW_HTTP_CONTINUE = 100, SW_HTTP_SWITCHING_PROTOCOLS = 101, SW_HTTP_PROCESSING = 102, SW_HTTP_OK = 200, SW_HTTP_CREATED = 201, SW_HTTP_ACCEPTED = 202, SW_HTTP_NO_CONTENT = 204, SW_HTTP_PARTIAL_CONTENT = 206, SW_HTTP_SPECIAL_RESPONSE = 300, SW_HTTP_MOVED_PERMANENTLY = 301, SW_HTTP_MOVED_TEMPORARILY = 302, SW_HTTP_SEE_OTHER = 303, SW_HTTP_NOT_MODIFIED = 304, SW_HTTP_TEMPORARY_REDIRECT = 307, SW_HTTP_PERMANENT_REDIRECT = 308, SW_HTTP_BAD_REQUEST = 400, SW_HTTP_UNAUTHORIZED = 401, SW_HTTP_FORBIDDEN = 403, SW_HTTP_NOT_FOUND = 404, SW_HTTP_NOT_ALLOWED = 405, SW_HTTP_REQUEST_TIME_OUT = 408, SW_HTTP_CONFLICT = 409, SW_HTTP_LENGTH_REQUIRED = 411, SW_HTTP_PRECONDITION_FAILED = 412, SW_HTTP_REQUEST_ENTITY_TOO_LARGE = 413, SW_HTTP_REQUEST_URI_TOO_LARGE = 414, SW_HTTP_UNSUPPORTED_MEDIA_TYPE = 415, SW_HTTP_RANGE_NOT_SATISFIABLE = 416, SW_HTTP_MISDIRECTED_REQUEST = 421, SW_HTTP_TOO_MANY_REQUESTS = 429, SW_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, SW_HTTP_INTERNAL_SERVER_ERROR = 500, SW_HTTP_NOT_IMPLEMENTED = 501, SW_HTTP_BAD_GATEWAY = 502, SW_HTTP_SERVICE_UNAVAILABLE = 503, SW_HTTP_GATEWAY_TIME_OUT = 504, SW_HTTP_VERSION_NOT_SUPPORTED = 505, SW_HTTP_INSUFFICIENT_STORAGE = 507 }; struct multipart_parser; namespace swoole { class Server; namespace http_server { //----------------------------------------------------------------- struct FormData { const char *multipart_boundary_buf; uint32_t multipart_boundary_len; multipart_parser *multipart_parser_; String *multipart_buffer_; String *upload_tmpfile; std::string upload_tmpfile_fmt_; const char *current_header_name; size_t current_header_name_len; size_t upload_filesize; size_t upload_max_filesize; }; struct Request { uint8_t method; uint8_t version; uchar excepted : 1; uchar too_large : 1; uchar unavailable : 1; uchar header_parsed : 1; uchar tried_to_dispatch : 1; uchar multipart_header_parsed : 1; uchar known_length : 1; uchar keep_alive : 1; uchar chunked : 1; uchar nobody_chunked : 1; uint32_t url_offset_; uint32_t url_length_; uint32_t max_length_; uint32_t request_line_length_; /* without \r\n */ uint32_t header_length_; /* include request_line_length + \r\n */ uint64_t content_length_; FormData *form_data_; String *buffer_; Request() { clean(); form_data_ = nullptr; buffer_ = nullptr; } ~Request(); void clean() { memset(&method, 0, offsetof(Request, form_data_)); } int get_protocol(); int get_header_length(); int get_chunked_body_length(); void parse_header_info(); bool parse_multipart_data(String *buffer); bool init_multipart_parser(const Server *server); void destroy_multipart_parser(); std::string get_header(const char *name) const; bool has_expect_header() const; }; typedef std::function ParseCookieCallback; int get_method(const char *method_str, size_t method_len); const char *get_method_string(int method); const char *get_status_message(int code); extern int list_of_status_code[128]; /** * This function will directly modify the data in the input buffer. */ size_t url_decode(char *str, size_t len); /** * Returns a new memory address, and the pointer at this address needs to be manually released in the calling layer. */ char *url_encode(char const *str, size_t len); int dispatch_request(Server *serv, const Protocol *proto, network::Socket *socket, const RecvData *rdata); bool parse_multipart_boundary( const char *at, size_t length, size_t offset, char **out_boundary_str, int *out_boundary_len); void parse_cookie(const char *at, size_t length, const ParseCookieCallback &cb); ssize_t get_package_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); uint8_t get_package_length_size(network::Socket *conn); int dispatch_frame(const Protocol *protocol, network::Socket *conn, const RecvData *rdata); struct ContextImpl; class Context { public: Context(Server *server, SessionId session_id, ContextImpl *_impl) { server_ = server; session_id_ = session_id; impl = _impl; } ~Context(); bool end(const std::string &data) { return end(data.c_str(), data.length()); } bool end(const char *data, size_t length); void setHeader(const std::string &key, const std::string &value) { response.headers[key] = value; } void setStatusCode(int code) { response.code = code; } // Request int version = 0; bool keepalive = false; bool post_form_urlencoded = false; std::string request_path; std::string query_string; std::string server_protocol; std::unordered_map headers; std::unordered_map files; std::unordered_map form_data; std::string body; // Response struct { int code = 200; std::unordered_map headers; } response; // Impl Server *server_; SessionId session_id_; ContextImpl *impl; }; std::shared_ptr listen(const std::string &addr, const std::function &cb, int mode = 1); //----------------------------------------------------------------- } // namespace http_server } // namespace swoole ================================================ FILE: include/swoole_http2.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_protocol.h" #define SW_HTTP2_PRI_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" enum swHttp2ErrorCode { SW_HTTP2_ERROR_NO_ERROR = 0x0, SW_HTTP2_ERROR_PROTOCOL_ERROR = 0x1, SW_HTTP2_ERROR_INTERNAL_ERROR = 0x2, SW_HTTP2_ERROR_FLOW_CONTROL_ERROR = 0x3, SW_HTTP2_ERROR_SETTINGS_TIMEOUT = 0x4, SW_HTTP2_ERROR_STREAM_CLOSED = 0x5, SW_HTTP2_ERROR_FRAME_SIZE_ERROR = 0x6, SW_HTTP2_ERROR_REFUSED_STREAM = 0x7, SW_HTTP2_ERROR_CANCEL = 0x8, SW_HTTP2_ERROR_COMPRESSION_ERROR = 0x9, SW_HTTP2_ERROR_CONNECT_ERROR = 0xa, SW_HTTP2_ERROR_ENHANCE_YOUR_CALM = 0xb, SW_HTTP2_ERROR_INADEQUATE_SECURITY = 0xc, SW_HTTP2_ERROR_HTTP_1_1_REQUIRED = 0xd, }; enum swHttp2FrameType { SW_HTTP2_TYPE_DATA = 0, SW_HTTP2_TYPE_HEADERS = 1, SW_HTTP2_TYPE_PRIORITY = 2, SW_HTTP2_TYPE_RST_STREAM = 3, SW_HTTP2_TYPE_SETTINGS = 4, SW_HTTP2_TYPE_PUSH_PROMISE = 5, SW_HTTP2_TYPE_PING = 6, SW_HTTP2_TYPE_GOAWAY = 7, SW_HTTP2_TYPE_WINDOW_UPDATE = 8, SW_HTTP2_TYPE_CONTINUATION = 9, }; enum swHttp2FrameFlag { SW_HTTP2_FLAG_NONE = 0x00, SW_HTTP2_FLAG_ACK = 0x01, SW_HTTP2_FLAG_END_STREAM = 0x01, SW_HTTP2_FLAG_END_HEADERS = 0x04, SW_HTTP2_FLAG_PADDED = 0x08, SW_HTTP2_FLAG_PRIORITY = 0x20, }; enum swHttp2SettingId { SW_HTTP2_SETTING_HEADER_TABLE_SIZE = 0x1, SW_HTTP2_SETTINGS_ENABLE_PUSH = 0x2, SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE = 0x4, SW_HTTP2_SETTINGS_MAX_FRAME_SIZE = 0x5, SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x6, }; enum swHttp2StreamFlag { SW_HTTP2_STREAM_NORMAL = 0, SW_HTTP2_STREAM_REQUEST_END = 1 << 0, SW_HTTP2_STREAM_PIPELINE_REQUEST = 1 << 1, SW_HTTP2_STREAM_PIPELINE_RESPONSE = 1 << 2, SW_HTTP2_STREAM_USE_PIPELINE_READ = 1 << 3, }; #define SW_HTTP2_FRAME_HEADER_SIZE 9 #define SW_HTTP2_SETTING_OPTION_SIZE 6 #define SW_HTTP2_SETTING_FRAME_SIZE (SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_SETTING_OPTION_SIZE * 6) #define SW_HTTP2_FRAME_PING_PAYLOAD_SIZE 8 #define SW_HTTP2_RST_STREAM_SIZE 4 #define SW_HTTP2_PRIORITY_SIZE 5 #define SW_HTTP2_PING_SIZE 8 #define SW_HTTP2_RST_STREAM_SIZE 4 #define SW_HTTP2_GOAWAY_SIZE 8 #define SW_HTTP2_WINDOW_UPDATE_SIZE 4 #define SW_HTTP2_STREAM_ID_SIZE 4 #define SW_HTTP2_SETTINGS_PARAM_SIZE 6 #define swoole_http2_frame_trace_log(_trace_str, ...) \ swoole_trace_log(SW_TRACE_HTTP2, \ SW_ECHO_RED_BG " [" SW_ECHO_GREEN "] " \ " " _trace_str, \ " RECV ", \ swoole::http2::get_type(type), \ length, \ swoole::http2::get_flag_string(flags).c_str(), \ stream_id, \ ##__VA_ARGS__) #define swoole_http2_send_trace_log(_trace_str, ...) \ swoole_trace_log(SW_TRACE_HTTP2, SW_ECHO_GREEN_BG " " _trace_str, " SEND ", ##__VA_ARGS__) #define swoole_http2_recv_trace_log(_trace_str, ...) \ swoole_trace_log(SW_TRACE_HTTP2, SW_ECHO_RED_BG " " _trace_str, " RECV ", ##__VA_ARGS__) namespace swoole { namespace http2 { struct Settings { uint32_t header_table_size; uint32_t enable_push; uint32_t max_concurrent_streams; uint32_t init_window_size; uint32_t max_frame_size; uint32_t max_header_list_size; }; /** +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ */ struct Frame { uint32_t length : 24; uint32_t type : 8; uint32_t flags : 8; uint32_t rsv1 : 1; uint32_t identifier : 31; char data[0]; }; static sw_inline ssize_t get_length(const char *buf) { return (((uint8_t) buf[0]) << 16) + (((uint8_t) buf[1]) << 8) + (uint8_t) buf[2]; } void put_default_setting(enum swHttp2SettingId id, uint32_t value); uint32_t get_default_setting(enum swHttp2SettingId id); size_t pack_setting_frame(char *buf, const Settings &settings, bool server_side); ReturnCode unpack_setting_data(const char *buf, ssize_t length, const std::function &cb); ssize_t get_frame_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); int send_setting_frame(Protocol *protocol, network::Socket *conn); const char *get_type(int type); int get_type_color(int type); static sw_inline void init_settings(Settings *settings) { settings->header_table_size = get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE); settings->enable_push = get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH); settings->max_concurrent_streams = get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); settings->init_window_size = get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE); settings->max_frame_size = get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE); settings->max_header_list_size = get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE); } static inline std::string get_flag_string(int _flags) { std::string str; if (_flags & SW_HTTP2_FLAG_ACK) { str.append("ACK|"); } if (_flags & SW_HTTP2_FLAG_END_STREAM) { str.append("END_STREAM|"); } if (_flags & SW_HTTP2_FLAG_END_HEADERS) { str.append("END_HEADERS|"); } if (_flags & SW_HTTP2_FLAG_PADDED) { str.append("PADDED|"); } if (_flags & SW_HTTP2_FLAG_PRIORITY) { str.append("PRIORITY|"); } if (str.back() == '|') { return str.substr(0, str.length() - 1); } else { return {"none"}; } } /** +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ */ static sw_inline void set_frame_header(char *buffer, uint8_t type, uint32_t length, uint8_t flags, uint32_t stream_id) { buffer[0] = length >> 16; buffer[1] = length >> 8; buffer[2] = length; buffer[3] = type; buffer[4] = flags; *(uint32_t *) (buffer + 5) = htonl(stream_id); } } // namespace http2 } // namespace swoole ================================================ FILE: include/swoole_iouring.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine.h" #ifdef SW_USE_IOURING #include using swoole::Coroutine; enum swIouringFlag { SW_IOURING_DEFAULT = 0, SW_IOURING_SQPOLL = IORING_SETUP_SQPOLL, }; namespace swoole { struct IouringEvent; struct IouringTimeout { int64_t tv_sec; int64_t tv_nsec; }; class Iouring { uint32_t task_num = 0; uint32_t entries = SW_IOURING_QUEUE_SIZE; io_uring ring; std::queue waiting_tasks; network::Socket *ring_socket = nullptr; Reactor *reactor = nullptr; IouringEvent *ready_events[SW_IOURING_QUEUE_SIZE]; io_uring_cqe *cqes[SW_IOURING_QUEUE_SIZE]; explicit Iouring(Reactor *reactor_); bool ready() const; void yield(IouringEvent *event); void resume(IouringEvent *event); bool cancel(IouringEvent *prev_event); void dispatch(IouringEvent *event); void submit(bool immediately); bool wakeup(); io_uring_sqe *alloc_sqe() { return io_uring_get_sqe(&ring); } static Iouring *get_instance(); static ssize_t execute(IouringEvent *event); static const char *get_opcode_name(enum io_uring_op opcode); public: ~Iouring(); bool is_empty_waiting_tasks() const { return waiting_tasks.empty(); } uint64_t get_task_num() const { return task_num; } uint32_t get_sq_space_left() const { return io_uring_sq_space_left(&ring); } uint32_t get_sq_capacity() const { return ring.sq.ring_entries; } uint32_t get_sq_used() const { return get_sq_capacity() - get_sq_space_left(); } size_t get_waiting_task_num() const { return waiting_tasks.size(); } float get_sq_usage_percent() const { return (float) get_sq_used() / get_sq_capacity() * 100.0f; } static int socket(int domain, int type, int protocol = 0, int flags = 0); static int open(const char *pathname, int flags, mode_t mode); static int connect(int fd, const struct sockaddr *addr, socklen_t len, double timeout = -1); static int accept(int fd, struct sockaddr *addr, socklen_t *len, int flags = 0, double timeout = -1); static int bind(int fd, const struct sockaddr *addr, socklen_t len); static int listen(int fd, int backlog); static int sleep(int tv_sec, int tv_nsec, int flags = 0); static int sleep(double seconds); static ssize_t recv(int fd, void *buf, size_t len, int flags, double timeout = -1); static ssize_t send(int fd, const void *buf, size_t len, int flags, double timeout = -1); static ssize_t recvmsg(int fd, struct msghdr *message, int flags, double timeout = -1); static ssize_t sendmsg(int fd, const struct msghdr *message, int flags, double timeout = -1); static ssize_t sendto( int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t len, double timeout = -1); static ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t size, double timeout = -1); static ssize_t recvfrom(int fd, void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen, double timeout = -1); static ssize_t readv(int fd, const struct iovec *iovec, int count, double timeout = -1); static ssize_t writev(int fd, const struct iovec *iovec, int count, double timeout = -1); static int shutdown(int fd, int how); static int close(int fd); static ssize_t read(int fd, void *buf, size_t size, double timeout = -1); static ssize_t write(int fd, const void *buf, size_t size, double timeout = -1); static int rename(const char *oldpath, const char *newpath); static int mkdir(const char *pathname, mode_t mode); static int unlink(const char *pathname); #ifdef HAVE_IOURING_STATX static int fstat(int fd, struct stat *statbuf); static int stat(const char *path, struct stat *statbuf); #endif static int rmdir(const char *pathname); static int fsync(int fd); static int fdatasync(int fd); static pid_t wait(int *stat_loc, double timeout = -1); static pid_t waitpid(pid_t pid, int *stat_loc, int options, double timeout = -1); /** * Only supports listening to the readable and writable events of a single fd; nfds must be 1. */ static int poll(struct pollfd *fds, nfds_t nfds, int timeout); #ifdef HAVE_IOURING_FUTEX static int futex_wait(uint32_t *futex); static int futex_wakeup(uint32_t *futex); #endif #ifdef HAVE_IOURING_FTRUNCATE static int ftruncate(int fd, off_t length); #endif static std::unordered_map list_all_opcode(); static int callback(Reactor *reactor, Event *event); }; }; // namespace swoole #endif ================================================ FILE: include/swoole_llhttp.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #ifndef SWOOLE_LLHTTP_H #define SWOOLE_LLHTTP_H #include "swoole.h" #include "thirdparty/llhttp/llhttp.h" static sw_inline void swoole_llhttp_parser_init(llhttp_t *parser, llhttp_type_t type, void *ctx) { llhttp_init(parser, type, nullptr); parser->data = ctx; } static sw_inline size_t swoole_llhttp_parser_execute(llhttp_t *parser, const llhttp_settings_t *settings, const char *data, size_t length) { parser->settings = (void *) settings; const llhttp_errno_t result = llhttp_execute(parser, data, length); if (result == HPE_OK) { return length; } const size_t parsed_length = llhttp_get_error_pos(parser) - data; switch (result) { case HPE_PAUSED: llhttp_resume(parser); break; case HPE_PAUSED_UPGRADE: llhttp_resume_after_upgrade(parser); break; default: break; } return parsed_length; } #endif // SWOOLE_LLHTTP_H ================================================ FILE: include/swoole_lock.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_memory.h" #include #include namespace swoole { class Lock { public: enum Type { RW_LOCK = 1, MUTEX = 3, SPIN_LOCK = 5, COROUTINE_LOCK = 6, }; Type get_type() const { return type_; } virtual ~Lock() = default; virtual int lock(int operation = LOCK_EX, int timeout_msec = -1) = 0; virtual int unlock() = 0; protected: Lock(Type type, bool shared) { type_ = type; shared_ = shared; } Type type_; bool shared_; }; struct MutexImpl; class Mutex final : public Lock { MutexImpl *impl; public: explicit Mutex(bool shared); ~Mutex() override; int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; }; #ifdef HAVE_RWLOCK struct RWLockImpl; class RWLock final : public Lock { RWLockImpl *impl; public: explicit RWLock(bool shared); ~RWLock() override; int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; int lock_rd() { return lock(LOCK_SH); } int lock_wr() { return lock(LOCK_EX); } }; #endif #ifdef HAVE_SPINLOCK class SpinLock final : public Lock { pthread_spinlock_t *impl; public: explicit SpinLock(bool shared); ~SpinLock() override; int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; }; #endif class CoroutineLock final : public Lock { long cid = 0; sw_atomic_t *value = nullptr; void *coroutine = nullptr; int lock_impl(bool blocking = true); public: explicit CoroutineLock(bool shared); ~CoroutineLock() override; int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; }; #if defined(HAVE_PTHREAD_BARRIER) && !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) #define SW_USE_PTHREAD_BARRIER #endif struct Barrier { #ifdef SW_USE_PTHREAD_BARRIER pthread_barrier_t barrier_; pthread_barrierattr_t barrier_attr_; bool shared_; #else sw_atomic_t count_; sw_atomic_t barrier_; #endif void init(bool shared, int count); void wait(); void destroy(); }; } // namespace swoole ================================================ FILE: include/swoole_log.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include #include #include #define SW_LOG_BUFFER_SIZE (SW_ERROR_MSG_SIZE + 256) #define SW_LOG_DATE_STRLEN 128 #define SW_LOG_DEFAULT_DATE_FORMAT "%F %T" enum swLogLevel { SW_LOG_DEBUG = 0, SW_LOG_TRACE, SW_LOG_INFO, SW_LOG_NOTICE, SW_LOG_WARNING, SW_LOG_ERROR, SW_LOG_NONE, }; enum swLogRotationType { SW_LOG_ROTATION_SINGLE = 0, SW_LOG_ROTATION_MONTHLY, SW_LOG_ROTATION_DAILY, SW_LOG_ROTATION_HOURLY, SW_LOG_ROTATION_EVERY_MINUTE, }; namespace swoole { class Logger { private: bool opened = false; // Redirect stdin and stdout to log_fd bool redirected = false; bool display_backtrace_ = false; int stdout_fd = -1; int stderr_fd = -1; FILE *log_fp = stdout; int log_level = SW_LOG_INFO; bool date_with_microseconds = false; std::string date_format = SW_LOG_DEFAULT_DATE_FORMAT; std::string log_file; std::string log_real_file; std::mutex lock; int log_rotation = SW_LOG_ROTATION_SINGLE; void reopen_without_lock(); public: bool open(const char *logfile); void reopen(); void set_stream(FILE *stream); /** * Only the `put` and `reopen` functions are thread-safe, * other functions must be used in a single-threaded environment. */ void put(int level, const char *content, size_t length); void close(); void reset(); void set_level(int lv); int get_level() const; bool set_date_format(const char *format); void set_rotation(int rotation); const char *get_real_file(); const char *get_file() const; bool is_opened() const; bool redirect_stdout_and_stderr(bool enable); void set_date_with_microseconds(bool enable); std::string gen_real_file(const std::string &file) const; static std::string get_pretty_name(const std::string &prettyFunction, bool strip = true); void display_backtrace() { display_backtrace_ = true; } }; } // namespace swoole swoole::Logger *sw_logger(); #define __SW_FUNC__ (swoole::Logger::get_pretty_name(__PRETTY_FUNCTION__).c_str()) #define swoole_info(str, ...) \ if (SW_LOG_INFO >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, str, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_INFO, sw_error, _sw_error_len); \ } #define swoole_notice(str, ...) \ if (SW_LOG_NOTICE >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, str, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_NOTICE, sw_error, _sw_error_len); \ } #define swoole_sys_notice(str, ...) \ do { \ swoole_set_last_error(errno); \ if (SW_LOG_ERROR >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, \ SW_ERROR_MSG_SIZE, \ "%s(:%d): " str ", Error: %s[%d]", \ __SW_FUNC__, \ __LINE__, \ ##__VA_ARGS__, \ swoole_strerror(errno), \ errno); \ sw_logger()->put(SW_LOG_NOTICE, sw_error, _sw_error_len); \ } \ } while (0) #define swoole_warning(str, ...) \ do { \ if (SW_LOG_WARNING >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(): " str, __SW_FUNC__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_WARNING, sw_error, _sw_error_len); \ } \ } while (0) #define swoole_sys_warning(str, ...) \ do { \ swoole_set_last_error(errno); \ if (SW_LOG_ERROR >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, \ SW_ERROR_MSG_SIZE, \ "%s(): " str ", Error: %s[%d]", \ __SW_FUNC__, \ ##__VA_ARGS__, \ swoole_strerror(errno), \ errno); \ sw_logger()->put(SW_LOG_WARNING, sw_error, _sw_error_len); \ } \ } while (0) #define swoole_error(str, ...) \ do { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(): " str, __SW_FUNC__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_ERROR, sw_error, _sw_error_len); \ swoole_exit(1); \ } while (0) #define swoole_sys_error(str, ...) \ do { \ size_t _sw_error_len = sw_snprintf(sw_error, \ SW_ERROR_MSG_SIZE, \ "%s(): " str ", Error: %s[%d]", \ __SW_FUNC__, \ ##__VA_ARGS__, \ swoole_strerror(errno), \ errno); \ sw_logger()->put(SW_LOG_ERROR, sw_error, _sw_error_len); \ swoole_exit(1); \ } while (0) #define swoole_fatal_error(code, str, ...) SwooleG.fatal_error(code, str, ##__VA_ARGS__) #define swoole_error_log(level, error, str, ...) \ do { \ swoole_set_last_error(error); \ if (level >= swoole_get_log_level() && !swoole_is_ignored_error(error)) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s() (ERRNO %d): " str, __SW_FUNC__, error, ##__VA_ARGS__); \ sw_logger()->put(level, sw_error, _sw_error_len); \ } \ } while (0) #ifdef SW_DEBUG #define swoole_debug(str, ...) \ if (SW_LOG_DEBUG >= swoole_get_log_level()) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(:%d): " str, __SW_FUNC__, __LINE__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_DEBUG, sw_error, _sw_error_len); \ } #define swoole_hex_dump(data, length) \ do { \ const char *__data = (data); \ size_t __length = (length); \ swoole_debug("+----------+------------+-----------+-----------+------------+------------------+"); \ for (size_t of = 0; of < __length; of += 16) { \ char hex[16 * 3 + 1]; \ char str[16 + 1]; \ size_t i, hof = 0, sof = 0; \ for (i = of; i < of + 16 && i < __length; i++) { \ hof += sprintf(hex + hof, "%02x ", (__data)[i] & 0xff); \ sof += sprintf(str + sof, "%c", isprint((int) (__data)[i]) ? (__data)[i] : '.'); \ } \ swoole_debug("| %08zx | %-48s| %-16s |", of, hex, str); \ } \ swoole_debug("+----------+------------+-----------+-----------+------------+------------------+"); \ } while (0) #else #define swoole_debug(str, ...) #define swoole_hex_dump(data, length) #endif enum swTraceWhat : long { /** * Server */ SW_TRACE_SERVER = 1u << 1, SW_TRACE_CLIENT = 1u << 2, SW_TRACE_BUFFER = 1u << 3, SW_TRACE_CONN = 1u << 4, SW_TRACE_EVENT = 1u << 5, SW_TRACE_WORKER = 1u << 6, SW_TRACE_MEMORY = 1u << 7, SW_TRACE_REACTOR = 1u << 8, SW_TRACE_PHP = 1u << 9, SW_TRACE_HTTP = 1u << 10, SW_TRACE_HTTP2 = 1u << 11, SW_TRACE_EOF_PROTOCOL = 1u << 12, SW_TRACE_LENGTH_PROTOCOL = 1u << 13, SW_TRACE_CLOSE = 1u << 14, SW_TRACE_WEBSOCKET = 1u << 15, /** * Client */ SW_TRACE_REDIS_CLIENT = 1u << 16, SW_TRACE_MYSQL_CLIENT = 1u << 17, SW_TRACE_HTTP_CLIENT = 1u << 18, SW_TRACE_AIO = 1u << 19, SW_TRACE_SSL = 1u << 20, SW_TRACE_NORMAL = 1u << 21, /** * Coroutine */ SW_TRACE_CHANNEL = 1u << 22, SW_TRACE_TIMER = 1u << 23, SW_TRACE_SOCKET = 1u << 24, SW_TRACE_COROUTINE = 1u << 25, SW_TRACE_CONTEXT = 1u << 26, SW_TRACE_CO_HTTP_SERVER = 1u << 27, SW_TRACE_TABLE = 1u << 28, SW_TRACE_CO_CURL = 1u << 29, SW_TRACE_CARES = 1u << 30, SW_TRACE_ZLIB = 1u << 31, SW_TRACE_CO_PGSQL = 1ul << 32, SW_TRACE_CO_ODBC = 1ul << 33, SW_TRACE_CO_ORACLE = 1ul << 34, SW_TRACE_CO_SQLITE = 1ul << 35, SW_TRACE_CO_FIREBIRD = 1ul << 36, SW_TRACE_CO_SSH2 = 1ul << 37, /** * Thread */ SW_TRACE_THREAD = 1ul << 40, SW_TRACE_ALL = 0x7fffffffffffffff }; #ifdef SW_LOG_TRACE_OPEN #define swoole_trace_log(what, str, ...) \ if (SW_LOG_TRACE >= swoole_get_log_level() && (what & SwooleG.trace_flags)) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(:%d): " str, __SW_FUNC__, __LINE__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_TRACE, sw_error, _sw_error_len); \ } #else #define swoole_trace_log(what, str, ...) #endif #define swoole_trace(str, ...) swoole_trace_log(SW_TRACE_NORMAL, str, ##__VA_ARGS__) ================================================ FILE: include/swoole_lru_cache.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include #include #include #include namespace swoole { /** * This cache isn't thread safe */ template class LRUCache { typedef std::pair> cache_node_t; typedef std::list> cache_list_t; std::unordered_map cache_map; cache_list_t cache_list; size_t cache_capacity; public: explicit LRUCache(size_t capacity) { cache_capacity = capacity; } std::shared_ptr get(const std::string &key) { auto iter = cache_map.find(key); if (iter == cache_map.end()) { return nullptr; } if (iter->second->second.first < ::time(nullptr) && iter->second->second.first > 0) { return nullptr; } cache_list.splice(cache_list.begin(), cache_list, iter->second); return iter->second->second.second; // iter -> list::iter -> cache_node_t -> value } void set(const std::string &key, const std::shared_ptr &val, time_t expire = 0) { time_t expire_time; if (expire <= 0) { expire_time = 0; } else { expire_time = ::time(nullptr) + expire; } auto iter = cache_map.find(key); if (iter != cache_map.end()) { iter->second->second.first = expire_time; iter->second->second.second = val; cache_list.splice(cache_list.begin(), cache_list, iter->second); return; } size_t size = cache_list.size(); if (size == cache_capacity && size > 0) { auto del = cache_list.back(); cache_map.erase(del.first); cache_list.pop_back(); } cache_list.emplace_front(key, cache_node_t{expire_time, val}); cache_map[key] = cache_list.begin(); } void del(const std::string &key) { auto iter = cache_map.find(key); if (iter == cache_map.end()) { return; } cache_list.erase(iter->second); cache_map.erase(iter); } void clear() { cache_list.clear(); cache_map.clear(); } }; } // namespace swoole ================================================ FILE: include/swoole_memory.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" //-------------------memory manager------------------------- namespace swoole { class MemoryPool { public: virtual ~MemoryPool() = default; virtual void *alloc(uint32_t size) = 0; virtual void free(void *ptr) = 0; protected: MemoryPool(){}; }; struct FixedPoolImpl; class FixedPool final : public MemoryPool { FixedPoolImpl *impl; public: FixedPool(uint32_t slice_num, uint32_t slice_size, bool shared); FixedPool(uint32_t slice_size, void *memory, size_t size, bool shared); ~FixedPool() override; void *alloc(uint32_t size) override; void free(void *ptr) override; void debug(int max_lines = 100) const; uint32_t get_number_of_spare_slice() const; uint32_t get_number_of_total_slice() const; uint32_t get_slice_size() const; static size_t sizeof_struct_slice(); static size_t sizeof_struct_impl(); }; struct RingBufferImpl; // RingBuffer, In order for malloc / free class RingBuffer : public MemoryPool { RingBufferImpl *impl; public: RingBuffer(uint32_t size, bool shared); ~RingBuffer() override; void *alloc(uint32_t size) override; void free(void *ptr) override; }; struct GlobalMemoryImpl; // Global memory, the program life cycle only malloc / free one time class GlobalMemory : public MemoryPool { private: GlobalMemoryImpl *impl; public: GlobalMemory(uint32_t page_size, bool shared); ~GlobalMemory() override; void *alloc(uint32_t size) override; void free(void *ptr) override; size_t capacity() const; size_t get_memory_size() const; }; } // namespace swoole void *sw_shm_malloc(size_t size); void sw_shm_free(void *ptr); void *sw_shm_calloc(size_t num, size_t _size); int sw_shm_protect(void *ptr, int flags); void *sw_shm_realloc(void *ptr, size_t new_size); ================================================ FILE: include/swoole_message_bus.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" #include namespace swoole { struct PipeBuffer { DataHead info; char data[0]; bool is_begin() const { return info.flags & SW_EVENT_DATA_BEGIN; } bool is_chunked() const { return info.flags & SW_EVENT_DATA_CHUNK; } bool is_end() const { return info.flags & SW_EVENT_DATA_END; } }; struct PacketPtr { size_t length; char *data; }; struct DgramPacket { SocketType socket_type; network::Address socket_addr; uint32_t length; char data[0]; }; struct PacketTask { size_t length; char tmpfile[SW_TASK_TMP_PATH_SIZE]; }; class MessageBus { private: const Allocator *allocator_; std::unordered_map> packet_pool_; std::vector pipe_sockets_; std::function id_generator_; size_t buffer_size_; PipeBuffer *buffer_ = nullptr; bool always_chunked_transfer_ = false; String *get_packet_buffer(); ReturnCode prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer); public: MessageBus() { allocator_ = sw_std_allocator(); buffer_size_ = SW_BUFFER_SIZE_STD; } ~MessageBus(); bool empty() const { return packet_pool_.empty(); } size_t count() const { return packet_pool_.size(); } void clear() { packet_pool_.clear(); } void set_allocator(const Allocator *allocator) { allocator_ = allocator; } void set_id_generator(const std::function &id_generator) { id_generator_ = id_generator; } void set_buffer_size(size_t buffer_size) { buffer_size_ = buffer_size; } void set_always_chunked_transfer() { always_chunked_transfer_ = true; } size_t get_buffer_size() const { return buffer_size_; } size_t get_memory_size() const; bool alloc_buffer(); /** * If use the zend_string_allocator, must manually call this function to release the memory, * otherwise coredump will occur when php shutdown, because zend_string has been released */ void free_buffer() { allocator_->free(buffer_); buffer_ = nullptr; } void pass(const SendData *task) const; /** * Send data to socket. If the data sent is larger than Server::ipc_max_size, then it is sent in chunks. * Otherwise, send it directly. * When sending data in multi-thread environment, must use get_pipe_socket() to separate socket memory. * @return: send success returns true, send failure returns false. */ bool write(network::Socket *sock, SendData *packet) const; /** * Receive data from socket, if only one chunk is received, packet will be saved in packet_pool. * Then continue to listen to readable events, waiting for more chunks. * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred */ ssize_t read(network::Socket *sock); /** * Receive data from pipeline, and store data to buffer * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred */ ssize_t read_with_buffer(network::Socket *sock); /** * The last chunk of data has been received, return address and length, start processing this packet. */ PacketPtr get_packet() const; PipeBuffer *get_buffer() const { return buffer_; } /** * Pop the data memory address to the outer layer, no longer managed by MessageBus */ char *move_packet(); /** * The processing of this data packet has been completed, and the relevant memory has been released */ void pop() { if (buffer_->is_end()) { packet_pool_.erase(buffer_->info.msg_id); } } /** * It is possible to operate the same pipe in multiple threads. * Each thread must have a unique buffer and the socket memory must be separated. */ network::Socket *get_pipe_socket(const network::Socket *sock) const { return pipe_sockets_[sock->get_fd()]; } void init_pipe_socket(const network::Socket *sock); }; } // namespace swoole ================================================ FILE: include/swoole_mime_type.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include namespace swoole { namespace mime_type { const std::unordered_map &list(); bool add(const std::string &suffix, const std::string &mime_type); void set(const std::string &suffix, const std::string &mime_type); bool del(const std::string &suffix); const std::string &get(const std::string &filename); bool exists(const std::string &filename); } // namespace mime_type } // namespace swoole ================================================ FILE: include/swoole_mqtt.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_protocol.h" #define SW_MQTT_MIN_LENGTH_SIZE 1 #define SW_MQTT_MAX_LENGTH_SIZE 4 #define SW_MQTT_MAX_PAYLOAD_SIZE 268435455 #define SW_MQTT_SETRETAIN(HDR, R) (HDR | (R)) #define SW_MQTT_SETQOS(HDR, Q) (HDR | ((Q) << 1)) #define SW_MQTT_SETDUP(HDR, D) (HDR | ((D) << 3)) namespace swoole { namespace mqtt { enum Opcode { SW_MQTT_CONNECT = 0x10, SW_MQTT_CONNACK = 0x20, SW_MQTT_PUBLISH = 0x30, SW_MQTT_PUBACK = 0x40, SW_MQTT_PUBREC = 0x50, SW_MQTT_PUBREL = 0x60, SW_MQTT_PUBCOMP = 0x70, SW_MQTT_SUBSCRIBE = 0x80, SW_MQTT_SUBACK = 0x90, SW_MQTT_UNSUBSCRIBE = 0xA0, SW_MQTT_UNSUBACK = 0xB0, SW_MQTT_PINGREQ = 0xC0, SW_MQTT_PINGRESP = 0xD0, SW_MQTT_DISCONNECT = 0xE0, }; struct Packet { uint8_t type : 4; uint8_t dup : 1; uint8_t qos : 2; uint8_t retain : 1; uint32_t length; char protocol_name[8]; }; ssize_t get_package_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); void set_protocol(Protocol *protocol); } // namespace mqtt } // namespace swoole ================================================ FILE: include/swoole_msg_queue.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #include namespace swoole { enum { SW_MSGQUEUE_ORIENT = 1, SW_MSGQUEUE_BALANCE = 2, }; struct QueueNode { long mtype; /* type of received/sent message */ char mdata[sizeof(EventData)]; /* text of the message */ }; class MsgQueue { private: bool blocking_; int msg_id_; key_t msg_key_; int flags_; int perms_; public: explicit MsgQueue(key_t msg_key, bool blocking = true, int perms = 0); ~MsgQueue(); bool ready() const { return msg_id_ >= 0; } int get_id() const { return msg_id_; } void set_blocking(bool blocking); bool set_capacity(size_t queue_bytes) const; bool push(const QueueNode *in, size_t mdata_length) const; ssize_t pop(QueueNode *out, size_t mdata_size) const; bool stat(size_t *queue_num, size_t *queue_bytes) const; bool destroy(); }; } // namespace swoole ================================================ FILE: include/swoole_pipe.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_socket.h" enum swPipe_close_which { SW_PIPE_CLOSE_MASTER = 1, SW_PIPE_CLOSE_WORKER = 2, SW_PIPE_CLOSE_READ = 3, SW_PIPE_CLOSE_WRITE = 4, SW_PIPE_CLOSE_BOTH = 0, }; namespace swoole { class SocketPair { protected: bool blocking; double timeout; /** * master : socks[1], for write operation * worker : socks[0], for read operation */ int socks[2]{}; network::Socket *master_socket = nullptr; network::Socket *worker_socket = nullptr; void init_socket(int master_fd, int worker_fd); public: explicit SocketPair(bool _blocking) { blocking = _blocking; timeout = network::Socket::default_read_timeout; } ~SocketPair(); ssize_t read(void *_buf, size_t length) const; ssize_t write(const void *_buf, size_t length) const; void clean() const; bool close(int which = 0); network::Socket *get_socket(bool _master) const { return _master ? master_socket : worker_socket; } bool ready() const { return master_socket != nullptr && worker_socket != nullptr; } void set_timeout(double _timeout) { timeout = _timeout; master_socket->set_timeout(timeout); worker_socket->set_timeout(timeout); } void set_blocking(bool _blocking); }; class Pipe : public SocketPair { public: explicit Pipe(bool blocking); }; class UnixSocket : public SocketPair { int protocol_; public: UnixSocket(bool blocking, int _protocol); bool set_buffer_size(size_t _size) const; }; } // namespace swoole ================================================ FILE: include/swoole_process_pool.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_signal.h" #include "swoole_lock.h" #include "swoole_pipe.h" #include "swoole_channel.h" #include "swoole_msg_queue.h" #include "swoole_message_bus.h" #include #include #include #include #include enum swWorkerStatus { SW_WORKER_BUSY = 1, SW_WORKER_IDLE = 2, SW_WORKER_EXIT = 3, }; enum swWorkerType { SW_MASTER = 1, SW_WORKER = 2, SW_MANAGER = 3, SW_EVENT_WORKER = 2, SW_TASK_WORKER = 4, SW_USER_WORKER = 5, }; enum swIPCMode { SW_IPC_NONE = 0, SW_IPC_UNIXSOCK = 1, SW_IPC_MSGQUEUE = 2, SW_IPC_SOCKET = 3, }; SW_API swoole::WorkerId swoole_get_worker_id(); SW_API pid_t swoole_get_worker_pid(); SW_API int swoole_get_worker_type(); SW_API void swoole_set_worker_pid(pid_t pid); SW_API void swoole_set_worker_id(swoole::WorkerId worker_id); SW_API void swoole_set_worker_type(int type); SW_API char swoole_get_worker_symbol(); namespace swoole { enum WorkerMessageType { SW_WORKER_MESSAGE_STOP = 1, }; enum ProtocolType { SW_PROTOCOL_TASK = 1, SW_PROTOCOL_STREAM, SW_PROTOCOL_MESSAGE, }; struct WorkerStopMessage { pid_t pid; uint16_t worker_id; }; class ExitStatus { pid_t pid_; int status_; public: ExitStatus(pid_t _pid, int _status) : pid_(_pid), status_(_status) {} pid_t get_pid() const { return pid_; } int get_status() const { return status_; } int get_code() const { return WEXITSTATUS(status_); } int get_signal() const { return WTERMSIG(status_); } bool is_normal_exit() const { return WIFEXITED(status_); } }; static inline ExitStatus wait_process() { int status = 0; pid_t pid = ::wait(&status); return {pid, status}; } static inline ExitStatus wait_process(pid_t _pid, int options) { int status = 0; pid_t pid = ::waitpid(_pid, &status, options); return {pid, status}; } struct ProcessPool; struct Worker; struct WorkerGlobal { WorkerId id; uint8_t type; pid_t pid; bool shutdown; bool running; uint32_t max_request; /** * worker is shared memory, visible in other work processes. * When a worker process restarts, it may be held by both the old and new processes simultaneously, * necessitating careful handling of the state. */ Worker *worker; /** * worker_copy is a copy of worker, * but it must be local memory and only used within the current process or thread. * It is not visible to other worker processes. */ Worker *worker_copy; time_t exit_time; }; struct Worker { pid_t pid; WorkerId id; ProcessPool *pool; MsgQueue *queue; bool shared; bool redirect_stdout; bool redirect_stdin; bool redirect_stderr; /** * worker status, IDLE or BUSY */ uint8_t status; uint8_t type; uint8_t msgqueue_mode; uint8_t child_process; uint32_t concurrency; time_t start_time; sw_atomic_long_t dispatch_count; sw_atomic_long_t request_count; sw_atomic_long_t response_count; size_t coroutine_num; Mutex *lock; UnixSocket *pipe_object; network::Socket *pipe_master; network::Socket *pipe_worker; network::Socket *pipe_current; void *ptr; ssize_t send_pipe_message(const void *buf, size_t n, int flags) const; bool has_exceeded_max_request() const; void set_max_request(uint32_t max_request, uint32_t max_request_grace); void report_error(const ExitStatus &exit_status) const; /** * Init global state for worker process. * Must be called after the process is spawned and before the main loop is executed. */ void init(); void shutdown(); bool is_shutdown(); static bool is_running(); void set_status(swWorkerStatus _status) { status = _status; } void set_status_to_idle() { set_status(SW_WORKER_IDLE); } void set_status_to_busy() { set_status(SW_WORKER_BUSY); } void add_request_count() { request_count++; } bool is_busy() const { return status == SW_WORKER_BUSY; } bool is_idle() const { return status == SW_WORKER_IDLE; } }; struct StreamInfo { network::Socket *socket; network::Socket *last_connection; char *socket_file; int socket_port; String *response_buffer; }; struct ReloadTask { std::unordered_map workers; std::queue kill_queue; TimerNode *timer = nullptr; size_t count() const { return workers.size(); } bool is_completed() const { return workers.empty(); } bool exists(pid_t pid) { return workers.find(pid) != workers.end(); } ReloadTask() = default; ~ReloadTask(); void kill_one(int signal_number = SIGTERM); void kill_all(int signal_number = SIGKILL); void add_workers(Worker *list, size_t n); void add_timeout_killer(int timeout); bool remove(pid_t pid); void clear_queue(); }; struct ProcessPool { bool running; bool reload_init; bool read_message; bool started; bool schedule_by_sysvmsg; bool async; uint8_t ipc_mode; ProtocolType protocol_type_; pid_t master_pid; uint32_t max_wait_time; uint64_t reload_count; time_t reload_last_time; /** * process type */ uint8_t type; /** * worker->id = start_id + i */ uint16_t start_id; /** * use message queue IPC */ uint8_t use_msgqueue; /** * use stream socket IPC */ uint8_t use_socket; char *packet_buffer; uint32_t max_packet_size_; /** * message queue key */ key_t msgqueue_key; uint32_t worker_num; uint32_t max_request; uint32_t max_request_grace; /** * No idle task work process is available. */ uint8_t scheduler_warning; time_t warning_time; void (*onStart)(ProcessPool *pool); void (*onShutdown)(ProcessPool *pool); void (*onBeforeReload)(ProcessPool *pool); void (*onAfterReload)(ProcessPool *pool); int (*onTask)(ProcessPool *pool, Worker *worker, EventData *task); void (*onWorkerStart)(ProcessPool *pool, Worker *worker); void (*onMessage)(ProcessPool *pool, RecvData *msg); void (*onWorkerExit)(ProcessPool *pool, Worker *worker); void (*onWorkerStop)(ProcessPool *pool, Worker *worker); void (*onWorkerError)(ProcessPool *pool, Worker *worker, const ExitStatus &exit_status); void (*onWorkerMessage)(ProcessPool *pool, EventData *msg); int (*onWorkerNotFound)(ProcessPool *pool, const ExitStatus &exit_status); int (*main_loop)(ProcessPool *pool, Worker *worker); sw_atomic_t round_id; Worker *workers; std::vector> *pipes; std::unordered_map *map_; MsgQueue *queue; StreamInfo *stream_info_; Channel *message_box = nullptr; MessageBus *message_bus = nullptr; ReloadTask *reload_task = nullptr; void *ptr; Worker *get_worker(WorkerId worker_id) const { return &(workers[worker_id - start_id]); } static TaskId get_task_id(const EventData *task) { return task->info.fd; } static WorkerId get_task_src_worker_id(const EventData *task) { return task->info.reactor_id; } void set_max_packet_size(uint32_t _max_packet_size) { max_packet_size_ = _max_packet_size; } bool is_master() const { return swoole_get_worker_type() == SW_MASTER; } bool is_worker() const { return swoole_get_worker_type() == SW_WORKER; } /** * SW_PROTOCOL_TASK * ================================================================== * The `EventData` structure must be sent as a single message and cannot be split into multiple transmissions. * If the length of the message content exceeds the size limit of the data field in EventData, * it should be written to a temporary file. * In this case, set the SW_TASK_TMPFILE flag in info.ext_flags. * Only the path to the temporary file will be transmitted, * and the receiving end should retrieve the actual message content from this temporary file. * Reference: Server::task_pack() * * SW_PROTOCOL_MESSAGE * ================================================================== * When sending the `EventData` structure, the message can be split into multiple transmissions. * When sending data in multiple parts, you must set a unique `info.msg_id`. * For the first slice, set the `info.flags` with the SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN flag, * and for the last slice, set the `info.flags` with the SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_END flag. * The receiving end will place the data into a memory cache table, merge the data, * and only execute the onMessage callback once the complete message has been received. * * Reference: MessageBus::write() and MessageBus::read() * * SW_PROTOCOL_STREAM * ================================================================== * +-------------------------------+-------------------------------+ * | Payload Length ( 4 byte, network byte order) | * | Payload Data ... ( Payload Length byte ) | * +-------------------------------- - - - - - - - - - - - - - - - + * * The packet consists of a 4 byte length header followed by the data payload. * The receiving end should first use `socket.recv(&payload_len, 4)` to obtain the length of the data payload. * Then, execute `socket.recv(payload, payload_len)` to receive the complete data. * Please note that sufficient memory space must be allocated for the payload, * for example, `payload = malloc(payload_len)`. */ void set_protocol(ProtocolType _protocol_type); void set_type(int _type); void set_start_id(int _start_id); void set_max_request(uint32_t _max_request, uint32_t _max_request_grace); bool detach(); int wait(); int start_check(); int start(); bool shutdown(); bool reload(); void reopen_logger(); void trigger_read_message_event() { read_message = true; } pid_t spawn(Worker *worker); void stop(Worker *worker); void kill_all_workers(int signo = SIGKILL); swResultCode dispatch(EventData *data, int *worker_id); int response(const char *data, uint32_t length) const; swResultCode dispatch_sync(EventData *data, int *dst_worker_id); swResultCode dispatch_sync(const char *data, uint32_t len) const; void add_worker(Worker *worker) const; bool del_worker(const Worker *worker) const; Worker *get_worker_by_pid(pid_t pid) const; void destroy(); int create(uint32_t worker_num, key_t msgqueue_key = 0, swIPCMode ipc_mode = SW_IPC_NONE); int create_message_box(size_t memory_size); int create_message_bus(); int push_message(uint8_t _type, const void *data, size_t length) const; int push_message(const EventData *msg) const; bool send_message(WorkerId worker_id, const char *message, size_t l_message) const; int pop_message(void *data, size_t size) const; int listen(const char *socket_file, int backlog) const; int listen(const char *host, int port, int backlog) const; int schedule(); bool is_worker_running(Worker *worker) const; private: static int recv_packet(Reactor *reactor, Event *event); static int recv_message(Reactor *reactor, Event *event); static int run_with_task_protocol(ProcessPool *pool, Worker *worker); static int run_with_stream_protocol(ProcessPool *pool, Worker *worker); static int run_with_message_protocol(ProcessPool *pool, Worker *worker); static int run_async(ProcessPool *pool, Worker *worker); void at_worker_enter(Worker *worker) const; void at_worker_exit(Worker *worker); bool wait_detached_worker(std::unordered_set &detached_workers, pid_t pid); }; }; // namespace swoole static sw_inline int swoole_kill(pid_t _pid, int _sig) { return kill(_pid, _sig); } typedef swoole::ProtocolType swProtocolType; extern SW_THREAD_LOCAL swoole::WorkerGlobal SwooleWG; static inline swoole::Worker *sw_worker() { return SwooleWG.worker; } ================================================ FILE: include/swoole_protocol.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_socket.h" #include namespace swoole { struct SendData { DataHead info; const char *data; }; struct RecvData { DataHead info; const char *data; SessionId session_id() const { return info.fd; } uint32_t length() const { return info.len; } }; struct PacketLength { const char *buf; uint32_t buf_size; uint32_t header_len; }; struct WebSocketSettings { std::string protocol; // with `Sec-WebSocket-Protocol` HTTP Header bool in_server; // server or client bool mask = true; // enable websocket mask bool open_ping_frame = false; // handle websocket ping frame by user bool open_pong_frame = false; // handle websocket pong frame by user bool open_close_frame = false; // handle websocket close frame by user /** * The default value is false, which means that websocket frame data compression is not enabled. * If supported by `zlib` or other compression libraries, the client can accept compressed data * (depending on whether the `Sec-Websocket-Extensions` header contains `permessage-deflate`) */ bool compression = false; }; struct Protocol { typedef ssize_t (*LengthFunc)(const Protocol *, network::Socket *, PacketLength *pl); /* one package: eof check */ bool split_by_eof; char package_eof[SW_DATA_EOF_MAXLEN]; uint8_t package_eof_len; char package_length_type; uint8_t package_length_size; uint16_t package_length_offset; uint16_t package_body_offset; uint32_t package_max_length; void *private_data_1; void *private_data_2; /** * callback this function when a complete data packet is received */ int (*onPackage)(const Protocol *, network::Socket *, const RecvData *); /** * parse the length value in the received data * @return 0: more data needs to be received * @return -1: abnormal value, connection should be closed * @return >0: the length of the data packet */ LengthFunc get_package_length; uint8_t (*get_package_length_size)(network::Socket *); int recv_with_eof_protocol(network::Socket *socket, String *buffer) const; int recv_with_length_protocol(network::Socket *socket, String *buffer) const; int recv_split_by_eof(network::Socket *socket, String *buffer) const; static ssize_t default_length_func(const Protocol *protocol, network::Socket *socket, PacketLength *pl); }; } // namespace swoole #ifdef __BYTE_ORDER #define SW_BYTE_ORDER __BYTE_ORDER #elif defined(_BYTE_ORDER) #define SW_BYTE_ORDER _BYTE_ORDER #elif defined(BYTE_ORDER) #define SW_BYTE_ORDER BYTE_ORDER #else #error "Unable to determine machine byte order" #endif #ifdef __LITTLE_ENDIAN #define SW_LITTLE_ENDIAN __LITTLE_ENDIAN #elif defined(_LITTLE_ENDIAN) #define SW_LITTLE_ENDIAN _LITTLE_ENDIAN #elif defined(LITTLE_ENDIAN) #define SW_LITTLE_ENDIAN LITTLE_ENDIAN #else #error "No LITTLE_ENDIAN macro" #endif static sw_inline uint16_t swoole_swap_endian16(uint16_t x) { return (((x & 0xff) << 8) | ((x & 0xff00) >> 8)); } static sw_inline uint32_t swoole_swap_endian32(uint32_t x) { return (((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24)); } static sw_inline uint64_t swoole_swap_endian64(uint64_t x) { return (((x & 0xff) << 56) | ((x & 0xff00) << 40) | ((x & 0xff0000) << 24) | ((x & 0xff000000) << 8) | ((x & 0xff00000000) >> 8) | ((x & 0xff0000000000) >> 24) | ((x & 0xff000000000000) >> 40) | ((x & 0xff00000000000000) >> 56)); } int64_t swoole_unpack(char type, const void *data); uint64_t swoole_hton64(uint64_t value); uint64_t swoole_ntoh64(uint64_t value); void swoole_dump_ascii(const char *data, size_t size); void swoole_dump_bin(const uchar *data, char type, size_t size); void swoole_dump_hex(const uchar *data, size_t outlen); char *swoole_dec2hex(ulong_t value, int base); ulong_t swoole_hex2dec(const char *hex, size_t *parsed_bytes); int swoole_type_size(char type); ================================================ FILE: include/swoole_proxy.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include #include #include #define SW_SOCKS5_VERSION_CODE 0x05 #define SW_HTTP_PROXY_CHECK_MESSAGE 0 #define SW_HTTP_PROXY_HANDSHAKE_RESPONSE "HTTP/1.1 200 Connection established\r\n" #define SW_HTTP_PROXY_FMT \ "CONNECT %.*s:%d HTTP/1.1\r\n" \ "Host: %.*s:%d\r\n" \ "User-Agent: Swoole/" SWOOLE_VERSION "\r\n" \ "Proxy-Connection: Keep-Alive\r\n" enum swHttpProxyState { SW_HTTP_PROXY_STATE_WAIT = 0, SW_HTTP_PROXY_STATE_HANDSHAKE, SW_HTTP_PROXY_STATE_READY, }; enum swSocks5State { SW_SOCKS5_STATE_WAIT = 0, SW_SOCKS5_STATE_HANDSHAKE, SW_SOCKS5_STATE_AUTH, SW_SOCKS5_STATE_CONNECT, SW_SOCKS5_STATE_READY, }; enum swSocks5Method { SW_SOCKS5_METHOD_NO_AUTH = 0x00, SW_SOCKS5_METHOD_AUTH = 0x02, }; namespace swoole { class String; struct HttpProxy { uint8_t state; uint8_t dont_handshake; int port; std::string host; std::string username; std::string password; std::string target_host; int target_port; std::string get_auth_str() const; size_t pack(const String *send_buffer, const std::string &host_name) const; static bool handshake(const String *recv_buffer); static HttpProxy *create(const std::string &host, int port, const std::string &user, const std::string &pwd); }; struct Socks5Proxy { std::string host; int port; uint8_t state; uint8_t version; uint8_t method; uint8_t dns_tunnel; std::string username; std::string password; std::string target_host; int target_port; int socket_type; char buf[512]; ssize_t pack_negotiate_request(); ssize_t pack_auth_request(); ssize_t pack_connect_request(); bool handshake(const char *rbuf, size_t rlen, const std::function &send_fn); static const char *strerror(int code); static Socks5Proxy *create( int socket_type, const std::string &host, int port, const std::string &user, const std::string &pwd); }; } // namespace swoole ================================================ FILE: include/swoole_reactor.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_socket.h" #include #include #include namespace swoole { struct DeferCallback { Callback callback; void *data; }; class Reactor; class ReactorImpl { protected: Reactor *reactor_; public: explicit ReactorImpl(Reactor *_reactor) { reactor_ = _reactor; } void after_removal_failure(const network::Socket *_socket) const; virtual ~ReactorImpl() = default; virtual bool ready() = 0; virtual int add(network::Socket *socket, int events) = 0; virtual int set(network::Socket *socket, int events) = 0; virtual int del(network::Socket *socket) = 0; virtual int wait() = 0; }; class CallbackManager { public: typedef std::list> TaskList; void append(const Callback &fn, void *private_data) { list_.emplace_back(fn, private_data); } void prepend(const Callback &fn, void *private_data) { list_.emplace_front(fn, private_data); auto t = list_.back(); } void remove(const TaskList::iterator iter) { list_.erase(iter); } void execute() { while (!list_.empty()) { std::pair task = list_.front(); list_.pop_front(); task.first(task.second); } } protected: TaskList list_; }; class Reactor { public: enum Type { TYPE_AUTO, TYPE_EPOLL, TYPE_KQUEUE, TYPE_POLL, }; enum EndCallback { PRIORITY_TIMER = 0, PRIORITY_DEFER_TASK, PRIORITY_IDLE_TASK, PRIORITY_SIGNAL_CALLBACK, PRIORITY_TRY_EXIT, PRIORITY_MALLOC_TRIM, PRIORITY_WORKER_CALLBACK, /** * PRIORITY_IOURING_SUBMIT must be the last one, as other callback functions might allocate new SQEs. * It is essential to ensure that the SQE is submitted before the next event loop iteration and before the * epoll_wait() call. */ PRIORITY_IOURING_SUBMIT, }; enum ExitCondition { EXIT_CONDITION_TIMER = 0, EXIT_CONDITION_DEFER_TASK, EXIT_CONDITION_WAIT_PID, EXIT_CONDITION_CO_SIGNAL_LISTENER, EXIT_CONDITION_SIGNAL_LISTENER, EXIT_CONDITION_AIO_TASK, EXIT_CONDITION_SIGNALFD, EXIT_CONDITION_USER_BEFORE_DEFAULT, EXIT_CONDITION_FORCED_TERMINATION, EXIT_CONDITION_IOURING, EXIT_CONDITION_DEFAULT = 999, EXIT_CONDITION_USER_AFTER_DEFAULT, }; Type type_; void *ptr = nullptr; int native_handle = -1; uint32_t max_event_num = 0; bool ready_ = false; bool running = false; bool start = false; bool once = false; bool wait_exit = false; bool destroyed = false; bool bailout = false; bool timed_out = false; /** * reactor->wait timeout (millisecond) or -1 */ int32_t timeout_msec = 0; uint16_t id = 0; uint32_t max_socket = 0; #ifdef SW_USE_MALLOC_TRIM time_t last_malloc_trim_time = 0; #endif ReactorHandler read_handler[SW_MAX_FDTYPE] = {}; ReactorHandler write_handler[SW_MAX_FDTYPE] = {}; ReactorHandler error_handler[SW_MAX_FDTYPE] = {}; ReactorHandler default_write_handler = nullptr; ReactorHandler default_error_handler = nullptr; int add(network::Socket *socket, int events) const { return impl->add(socket, events); } int set(network::Socket *socket, int events) const { return impl->set(socket, events); } int del(network::Socket *socket) const { return impl->del(socket); } int wait() const { return impl->wait(); } CallbackManager *defer_tasks = nullptr; CallbackManager destroy_callbacks; DeferCallback idle_task; DeferCallback future_task; #ifdef SW_USE_IOURING std::function iouring_interrupt_handler; #endif ssize_t (*write)(Reactor *reactor, network::Socket *socket, const void *buf, size_t n) = nullptr; ssize_t (*writev)(Reactor *reactor, network::Socket *socket, const iovec *iov, size_t iovcnt) = nullptr; int (*close)(Reactor *reactor, network::Socket *socket) = nullptr; private: ReactorImpl *impl; std::map> end_callbacks; std::map> exit_conditions; std::unordered_map sockets_; public: explicit Reactor(int max_event = SW_REACTOR_MAXEVENTS, Type _type = TYPE_AUTO); ~Reactor(); bool if_exit(); void defer(const Callback &cb, void *data = nullptr); void set_end_callback(EndCallback _id, const std::function &fn); void erase_end_callback(EndCallback _id); void set_exit_condition(ExitCondition _id, const std::function &fn); void set_handler(int fd_type, int event, ReactorHandler handler); bool isset_handler(int fd_type, int event) const; void add_destroy_callback(const Callback &cb, void *data = nullptr); void execute_begin_callback() const; void execute_end_callbacks(bool _timed_out = false); void drain_write_buffer(network::Socket *socket); bool ready() const { return ready_; } bool is_running() const { return running; } size_t remove_exit_condition(const ExitCondition _id) { return exit_conditions.erase(_id); } bool isset_exit_condition(const ExitCondition _id) { return exit_conditions.find(_id) != exit_conditions.end(); } int add_event(network::Socket *_socket, EventType event_type) const { if (!(_socket->events & event_type)) { return set(_socket, _socket->events | event_type); } return SW_OK; } int del_event(network::Socket *_socket, EventType event_type) const { if (_socket->events & event_type) { return set(_socket, _socket->events & (~event_type)); } return SW_OK; } int remove_read_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_WRITE) { _socket->events &= (~SW_EVENT_READ); return set(_socket, _socket->events); } else { return del(_socket); } } int remove_write_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_READ) { _socket->events &= (~SW_EVENT_WRITE); return set(_socket, _socket->events); } else { return del(_socket); } } int add_read_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_WRITE) { _socket->events |= SW_EVENT_READ; return set(_socket, _socket->events); } else { return add(_socket, SW_EVENT_READ); } } int add_write_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_READ) { _socket->events |= SW_EVENT_WRITE; return set(_socket, _socket->events); } else { return add(_socket, SW_EVENT_WRITE); } } bool exists(const network::Socket *_socket) const { return !_socket->removed && _socket->events; } bool exists(const int fd) const { return sockets_.find(fd) != sockets_.end(); } int get_timeout_msec() const { return defer_tasks == nullptr ? timeout_msec : 0; } void set_timeout_msec(int mesc) { timeout_msec = mesc; } size_t get_event_num() const { return sockets_.size(); } const std::unordered_map &get_sockets() { return sockets_; } network::Socket *get_socket(const int fd) { return sockets_[fd]; } void foreach_socket(const std::function &callback) const { for (auto &kv : sockets_) { callback(kv.first, kv.second); } } ReactorHandler get_handler(const FdType fd_type, const EventType event) const { switch (event) { case SW_EVENT_READ: return read_handler[fd_type]; case SW_EVENT_WRITE: return write_handler[fd_type] ? write_handler[fd_type] : default_write_handler; case SW_EVENT_ERROR: return error_handler[fd_type] ? error_handler[fd_type] : default_error_handler; default: abort(); } } ReactorHandler get_error_handler(const FdType fd_type) const { ReactorHandler handler = get_handler(fd_type, SW_EVENT_ERROR); // error callback is not set, try to use readable or writable callback if (handler == nullptr) { handler = get_handler(fd_type, SW_EVENT_READ); if (handler == nullptr) { handler = get_handler(fd_type, SW_EVENT_WRITE); } } return handler; } void before_wait() { start = running = true; if (timeout_msec == 0) { timeout_msec = -1; } } int trigger_close_event(Event *event) { return default_error_handler(this, event); } void set_wait_exit(const bool enable) { wait_exit = enable; } void _add(network::Socket *_socket, const int events) { _socket->events = events; _socket->removed = 0; sockets_[_socket->fd] = _socket; } void _set(network::Socket *_socket, const int events) { _socket->events = events; } bool _exists(const network::Socket *_socket) { return sockets_.find(_socket->fd) != sockets_.end(); } void _del(network::Socket *_socket) { _socket->events = 0; _socket->removed = 1; sockets_.erase(_socket->fd); } bool catch_error() const { switch (errno) { case EINTR: return true; default: break; } return false; } static ssize_t _write(Reactor *reactor, network::Socket *socket, const void *buf, size_t n); static ssize_t _writev(Reactor *reactor, network::Socket *socket, const iovec *iov, size_t iovcnt); static int _close(Reactor *reactor, network::Socket *socket); static int _writable_callback(Reactor *reactor, Event *ev); static ssize_t write_func(const Reactor *reactor, network::Socket *socket, size_t _len, const std::function &send_fn, const std::function &append_fn); static bool isset_read_event(const int events) { return (events < SW_EVENT_DEAULT) || (events & SW_EVENT_READ); } static bool isset_write_event(const int events) { return events & SW_EVENT_WRITE; } static bool isset_error_event(const int events) { return events & SW_EVENT_ERROR; } }; int16_t translate_events_to_poll(int events); int translate_events_from_poll(int16_t events); } // namespace swoole #define SW_REACTOR_CONTINUE \ if (reactor_->once) { \ break; \ } else { \ continue; \ } ================================================ FILE: include/swoole_redis.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_server.h" #include #include #define SW_REDIS_RETURN_NIL "$-1\r\n" #define SW_REDIS_MAX_COMMAND_SIZE 64 #define SW_REDIS_MAX_LINES 128 #define SW_REDIS_MAX_STRING_SIZE 536870912 // 512M namespace swoole { namespace redis { enum State { STATE_RECEIVE_TOTAL_LINE, STATE_RECEIVE_LENGTH, STATE_RECEIVE_STRING, }; enum ReplyType { REPLY_ERROR, REPLY_NIL, REPLY_STATUS, REPLY_INT, REPLY_STRING, REPLY_SET, REPLY_MAP, }; const char *get_number(const char *p, int *_ret); int recv_packet(Protocol *protocol, Connection *conn, String *buffer); std::vector parse(const char *data, size_t len); void format_nil(String *buf); void format(String *buf, ReplyType type, const std::string &value); void format(String *buf, ReplyType type, long value); } // namespace redis } // namespace swoole ================================================ FILE: include/swoole_server.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_timer.h" #include "swoole_reactor.h" #include "swoole_signal.h" #include "swoole_protocol.h" #include "swoole_process_pool.h" #include "swoole_pipe.h" #include "swoole_channel.h" #include "swoole_message_bus.h" #ifdef SW_SUPPORT_DTLS #include "swoole_dtls.h" #endif #ifdef __MACH__ #include #endif #include #include #include #include #include #include #include #include //------------------------------------Server------------------------------------------- namespace swoole { namespace http_server { struct Request; struct RewriteRule; class StaticHandler; } // namespace http_server class Server; struct Manager; class Thread; typedef std::function WorkerFn; struct Session { SessionId id; int fd; uint32_t reactor_id : 8; uint32_t reserve_ : 24; }; struct Connection { /** * It must be in the header. When set to 0, it means that connection does not exist. * One-write and multiple-read operation is thread-safe * system fd must be 0. en: signalfd, listen socket */ uint8_t active; SocketType socket_type; int fd; int worker_id; SessionId session_id; //-------------------------------------------------------------- uint8_t ssl; uint8_t ssl_ready; uint8_t overflow; uint8_t high_watermark; uint8_t http_upgrade; uint8_t http2_stream; uint8_t websocket_compression; // If it is equal to 1, it means server actively closed the connection uint8_t close_actively; uint8_t closed; uint8_t close_queued; uint8_t closing; uint8_t close_reset; uint8_t peer_closed; // protected connection, do not close connection when receiving/sending timeout uint8_t protect; uint8_t close_notify; uint8_t close_force; ReactorId reactor_id; uint16_t close_errno; int server_fd; sw_atomic_t recv_queued_bytes; uint32_t send_queued_bytes; uint16_t waiting_time; uint16_t local_port; uint16_t local_addr_index; TimerNode *timer; /** * socket address */ network::Address info; /** * link anything, for kernel, do not use with application. */ void *object; /** * socket, only operated in the main process */ network::Socket *socket; /** * connect/recv/send/close time */ double connect_time; double last_recv_time; double last_send_time; double last_dispatch_time; /** * bind uid */ uint32_t uid; /** * upgrade websocket */ uint8_t websocket_status; /** * unfinished data frame */ String *websocket_buffer; String *ssl_client_cert; pid_t ssl_client_cert_pid; sw_atomic_t lock; }; //------------------------------------ReactorThread------------------------------------------- struct ReactorThread { int id; std::thread thread; network::Socket *notify_pipe = nullptr; uint64_t dispatch_count = 0; network::Socket *pipe_command = nullptr; TimerNode *heartbeat_timer = nullptr; MessageBus message_bus; int init(Server *serv, Reactor *reactor, uint16_t reactor_id); void shutdown(Reactor *reactor); int close_connection(Reactor *reactor, SessionId session_id); void clean(); }; struct ServerPortGS { sw_atomic_t connection_num; sw_atomic_t *connection_nums = nullptr; sw_atomic_long_t abort_count; sw_atomic_long_t accept_count; sw_atomic_long_t close_count; sw_atomic_long_t dispatch_count; sw_atomic_long_t request_count; sw_atomic_long_t response_count; sw_atomic_long_t total_recv_bytes; sw_atomic_long_t total_send_bytes; }; struct ListenPort { uint16_t object_id; /** * tcp socket listen backlog */ uint16_t backlog = SW_BACKLOG; bool listening = false; /** * open tcp_defer_accept option */ int tcp_defer_accept = 0; /** * TCP_FASTOPEN */ int tcp_fastopen = 0; /** * TCP KeepAlive */ int tcp_keepidle = SW_TCP_KEEPIDLE; int tcp_keepinterval = SW_TCP_KEEPINTERVAL; int tcp_keepcount = SW_TCP_KEEPCOUNT; int tcp_user_timeout = 0; double max_idle_time = 0; int socket_buffer_size = network::Socket::default_buffer_size; uint32_t buffer_high_watermark = 0; uint32_t buffer_low_watermark = 0; SocketType type = SW_SOCK_TCP; uint8_t ssl = 0; std::string host; int port = 0; network::Socket *socket = nullptr; pthread_t thread_id = 0; uint16_t heartbeat_idle_time = 0; /** * check data eof */ bool open_eof_check = false; /** * built-in http protocol */ bool open_http_protocol = false; /** * built-in http2.0 protocol */ bool open_http2_protocol = false; /** * built-in websocket protocol */ bool open_websocket_protocol = false; /** * Relevant settings of websocket server */ WebSocketSettings websocket_settings = {}; /** * one package: length check */ bool open_length_check = false; /** * for mqtt protocol */ bool open_mqtt_protocol = false; /** * redis protocol */ bool open_redis_protocol = false; /** * open tcp nodelay option */ bool open_tcp_nodelay = false; /** * open tcp nopush option(for sendfile) */ bool open_tcp_nopush = true; /** * open tcp keepalive */ bool open_tcp_keepalive = false; /** * set socket option */ int kernel_socket_recv_buffer_size = 0; int kernel_socket_send_buffer_size = 0; std::shared_ptr ssl_context = nullptr; std::unordered_map> sni_contexts; #ifdef SW_SUPPORT_DTLS std::unordered_map *dtls_sessions = nullptr; dtls::Session *create_dtls_session(network::Socket *sock) const; #endif bool ssl_is_enable() const { return get_ssl_context() != nullptr; } SSLContext *get_ssl_context() const { return ssl_context.get(); } std::shared_ptr dup_ssl_context() const { auto new_ctx = std::make_shared(); *new_ctx = *ssl_context; return new_ctx; } ServerPortGS *gs = nullptr; Protocol protocol = {}; void *ptr = nullptr; int (*onRead)(Reactor *reactor, ListenPort *port, Event *event) = nullptr; bool is_dgram() const { return network::Socket::is_dgram(type); } bool is_dtls() const { #ifdef SW_SUPPORT_DTLS return ssl_context && (ssl_context->protocols & SW_SSL_DTLS); #else return false; #endif } bool is_stream() const { return network::Socket::is_stream(type); } void set_eof_protocol(const std::string &eof, bool find_from_right = false); void set_length_protocol(uint32_t length_offset, char length_type, uint32_t body_offset); void set_stream_protocol(); void set_package_max_length(uint32_t max_length) { protocol.package_max_length = max_length; } explicit ListenPort(Server *server); ~ListenPort() = default; int listen(); void close(); bool import(int sock); void init_protocol(); const char *get_protocols() const; int create_socket(); void close_socket(); void destroy_http_request(Connection *conn); static int readable_callback_raw(Reactor *reactor, ListenPort *lp, Event *event); static int readable_callback_length(Reactor *reactor, ListenPort *lp, Event *event); static int readable_callback_eof(Reactor *reactor, ListenPort *lp, Event *event); static int readable_callback_http(Reactor *reactor, ListenPort *lp, Event *event); static int readable_callback_redis(Reactor *reactor, ListenPort *lp, Event *event); bool ssl_context_init(); bool ssl_context_create(SSLContext *context) const; bool ssl_create(network::Socket *sock); bool ssl_add_sni_cert(const std::string &name, const std::shared_ptr &ctx); static bool ssl_matches_wildcard_name(const char *subject_name, const char *cert_name); bool ssl_init() const; bool set_ssl_key_file(const std::string &file) const { return ssl_context->set_key_file(file); } bool set_ssl_cert_file(const std::string &file) const { return ssl_context->set_cert_file(file); } void set_ssl_cafile(const std::string &file) const { ssl_context->cafile = file; } bool set_ssl_client_cert_file(const std::string &file) const { return ssl_context->set_client_cert_file(file); } void set_ssl_capath(const std::string &path) const { ssl_context->capath = path; } void set_ssl_passphrase(const std::string &str) const { ssl_context->passphrase = str; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME void set_tls_host_name(const std::string &str) const { ssl_context->tls_host_name = str; // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); } #endif void set_ssl_dhparam(const std::string &file) const { ssl_context->dhparam = file; } void set_ssl_ecdh_curve(const std::string &str) const { ssl_context->ecdh_curve = str; } void set_ssl_protocols(long protocols) const { if (protocols & SW_SSL_DTLS) { #ifndef SW_SUPPORT_DTLS protocols ^= SW_SSL_DTLS; #else if (is_dgram()) { protocols ^= SW_SSL_DTLS; } #endif } ssl_context->protocols = protocols; } void set_ssl_disable_compress(bool value) const { ssl_context->disable_compress = value; } void set_ssl_verify_peer(bool value) const { ssl_context->verify_peer = value; } void set_ssl_allow_self_signed(bool value) const { ssl_context->allow_self_signed = value; } void set_ssl_verify_depth(uint8_t value) const { ssl_context->verify_depth = value; } void set_ssl_ciphers(const std::string &str) const { ssl_context->ciphers = str; } void set_ssl_prefer_server_ciphers(bool value) const { ssl_context->prefer_server_ciphers = value; } #ifdef OPENSSL_IS_BORINGSSL void set_ssl_grease(uint8_t value) { ssl_context->grease = value; } #endif const std::string &get_ssl_cert_file() const { return ssl_context->cert_file; } const std::string &get_ssl_key_file() const { return ssl_context->key_file; } const std::string &get_ssl_client_cert_file() const { return ssl_context->client_cert_file; } const std::string &get_ssl_capath() const { return ssl_context->capath; } const std::string &get_ssl_cafile() const { return ssl_context->cafile; } bool get_ssl_verify_peer() const { return ssl_context->verify_peer; } bool get_ssl_allow_self_signed() const { return ssl_context->allow_self_signed; } uint32_t get_ssl_protocols() const { return ssl_context->protocols; } bool has_sni_contexts() const { return !sni_contexts.empty(); } static int ssl_server_sni_callback(SSL *ssl, int *al, void *arg); void clear_protocol(); network::Socket *get_socket() const { return socket; } int get_port() const { return port; } const char *get_host() const { return host.c_str(); } SocketType get_type() const { return type; } int get_fd() const { return socket ? socket->fd : -1; } size_t get_connection_num() const; }; struct ServerGS { pid_t master_pid; pid_t manager_pid; SessionId session_round; sw_atomic_t start; sw_atomic_t shutdown; int max_fd; int min_fd; bool onstart_called; time_t start_time; sw_atomic_t connection_num; sw_atomic_t *connection_nums = nullptr; sw_atomic_t tasking_num; uint32_t max_concurrency; sw_atomic_t concurrency; sw_atomic_long_t abort_count; sw_atomic_long_t accept_count; sw_atomic_long_t close_count; sw_atomic_long_t dispatch_count; sw_atomic_long_t request_count; sw_atomic_long_t response_count; sw_atomic_long_t total_recv_bytes; sw_atomic_long_t total_send_bytes; sw_atomic_long_t pipe_packet_msg_id; sw_atomic_long_t task_count; sw_atomic_t spinlock; Barrier manager_barrier; ProcessPool task_workers; ProcessPool event_workers; }; class Factory { protected: Server *server_; public: explicit Factory(Server *_server) { server_ = _server; } pid_t spawn_event_worker(Worker *worker) const; pid_t spawn_user_worker(Worker *worker) const; pid_t spawn_task_worker(Worker *worker) const; void kill_user_workers() const; void kill_event_workers() const; void kill_task_workers() const; void check_worker_exit_status(Worker *worker, const ExitStatus &exit_status) const; virtual ~Factory() = default; virtual bool start() = 0; virtual bool shutdown() = 0; virtual bool dispatch(SendData *) = 0; virtual bool finish(SendData *) = 0; virtual bool notify(DataHead *) = 0; virtual bool end(SessionId session_id, int flags) = 0; }; class BaseFactory : public Factory { public: explicit BaseFactory(Server *server); ~BaseFactory() override; bool start() override; bool shutdown() override; bool dispatch(SendData *) override; bool finish(SendData *) override; bool notify(DataHead *) override; bool end(SessionId session_id, int flags) override; bool forward_message(const Session *session, SendData *data) const; }; class ProcessFactory : public Factory { public: explicit ProcessFactory(Server *server); ~ProcessFactory() override; bool start() override; bool shutdown() override; bool dispatch(SendData *) override; bool finish(SendData *) override; bool notify(DataHead *) override; bool end(SessionId session_id, int flags) override; }; struct ThreadReloadTask { Server *server_; uint16_t worker_num; uint16_t reloaded_num; bool is_completed() const { return reloaded_num == worker_num; } ThreadReloadTask(Server *_server, bool _reload_all_workers); ~ThreadReloadTask() = default; }; class ThreadFactory : public BaseFactory { std::vector> threads_; std::mutex lock_; std::condition_variable cv_; std::queue queue_; bool reload_all_workers = false; sw_atomic_t reloading = 0; std::shared_ptr reload_task; void at_thread_enter(WorkerId id, int process_type); void at_thread_exit(Worker *worker); void create_message_bus() const; void destroy_message_bus(); void do_reload(); void push_to_wait_queue(Worker *worker); public: explicit ThreadFactory(Server *server); ~ThreadFactory() override; WorkerId get_manager_thread_id() const; WorkerId get_master_thread_id() const; void spawn_event_worker(WorkerId i); void spawn_task_worker(WorkerId i); void spawn_user_worker(WorkerId i); void spawn_manager_thread(WorkerId i); void terminate_manager_thread(); void wait(); bool reload(bool reload_all_workers); bool start() override; bool shutdown() override; }; enum ServerEventType { // recv data payload SW_SERVER_EVENT_RECV_DATA, SW_SERVER_EVENT_RECV_DGRAM, // send data SW_SERVER_EVENT_SEND_DATA, SW_SERVER_EVENT_SEND_FILE, // connection event SW_SERVER_EVENT_CLOSE, SW_SERVER_EVENT_CONNECT, SW_SERVER_EVENT_CLOSE_FORCE, SW_SERVER_EVENT_CLOSE_FORWARD, // task SW_SERVER_EVENT_TASK, SW_SERVER_EVENT_FINISH, // pipe SW_SERVER_EVENT_PIPE_MESSAGE, // event operate SW_SERVER_EVENT_PAUSE_RECV, SW_SERVER_EVENT_RESUME_RECV, // buffer event SW_SERVER_EVENT_BUFFER_FULL, SW_SERVER_EVENT_BUFFER_EMPTY, // process message SW_SERVER_EVENT_INCOMING, SW_SERVER_EVENT_SHUTDOWN, SW_SERVER_EVENT_COMMAND_REQUEST, SW_SERVER_EVENT_COMMAND_RESPONSE, SW_SERVER_EVENT_SHUTDOWN_SIGNAL, }; class Server { public: typedef int (*DispatchFunction)(Server *, Connection *, SendData *); struct Command { typedef std::function Callback; typedef std::function Handler; enum ProcessType { MASTER = 1u << 1, REACTOR_THREAD = 1u << 2, EVENT_WORKER = 1u << 3, TASK_WORKER = 1u << 4, MANAGER = 1u << 5, ALL_PROCESS = MASTER | REACTOR_THREAD | EVENT_WORKER | TASK_WORKER | MANAGER, }; int id; int accepted_process_types; std::string name; }; struct MultiTask { uint16_t count; std::unordered_map map; std::function pack; std::function unpack; std::function fail; explicit MultiTask(uint16_t n) { count = n; } int find(TaskId task_id); }; enum Mode { MODE_BASE = 1, MODE_PROCESS = 2, MODE_THREAD = 3, }; enum TaskIpcMode { TASK_IPC_UNIXSOCK = 1, TASK_IPC_MSGQUEUE = 2, TASK_IPC_PREEMPTIVE = 3, TASK_IPC_STREAM = 4, }; enum ThreadType { THREAD_NORMAL = 0, THREAD_MASTER = 1, THREAD_REACTOR = 2, THREAD_HEARTBEAT = 3, THREAD_WORKER = 4, }; enum DispatchMode { DISPATCH_ROUND = 1, DISPATCH_FDMOD = 2, DISPATCH_IDLE_WORKER = 3, DISPATCH_IPMOD = 4, DISPATCH_UIDMOD = 5, DISPATCH_USERFUNC = 6, DISPATCH_CO_CONN_LB = 8, DISPATCH_CO_REQ_LB = 9, DISPATCH_CONCURRENT_LB = 10, }; enum FactoryDispatchResult { DISPATCH_RESULT_DISCARD_PACKET = -1, DISPATCH_RESULT_CLOSE_CONNECTION = -2, DISPATCH_RESULT_USERFUNC_FALLBACK = -3, }; // deprecated, will be removed in the next minor version enum HookType { HOOK_MASTER_START, HOOK_MASTER_TIMER, HOOK_REACTOR_START, HOOK_WORKER_START, HOOK_TASK_WORKER_START, HOOK_MASTER_CONNECT, HOOK_REACTOR_CONNECT, HOOK_WORKER_CONNECT, HOOK_REACTOR_RECEIVE, HOOK_WORKER_RECEIVE, HOOK_REACTOR_CLOSE, HOOK_WORKER_CLOSE, HOOK_MANAGER_START, HOOK_MANAGER_TIMER, HOOK_PROCESS_TIMER, HOOK_END = SW_MAX_HOOK_TYPE - 1, }; enum CloseFlag { CLOSE_RESET = 1u << 1, CLOSE_ACTIVELY = 1u << 2, }; /** * reactor thread/process num */ uint16_t reactor_num = 0; /** * worker process num */ uint32_t worker_num = 0; uint8_t dgram_port_num = 0; /** * package dispatch mode */ uint8_t dispatch_mode = DISPATCH_FDMOD; /** * No idle work process is available. */ bool scheduler_warning = false; int worker_uid = 0; int worker_groupid = 0; /** * worker process max request */ uint32_t max_request = 0; uint32_t max_request_grace = 0; network::Socket *udp_socket_ipv4 = nullptr; network::Socket *udp_socket_ipv6 = nullptr; network::Socket *dgram_socket = nullptr; uint32_t max_wait_time = SW_WORKER_MAX_WAIT_TIME; uint32_t worker_max_concurrency = UINT_MAX; /*----------------------------Reactor schedule--------------------------------*/ sw_atomic_t worker_round_id = 0; /** * worker(worker and task_worker) process chroot / user / group */ std::string chroot_; std::string user_; std::string group_; /** * run as a daemon process */ bool daemonize = false; /** * have dgram socket */ bool have_dgram_sock = false; /** * have stream socket */ bool have_stream_sock = false; /** * open cpu affinity setting */ bool open_cpu_affinity = false; /** * disable notice when use SW_DISPATCH_ROUND and SW_DISPATCH_QUEUE */ bool disable_notify = false; /** * discard the timeout request */ bool discard_timeout_request = false; /** * parse cookie header */ bool http_parse_cookie = true; /** * parse x-www-form-urlencoded data */ bool http_parse_post = true; /** * parse multipart/form-data files to match $_FILES */ bool http_parse_files = false; /** * http content compression */ bool http_compression = false; /** * RFC-7692 */ bool websocket_compression = false; /** * handle static files */ bool enable_static_handler = false; /** * show file list in the current directory */ bool http_autoindex = false; /** * enable onConnect/onClose event when use dispatch_mode=1/3 */ bool enable_unsafe_event = false; /** * waiting for worker onConnect callback function to return */ bool enable_delay_receive = false; /** * reuse port */ bool enable_reuse_port = false; /** * asynchronous reloading */ bool reload_async = true; /** * use event object */ bool event_object = false; /** * use task object */ bool task_object = false; /** * enable coroutine in task worker */ bool task_enable_coroutine = false; /** * yield coroutine when the output buffer is full */ bool send_yield = true; /** * enable coroutine */ bool enable_coroutine = true; /** * disable multi-threads */ bool single_thread = false; /** * server status */ bool running = true; int *cpu_affinity_available = nullptr; int cpu_affinity_available_num = 0; UnixSocket *pipe_command = nullptr; MessageBus message_bus; double send_timeout = 0; uint16_t heartbeat_check_interval = 0; time_t reload_time = 0; time_t warning_time = 0; long timezone_ = 0; TimerNode *master_timer = nullptr; TimerNode *heartbeat_timer = nullptr; /* buffer output/input setting*/ uint32_t output_buffer_size = UINT_MAX; uint32_t input_buffer_size = SW_INPUT_BUFFER_SIZE; uint32_t max_queued_bytes = 0; /** * the master process and worker process communicate using unix socket dgram. * ipc_max_size represents the maximum size of each datagram, * which is obtained from the kernel send buffer of unix socket in swServer_set_ipc_max_size function. */ uint32_t ipc_max_size = SW_IPC_MAX_SIZE; void *private_data_1 = nullptr; void *private_data_2 = nullptr; void *private_data_3 = nullptr; void *private_data_4 = nullptr; Factory *factory_ = nullptr; Manager *manager_ = nullptr; std::vector ports; std::vector> worker_pipes; ListenPort *get_primary_port() const { return ports.front(); } Mode get_mode() const { return mode_; }; /** * This method can only be used for INET ports and cannot obtain Unix socket ports. */ ListenPort *get_port(int _port) const { for (auto port : ports) { if (port->port == _port || _port == 0) { return port; } } return nullptr; } ListenPort *get_port(SocketType type, const char *host, int _port) const { for (auto port : ports) { if (port->port == _port && port->type == type && strcmp(host, port->host.c_str()) == 0) { return port; } } return nullptr; } ListenPort *get_port_by_server_fd(const int server_fd) const { return static_cast(connection_list[server_fd].object); } ListenPort *get_port_by_fd(int fd) const { return get_port_by_server_fd(connection_list[fd].server_fd); } ListenPort *get_port_by_session_id(SessionId session_id) const { const Connection *conn = get_connection_by_session_id(session_id); if (!conn) { return nullptr; } return get_port_by_fd(conn->fd); } uint32_t get_package_max_length(Connection *conn) { return get_port_by_fd(conn->fd)->protocol.package_max_length; } network::Socket *get_server_socket(int fd) const { return connection_list[fd].socket; } network::Socket *get_command_reply_socket() const { return is_base_mode() ? get_worker(0)->pipe_master : pipe_command->get_socket(false); } network::Socket *get_worker_pipe_master(WorkerId id) const { return get_worker(id)->pipe_master; } network::Socket *get_worker_pipe_worker(WorkerId id) const { return get_worker(id)->pipe_worker; } network::Socket *get_pipe_socket_in_message_bus(network::Socket *_socket) { if (is_thread_mode()) { return get_worker_message_bus()->get_pipe_socket(_socket); } else { return _socket; } } network::Socket *get_worker_pipe_master_in_message_bus(const Worker *worker) { return get_pipe_socket_in_message_bus(worker->pipe_master); } network::Socket *get_worker_pipe_worker_in_message_bus(const Worker *worker) { return get_pipe_socket_in_message_bus(worker->pipe_worker); } network::Socket *get_worker_pipe_master_in_message_bus(WorkerId id) { return get_worker_pipe_master_in_message_bus(get_worker(id)); } network::Socket *get_worker_pipe_worker_in_message_bus(WorkerId id) { return get_worker_pipe_worker_in_message_bus(get_worker(id)); } /** * [Worker|Master] */ network::Socket *get_reactor_pipe_socket(const SessionId session_id, int reactor_id) const { const int pipe_index = session_id % reactor_pipe_num; /** * pipe_worker_id: The pipe in which worker. */ int pipe_worker_id = reactor_id + (pipe_index * reactor_num); Worker *worker = get_worker(pipe_worker_id); return worker->pipe_worker; } /** * task process */ uint32_t task_worker_num = 0; uint8_t task_ipc_mode = TASK_IPC_UNIXSOCK; uint32_t task_max_request = 0; uint32_t task_max_request_grace = 0; std::vector> task_notify_pipes; EventData *task_results = nullptr; /** * Used for process management, saving the mapping relationship between PID and worker pointers */ std::unordered_map user_worker_map; /** * Shared memory, sharing state between processes */ Worker *user_workers = nullptr; std::unordered_map commands; std::unordered_map command_handlers; std::unordered_map command_callbacks; int command_current_id = 1; int64_t command_current_request_id = 1; Worker *workers = nullptr; ServerGS *gs = nullptr; std::shared_ptr> locations = nullptr; std::shared_ptr> http_index_files = nullptr; std::shared_ptr> http_compression_types = nullptr; Barrier reactor_thread_barrier = {}; /** * temporary directory for HTTP uploaded file. */ std::string upload_tmp_dir = "/tmp"; /** * Write the uploaded file in form-data to disk file */ size_t upload_max_filesize = 0; /** * http compression level for gzip/br */ uint8_t http_compression_level = 0; uint32_t compression_min_length; /** * master process pid */ std::string pid_file; std::queue *buffer_pool = nullptr; const Allocator *recv_buffer_allocator = &SwooleG.std_allocator; size_t recv_buffer_size = SW_BUFFER_SIZE_BIG; int manager_alarm = 0; /** * message queue key */ uint64_t message_queue_key = 0; void *hooks[SW_MAX_HOOK_TYPE] = {}; /*----------------------------Event Callback--------------------------------*/ /** * Master Process */ std::function onStart; std::function onBeforeShutdown; std::function onShutdown; /** * Manager Process */ std::function onManagerStart; std::function onManagerStop; std::function onWorkerError; std::function onBeforeReload; std::function onAfterReload; /** * Worker Process */ std::function onPipeMessage; std::function onWorkerStart; std::function onWorkerStop; std::function onWorkerExit; std::function onUserWorkerStart; /** * Connection */ std::function onReceive; std::function onPacket; std::function onClose; std::function onConnect; std::function onBufferFull; std::function onBufferEmpty; /** * Task Worker */ std::function onTask; std::function onFinish; /** * for MessageBus */ std::function msg_id_generator; /** * Hook */ int (*dispatch_func)(Server *, Connection *, SendData *) = nullptr; explicit Server(Mode _mode = MODE_BASE); ~Server(); bool set_document_root(const std::string &path); void add_static_handler_location(const std::string &); void add_static_handler_index_files(const std::string &); bool select_static_handler(const http_server::Request *request, const Connection *conn); bool apply_rewrite_rules(http_server::StaticHandler *handler); void add_http_compression_type(const std::string &type); void add_rewrite_rule(const std::string &pattern, const std::string &replacement); int create(); bool create_worker_pipes(); int start(); void destroy(); bool reload(bool reload_all_workers) const; bool shutdown(); int add_worker(Worker *worker); ListenPort *add_port(SocketType type, const char *host, int port); int add_systemd_socket(); void add_hook(enum HookType type, const Callback &func, int push_back); bool add_command(const std::string &command, int accepted_process_types, const Command::Handler &func); Connection *add_connection(const ListenPort *ls, network::Socket *_socket, int server_fd); const char *get_local_addr(Connection *conn); const char *get_remote_addr(Connection *conn); void abort_connection(Reactor *reactor, const ListenPort *ls, network::Socket *_socket) const; void abort_worker(Worker *worker) const; void reset_worker_counter(Worker *worker) const; int connection_incoming(Reactor *reactor, Connection *conn) const; uint32_t get_idle_worker_num() const; int get_idle_task_worker_num() const; int get_tasking_num() const; TaskId get_task_id(const EventData *task) const { return get_task_worker_pool()->get_task_id(task); } static uint16_t get_command_id(const EventData *cmd) { return cmd->info.server_fd; } EventData *get_task_result() const { return &(task_results[swoole_get_worker_id()]); } WorkerId get_task_src_worker_id(const EventData *task) const { return get_task_worker_pool()->get_task_src_worker_id(task); } int get_minfd() const { return gs->min_fd; } int get_maxfd() const { return gs->max_fd; } void set_maxfd(int maxfd) const { gs->max_fd = maxfd; } void set_minfd(int minfd) const { gs->min_fd = minfd; } pid_t get_master_pid() const { return gs->master_pid; } pid_t get_manager_pid() const { return gs->manager_pid; } pid_t get_worker_pid(WorkerId worker_id) const { return get_worker(worker_id)->pid; } const std::string &get_document_root() { return document_root; } String *get_recv_buffer(network::Socket *_socket) const { String *buffer = _socket->recv_buffer; if (buffer == nullptr) { buffer = new String(SW_BUFFER_SIZE_BIG, recv_buffer_allocator); _socket->recv_buffer = buffer; } return buffer; } MessageBus *get_worker_message_bus() { #ifdef SW_THREAD return sw_likely(is_thread_mode()) ? SwooleTG.message_bus : &message_bus; #else return &message_bus; #endif } uint32_t get_worker_buffer_num() const { return is_base_mode() ? 1 : reactor_num + dgram_port_num; } ProcessPool *get_task_worker_pool() const { return &gs->task_workers; } ProcessPool *get_event_worker_pool() const { return &gs->event_workers; } bool is_support_unsafe_events() const { if (is_hash_dispatch_mode()) { return true; } else { return enable_unsafe_event; } } bool is_process_mode() const { return mode_ == MODE_PROCESS; } bool is_base_mode() const { return mode_ == MODE_BASE; } bool is_thread_mode() const { return mode_ == MODE_THREAD; } bool is_enable_coroutine() const { if (is_task_worker()) { return task_enable_coroutine; } else if (is_manager()) { return false; } else { return enable_coroutine; } } bool is_master_thread() const { return swoole_get_thread_type() == THREAD_MASTER; } bool is_hash_dispatch_mode() const { return dispatch_mode == DISPATCH_FDMOD || dispatch_mode == DISPATCH_IPMOD || dispatch_mode == DISPATCH_CO_CONN_LB; } bool is_support_send_yield() const { return is_hash_dispatch_mode(); } bool if_require_packet_callback(const ListenPort *port, bool isset) const { return (port->is_dgram() && !port->ssl && !isset); } bool if_require_receive_callback(const ListenPort *port, bool isset) const { return (((port->is_dgram() && port->ssl) || port->is_stream()) && !isset); } bool if_forward_message(const Session *session) const { return session->reactor_id != swoole_get_worker_id(); } Worker *get_worker(uint16_t worker_id) const; bool kill_worker(int worker_id); void stop_async_worker(Worker *worker); Pipe *get_pipe_object(int pipe_fd) const { return static_cast(connection_list[pipe_fd].object); } size_t get_all_worker_num() const { return get_core_worker_num() + get_user_worker_num(); } size_t get_user_worker_num() const { return user_worker_list.size(); } size_t get_core_worker_num() const { return worker_num + task_worker_num; } ReactorThread *get_thread(int reactor_id) const { return &reactor_threads[reactor_id]; } bool is_started() const { return gs->start; } bool is_created() const { return factory_ != nullptr; } bool is_running() const { return running; } bool is_master() const { return swoole_get_worker_type() == SW_MASTER; } bool is_worker() const { return swoole_get_worker_type() == SW_EVENT_WORKER; } bool is_event_worker() const { return is_worker(); } bool is_task_worker() const { return swoole_get_worker_type() == SW_TASK_WORKER; } bool is_manager() const { return swoole_get_worker_type() == SW_MANAGER; } bool is_user_worker() const { return swoole_get_worker_type() == SW_USER_WORKER; } bool is_worker_thread() const { return is_thread_mode() && swoole_get_thread_type() == THREAD_WORKER; } bool is_worker_process() const { return !is_thread_mode() && (is_worker() || is_task_worker()); } bool is_reactor_thread() { return swoole_get_thread_type() == THREAD_REACTOR; } bool is_single_worker() const { return (worker_num == 1 && task_worker_num == 0 && max_request == 0 && get_user_worker_num() == 0); } bool isset_hook(HookType type) const { assert(type <= HOOK_END); return hooks[type]; } bool is_sync_process() const { if (is_manager()) { return true; } if (is_task_worker() && !task_enable_coroutine) { return true; } return false; } bool is_shutdown() const { return gs->shutdown; } // can only be used in the main process static bool is_valid_connection(const Connection *conn) { return (conn && conn->socket && conn->active && conn->socket->fd_type == SW_FD_SESSION); } bool is_healthy_connection(double now, const Connection *conn) const; static bool is_dgram_event(uint8_t type) { return type == SW_SERVER_EVENT_RECV_DGRAM; } static bool is_stream_event(uint8_t type) { switch (type) { case SW_SERVER_EVENT_RECV_DATA: case SW_SERVER_EVENT_SEND_DATA: case SW_SERVER_EVENT_SEND_FILE: case SW_SERVER_EVENT_CONNECT: case SW_SERVER_EVENT_CLOSE: case SW_SERVER_EVENT_PAUSE_RECV: case SW_SERVER_EVENT_RESUME_RECV: case SW_SERVER_EVENT_BUFFER_FULL: case SW_SERVER_EVENT_BUFFER_EMPTY: return true; default: return false; } } int get_connection_fd(SessionId session_id) const { return session_list[session_id % SW_SESSION_LIST_SIZE].fd; } Connection *get_connection_verify_no_ssl(SessionId session_id) const { Session *session = get_session(session_id); int fd = session->fd; Connection *conn = get_connection(fd); if (!conn || conn->active == 0) { return nullptr; } if (session->id != session_id || conn->session_id != session_id) { return nullptr; } return conn; } Connection *get_connection_verify(SessionId session_id) const { Connection *conn = get_connection_verify_no_ssl(session_id); if (conn && conn->ssl && !conn->ssl_ready) { return nullptr; } return conn; } Connection *get_connection(const int fd) const { if (static_cast(fd) > max_connection) { return nullptr; } return &connection_list[fd]; } Connection *get_connection_for_iterator(int fd) const { Connection *conn = get_connection(fd); if (conn && conn->active && !conn->closed) { if (conn->ssl && !conn->ssl_ready) { return nullptr; } return conn; } return nullptr; } Connection *get_connection_by_session_id(SessionId session_id) const { return get_connection(get_connection_fd(session_id)); } Session *get_session(SessionId session_id) const { return &session_list[session_id % SW_SESSION_LIST_SIZE]; } void clear_timer(); static void timer_callback(Timer *timer, TimerNode *tnode); bool create_event_workers(); bool create_task_workers(); bool create_user_workers(); int start_manager_process(); void call_hook(enum HookType type, void *arg); void call_worker_start_callback(Worker *worker); void call_worker_stop_callback(Worker *worker); void call_worker_error_callback(Worker *worker, const ExitStatus &status); void call_command_handler(MessageBus &mb, uint16_t worker_id, network::Socket *sock); std::string call_command_handler_in_master(int command_id, const std::string &msg); void call_command_callback(int64_t request_id, const std::string &result); void foreach_connection(const std::function &callback) const; static int accept_connection(Reactor *reactor, Event *event); #ifdef SW_SUPPORT_DTLS dtls::Session *accept_dtls_connection(const ListenPort *ls, const network::Address *sa); #endif static int accept_command_result(Reactor *reactor, Event *event); static int close_connection(Reactor *reactor, network::Socket *_socket); static int dispatch_task(const Protocol *proto, network::Socket *_socket, const RecvData *rdata); int send_to_connection(const SendData *) const; ssize_t send_to_worker_from_worker(const Worker *dst_worker, const void *buf, size_t len, int flags); bool has_kernel_nobufs_error(SessionId session_id) const; ssize_t send_to_worker_from_worker(WorkerId id, const EventData *data, int flags) { return send_to_worker_from_worker(get_worker(id), data, data->size(), flags); } ssize_t send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id) const; /** * Send data to session. * This function is used for sending data to the client in the server. * @return true on success, false on failure. */ bool send(SessionId session_id, const void *data, uint32_t length) const; /** * Send file to session. * This function is used for sending files in the HTTP server. * It will read the file from disk and send it to the client. */ bool sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length) const; bool sendwait(SessionId session_id, const void *data, uint32_t length) const; bool close(SessionId session_id, bool reset = false) const; bool notify(Connection *conn, ServerEventType event) const; bool feedback(Connection *conn, ServerEventType event); bool command(WorkerId process_id, Command::ProcessType process_type, const std::string &name, const std::string &msg, const Command::Callback &fn); bool task(EventData *_task, int *dst_worker_id, bool blocking = false); bool finish(const char *data, size_t data_len, int flags = 0, const EventData *current_task = nullptr); bool task_sync(EventData *task, int *dst_worker_id, double timeout = -1); bool task_sync(MultiTask &mtask, double timeout = -1); bool send_pipe_message(WorkerId worker_id, EventData *msg); bool send_pipe_message(WorkerId worker_id, const char *data, size_t len); void init_reactor(Reactor *reactor); void init_event_worker(Worker *worker) const; bool init_task_workers(); void init_signal_handler() const; void init_ipc_max_size(); void init_pipe_sockets(MessageBus *mb) const; void set_max_connection(uint32_t _max_connection); void set_max_concurrency(uint32_t _max_concurrency) const { if (_max_concurrency == 0) { _max_concurrency = UINT_MAX; } gs->max_concurrency = _max_concurrency; } void set_worker_max_concurrency(uint32_t _max_concurrency) { if (_max_concurrency == 0) { _max_concurrency = UINT_MAX; } worker_max_concurrency = _max_concurrency; } uint32_t get_max_connection() const { return max_connection; } uint32_t get_max_concurrency() const { return gs->max_concurrency; } uint32_t get_concurrency() const { return gs->concurrency; } bool is_unavailable() const { return get_concurrency() >= get_max_concurrency(); } uint32_t get_worker_max_concurrency() const { return worker_max_concurrency; } void set_start_session_id(SessionId value) const { if (value > UINT_MAX) { value = UINT_MAX; } gs->session_round = value; } void disable_accept(); int schedule_worker(int fd, SendData *data); size_t get_connection_num() const { if (gs->connection_nums) { size_t num = 0; for (uint32_t i = 0; i < worker_num; i++) { num += gs->connection_nums[i]; } return num; } else { return gs->connection_num; } } static int wait_other_worker(ProcessPool *pool, const ExitStatus &exit_status); static void read_worker_message(ProcessPool *pool, EventData *msg); void drain_worker_pipe() const; void clean_worker_connections(Worker *worker); /** * [Worker] */ void worker_start_callback(Worker *worker); void worker_stop_callback(Worker *worker); void worker_accept_event(DataHead *info); void worker_signal_init() const; std::function, const WorkerFn &fn)> worker_thread_start; /** * [Master] */ bool signal_handler_shutdown(); bool signal_handler_child_exit() const; bool signal_handler_reload(bool reload_all_workers) const; bool signal_handler_read_message() const; bool signal_handler_reopen_logger() const; static void worker_signal_handler(int signo); static int reactor_process_main_loop(ProcessPool *pool, Worker *worker); static void reactor_thread_main_loop(Server *serv, int reactor_id); static bool task_pack(EventData *task, const void *data, size_t data_len); static void task_dump(EventData *task); static bool task_unpack(EventData *task, String *buffer, PacketPtr *packet); static void master_signal_handler(int signo); static void heartbeat_check(Timer *timer, TimerNode *tnode); int start_event_worker(Worker *worker); const char *get_startup_error_message() const; private: enum Mode mode_; Connection *connection_list = nullptr; Session *session_list = nullptr; ServerPortGS *port_gs_list = nullptr; /** * http static file directory */ std::string document_root; std::shared_ptr> rewrite_rules; std::mutex lock_; uint32_t max_connection = 0; TimerNode *enable_accept_timer = nullptr; std::thread heartbeat_thread; /** * The number of pipe per reactor maintenance */ uint16_t reactor_pipe_num = 0; ReactorThread *reactor_threads = nullptr; /** * Only used for temporarily saving pointers in add_worker() */ std::vector user_worker_list; std::unordered_map local_addr_v4_map; std::unordered_map local_addr_v6_map; int create_pipe_buffers(); void release_pipe_buffers(); void create_worker(Worker *worker); Factory *create_base_factory(); Factory *create_thread_factory(); Factory *create_process_factory(); int start_check(); void check_port_type(const ListenPort *ls); void store_listen_socket(); void store_pipe_fd(UnixSocket *p); void destroy_base_factory() const; void destroy_thread_factory() const; void destroy_process_factory(); void destroy_worker(Worker *worker); void destroy_task_workers() const; int start_reactor_threads(); int start_reactor_processes(); int start_worker_threads(); int start_master_thread(Reactor *reactor); void start_heartbeat_thread(); void stop_worker_threads(); bool reload_worker_threads(bool reload_all_workers) const; void join_reactor_thread(); void stop_master_thread(); void join_heartbeat_thread(); TimerCallback get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn) const; bool init_network_interface_addr_map(); uint16_t get_local_addr_index(network::Address *addr); int get_lowest_load_worker_id() const { uint32_t lowest_load_worker_id = 0; size_t min_coroutine = workers[0].coroutine_num; for (uint32_t i = 1; i < worker_num; i++) { if (workers[i].coroutine_num < min_coroutine) { min_coroutine = workers[i].coroutine_num; lowest_load_worker_id = i; } } return lowest_load_worker_id; } int get_lowest_concurrent_worker_id() const { uint32_t lowest_concurrent_worker_id = 0; size_t min_concurrency = workers[0].concurrency; for (uint32_t i = 1; i < worker_num; i++) { if (workers[i].concurrency < min_concurrency) { min_concurrency = workers[i].concurrency; lowest_concurrent_worker_id = i; } } return lowest_concurrent_worker_id; } int get_idle_worker_id() { bool found = false; uint32_t key = 0; SW_LOOP_N(worker_num + 1) { key = sw_atomic_fetch_add(&worker_round_id, 1) % worker_num; if (workers[key].is_idle()) { found = true; break; } } if (sw_unlikely(!found)) { scheduler_warning = true; } swoole_trace_log(SW_TRACE_SERVER, "schedule=%d, round=%d", key, worker_round_id); return key; } void lock() { lock_.lock(); } void unlock() { lock_.unlock(); } }; } // namespace swoole typedef swoole::Server swServer; static inline swoole::Server *sw_server() { return SwooleG.server; } ================================================ FILE: include/swoole_signal.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #include namespace swoole { typedef void (*SignalHandler)(int); struct Signal { SignalHandler handler; uint16_t signo; bool activated; }; } // namespace swoole typedef swoole::SignalHandler swSignalHandler; #ifdef HAVE_SIGNALFD void swoole_signalfd_init(); #endif /** * The synchronous blocking IO mode is unsafe for executing PHP code within signal callback functions, * such as in the Server's Task worker process or the Manager process. * If a new signal is triggered during the execution of a signal function, * the recursive execution of the signal function can lead to a crash of the ZendVM. * When using `Swoole\Process::signal()` to register a PHP function as a signal handler, * it is crucial to set the third parameter to true; * this way, the underlying layer will not execute directly but will call * `swoole_signal_dispatch()` in a safe manner to execute the PHP signal callback function. */ SW_API swSignalHandler swoole_signal_set(int signo, swSignalHandler func, bool safety = false); SW_API bool swoole_signal_isset(int signo); SW_API swSignalHandler swoole_signal_set(int signo, swSignalHandler func, int restart, int mask); SW_API swSignalHandler swoole_signal_get_handler(int signo); SW_API uint32_t swoole_signal_get_listener_num(); SW_API void swoole_signal_clear(); SW_API void swoole_signal_block_all(); SW_API void swoole_signal_unblock_all(); SW_API char *swoole_signal_to_str(int sig); SW_API void swoole_signal_callback(int signo); /** * Only for synchronously blocked processes. * Due to the unreliability of signals, * executing complex logic directly within the signal handler function may pose security risks. * Therefore, the lower layer only marks memory in the signal handler * without directly invoking the application's set signal callback function. * Executing `swoole_signal_dispatch` in a safe context will actually call the signal callback function, * allowing for the execution of complex code within the callback. */ SW_API void swoole_signal_dispatch(); ================================================ FILE: include/swoole_socket.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #include "swoole_ssl.h" #include "swoole_buffer.h" #include "swoole_file.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK O_NONBLOCK #endif #ifdef __sun #define s6_addr8 _S6_un._S6_u8 #define s6_addr16 _S6_un._S6_u16 #define s6_addr32 _S6_un._S6_u32 #endif ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size); enum { SW_BAD_SOCKET = -1, }; namespace swoole { struct GethostbynameRequest; struct GetaddrinfoRequest; namespace network { struct SendfileTask { off_t offset; size_t length; char filename[0]; }; struct SendfileRequest { File file; int8_t corked; off_t begin; off_t end; public: SendfileRequest(const char *filename, off_t _offset) : file(filename, O_RDONLY) { begin = _offset; end = 0; corked = 0; } const char *get_filename() const { return file.get_path().c_str(); } }; struct Address { union { sockaddr ss; sockaddr_in inet_v4; sockaddr_in6 inet_v6; sockaddr_un un; } addr; socklen_t len; SocketType type; /** * Assign an address based on the socket type and host/port. * For IPv4, the host can be an IP address like "192.168.1.100" * or a domain name like "www.example.com". * For IPv6, the host can be an IP address like "2001:0db8:85a3:0000:0000:8a2e:0370:7334" * or a domain name like "ipv6.example.com". * For UNIX socket, the host is the path to the socket file. * If _port is 0, it will not set the port. * If _resolve_name is false, it will not resolve the domain name. * * Returns true on success, false on failure. */ bool assign(SocketType _type, const std::string &_host, int _port = 0, bool _resolve_name = true); /** * Assign an address based on a URL string. * The format of the URL can be: * - tcp://hostname:port * - udp://hostname:port * - tcp://[IPv6_address]:port * - udp://[IPv6_address]:port * - unix:///path/to/socket * - udg:///path/to/socket * * Returns true on success, false on failure. */ bool assign(const std::string &url); int get_port() const; void set_port(int _port); const char *get_addr() const; bool is_loopback_addr() const; bool empty() const; in_addr *addr_v4() { return &addr.inet_v4.sin_addr; } in6_addr *addr_v6() { return &addr.inet_v6.sin6_addr; } /** * Get the string representation of the address */ static const char *type_str(SocketType type); /** * Convert the address to a string representation. * For IPv4, it will be in the format "192.168.1.100" * For IPv6, it will be in the format "2001:0db8:85a3:0000:0000:8a2e:0370:7334" * For UNIX socket, it will be the path of the socket file. * The returned pointer is a static buffer, so it should not be freed. */ static const char *addr_str(int family, const void *addr); /** * Verify if the input string is an IP address, * where AF_INET indicates an IPv4 address, such as 192.168.1.100, * and AF_INET6 indicates an IPv6 address, for example, 2001:0000:130F:0000:0000:09C0:876A:130B. */ static bool verify_ip(int family, const std::string &str); static bool verify_port(int port, bool for_connect = false); }; struct IOVector { // we should modify iov_iterator instead of iov, iov is readonly iovec *iov = nullptr; iovec *iov_iterator = nullptr; int count = 0; int remain_count = 0; int index = 0; size_t offset_bytes = 0; IOVector(const iovec *_iov, int _iovcnt); ~IOVector(); void update_iterator(ssize_t _n); iovec *get_iterator() const { return iov_iterator; } size_t length() { size_t len = 0; SW_LOOP_N(count) { len += iov[i].iov_len; } return len; } int get_remain_count() const { return remain_count; } int get_index() const { return index; } size_t get_offset_bytes() const { return offset_bytes; } }; struct Socket { static double default_dns_timeout; static double default_connect_timeout; static double default_read_timeout; static double default_write_timeout; static uint32_t default_buffer_size; int fd; FdType fd_type; SocketType socket_type; int events; bool enable_tcp_nodelay; bool kernel_nobufs; uchar removed : 1; uchar silent_remove : 1; uchar nonblock : 1; uchar cloexec : 1; uchar direct_send : 1; uchar bound : 1; uchar listened : 1; uchar ssl_send_ : 1; uchar ssl_want_read : 1; uchar ssl_want_write : 1; uchar ssl_renegotiation : 1; uchar ssl_handshake_buffer_set : 1; uchar ssl_quiet_shutdown : 1; uchar ssl_closed_ : 1; #ifdef SW_SUPPORT_DTLS uchar dtls : 1; #endif uchar close_wait : 1; uchar send_wait : 1; uchar tcp_nopush : 1; uchar tcp_nodelay : 1; uchar skip_recv : 1; uchar recv_wait : 1; uchar event_hup : 1; /** * The default setting is false, meaning that system calls interrupted by signals will be automatically retried. If * set to true, the call will not be retried but will immediately return -1, setting errno to EINTR. In this case, * the caller must explicitly handle this error. */ uchar dont_restart : 1; // memory buffer size [user space] uint32_t buffer_size; uint32_t chunk_size; void *object; SSL *ssl; uint32_t ssl_state; /** * Only used for getsockname, written by the OS, not user. This is the exact actual address. */ Address info; double dns_timeout = default_dns_timeout; double connect_timeout = default_connect_timeout; double read_timeout = default_read_timeout; double write_timeout = default_write_timeout; double last_received_time; double last_sent_time; Buffer *out_buffer; Buffer *in_buffer; String *recv_buffer; TimerNode *recv_timer; TimerNode *send_timer; size_t total_recv_bytes; size_t total_send_bytes; // for reactor int handle_send(); int handle_sendfile(); // user space memory buffer void set_memory_buffer_size(uint32_t _buffer_size) { buffer_size = _buffer_size; } // socket option [kernel space] bool set_buffer_size(uint32_t _buffer_size) const; bool set_recv_buffer_size(uint32_t _buffer_size) const; bool set_send_buffer_size(uint32_t _buffer_size) const; bool set_kernel_read_timeout(double timeout); bool set_kernel_write_timeout(double timeout); bool set_kernel_timeout(double timeout) { return set_kernel_read_timeout(timeout) && set_kernel_write_timeout(timeout); } // socket option [user space] void set_timeout(double timeout, int type = SW_TIMEOUT_ALL); double get_timeout(TimeoutType type) const; bool has_timedout() const; bool has_kernel_nobufs(); bool set_nonblock() { return set_fd_option(1, -1); } bool set_block() { return set_fd_option(0, -1); } bool set_fd_option(int _nonblock, int _cloexec); int set_option(int level, int optname, int optval) const { return setsockopt(fd, level, optname, &optval, sizeof(optval)); } int set_option(int level, int optname, const void *optval, socklen_t optlen) const { return setsockopt(fd, level, optname, optval, optlen); } int get_option(int level, int optname, void *optval, socklen_t *optlen) const { return getsockopt(fd, level, optname, optval, optlen); } int get_option(int level, int optname, int *optval) const { socklen_t optlen = sizeof(*optval); return get_option(level, optname, optval, &optlen); } int get_fd() const { return fd; } const char *get_addr() const { return info.get_addr(); } int get_port() const { return info.get_port(); } uint32_t get_out_buffer_length() const { return out_buffer ? out_buffer->length() : 0; } int move_fd() { int sock_fd = fd; fd = SW_BAD_SOCKET; return sock_fd; } int get_name(); int get_peer_name(Address *sa) const; int set_tcp_nopush(int nopush); int set_reuse_addr(int enable = 1) const { return set_option(SOL_SOCKET, SO_REUSEADDR, enable); } int set_reuse_port(int enable = 1) const { #ifdef SO_REUSEPORT return set_option(SOL_SOCKET, SO_REUSEPORT, enable); #endif return -1; } bool set_tcp_nodelay(int nodelay = 1); bool check_liveness(); int sendfile_async(const char *filename, off_t offset, size_t length); int sendfile_sync(const char *filename, off_t offset, size_t length); ssize_t sendfile(const File &fp, off_t *offset, size_t length); ssize_t recv(void *_buf, size_t _n, int _flags); ssize_t send(const void *_buf, size_t _n, int _flags); ssize_t peek(void *_buf, size_t _n, int _flags) const; Socket *accept(); Socket *dup() const; ssize_t readv(IOVector *io_vector); ssize_t writev(IOVector *io_vector); ssize_t writev(const iovec *iov, size_t iovcnt) const { return ::writev(fd, iov, iovcnt); } /** * If the port is 0, the system will automatically allocate an available port. */ int bind(const std::string &_host, int port = 0); int bind(const Address &addr) { return bind(&addr.addr.ss, addr.len); } int bind(const struct sockaddr *sa, socklen_t len); int listen(int backlog = 0); void clean() const; ssize_t send_sync(const void *_data, size_t _len, int flags = 0); ssize_t send_async(const void *_data, size_t _len); ssize_t recv_sync(void *_data, size_t _len, int flags = 0); ssize_t writev_sync(const iovec *iov, size_t iovcnt); int connect(const Address &sa) const { return ::connect(fd, &sa.addr.ss, sa.len); } int connect(const Address *sa) const { return ::connect(fd, &sa->addr.ss, sa->len); } int connect(const std::string &host, int port) const { Address addr; addr.assign(socket_type, host, port); return connect(addr); } int connect_sync(const Address &sa); ReturnCode connect_async(const Address &sa); void ssl_clear_error() { ERR_clear_error(); ssl_want_read = 0; ssl_want_write = 0; } /** * This function does not set the last error; to obtain internal SSL error information, you should call * ERR_get_error(). */ int ssl_create(SSLContext *_ssl_context, int _flags); int ssl_connect(); ReturnCode ssl_accept(); ssize_t ssl_recv(void *_buf, size_t _n); ssize_t ssl_send(const void *_buf, size_t _n); ssize_t ssl_readv(IOVector *io_vector); ssize_t ssl_writev(IOVector *io_vector); ssize_t ssl_sendfile(const File &fp, off_t *offset, size_t size); STACK_OF(X509) * ssl_get_peer_cert_chain() const; std::vector ssl_get_peer_cert_chain(int limit) const; X509 *ssl_get_peer_certificate() const; int ssl_get_peer_certificate(char *buf, size_t n) const; bool ssl_get_peer_certificate(String *buf) const; bool ssl_verify(bool allow_self_signed) const; bool ssl_check_host(const char *tls_host_name) const; void ssl_catch_error() const; bool ssl_shutdown(); void ssl_close(); static const char *ssl_get_error_reason(int *reason); ssize_t recvfrom(char *_buf, size_t _len, int flags, Address *sa) const { sa->len = sizeof(sa->addr); return recvfrom(_buf, _len, flags, &sa->addr.ss, &sa->len); } ssize_t recvfrom(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) const; ssize_t recvfrom_sync(char *_buf, size_t _len, int flags, Address *sa); ssize_t recvfrom_sync(char *_buf, size_t _len, int flags, sockaddr *addr, socklen_t *addr_len); bool cork(); bool uncork(); bool isset_readable_event() const { return events & SW_EVENT_READ; } bool isset_writable_event() const { return events & SW_EVENT_WRITE; } int wait_event(int timeout_ms, int _events) const; bool wait_for(const std::function &fn, int event, int timeout_msec = -1); int what_event_want(int default_event) const; void free(); static inline bool is_dgram(SocketType type) { return type == SW_SOCK_UDP || type == SW_SOCK_UDP6 || type == SW_SOCK_UNIX_DGRAM; } static inline bool is_stream(SocketType type) { return type == SW_SOCK_TCP || type == SW_SOCK_TCP6 || type == SW_SOCK_UNIX_STREAM; } static inline bool is_inet4(SocketType type) { return type == SW_SOCK_TCP || type == SW_SOCK_UDP || type == SW_SOCK_RAW; } static inline bool is_inet6(SocketType type) { return type == SW_SOCK_TCP6 || type == SW_SOCK_UDP6 || type == SW_SOCK_RAW6; } static inline bool is_tcp(SocketType type) { return type == SW_SOCK_TCP || type == SW_SOCK_TCP6; } static inline bool is_udp(SocketType type) { return type == SW_SOCK_UDP || type == SW_SOCK_UDP6; } static inline bool is_local(SocketType type) { return type == SW_SOCK_UNIX_STREAM || type == SW_SOCK_UNIX_DGRAM; } static inline bool is_raw(SocketType type) { return type == SW_SOCK_RAW || type == SW_SOCK_RAW6; } bool is_stream() const { return is_stream(socket_type); } bool is_tcp() const { return is_tcp(socket_type); } bool is_udp() const { return is_udp(socket_type); } bool is_dgram() const { return is_dgram(socket_type); } bool is_inet4() const { return is_inet4(socket_type); } bool is_inet6() const { return is_inet6(socket_type); } bool is_inet() const { return is_inet4() || is_inet6(); } bool is_local() const { return is_local(socket_type); } bool is_raw() const { return is_raw(socket_type); } ssize_t write(const void *_buf, size_t _len) const { return ::write(fd, _buf, _len); } ssize_t read(void *_buf, size_t _len) const { return ::read(fd, _buf, _len); } /** * Read data from the socket synchronously without setting non-blocking or blocking IO, * and allow interruptions by signals. */ ssize_t read_sync(void *_buf, size_t _len); /** * Write data to the socket synchronously without setting non-blocking or blocking IO, * and allow interruptions by signals. */ ssize_t write_sync(const void *_buf, size_t _len); int shutdown(int _how) const { return ::shutdown(fd, _how); } ssize_t sendto_sync(const Address &dst_addr, const void *_buf, size_t _n, int flags = 0); ssize_t sendto(const char *dst_host, int dst_port, const void *data, size_t len, int flags = 0) const { Address addr; if (!addr.assign(socket_type, dst_host, dst_port)) { return SW_ERR; } return sendto(addr, data, len, flags); } ssize_t sendto(const Address &dst_addr, const void *data, size_t len, int flags = 0) const { return ::sendto(fd, data, len, flags, &dst_addr.addr.ss, dst_addr.len); } int catch_error(int err); int catch_write_error(const int err) { return catch_error(err); } int catch_write_pipe_error(const int err) { switch (err) { case ENOBUFS: #ifdef __linux__ kernel_nobufs = true; return SW_REDUCE_SIZE; #else return catch_error(err); #endif case EMSGSIZE: return SW_REDUCE_SIZE; default: return catch_error(err); } } int catch_read_error(const int err) { return catch_error(err); } static SocketType convert_to_type(int domain, int type); static SocketType convert_to_type(std::string &host); static int get_domain_and_type(SocketType type, int *sock_domain, int *sock_type); }; std::string gethostbyname(int type, const std::string &name); int gethostbyname(int type, const char *name, char *addr); int gethostbyname(GethostbynameRequest *req); int getaddrinfo(GetaddrinfoRequest *req); } // namespace network /** * This function will never return NULL; if memory allocation fails, a C++ exception will be thrown. * Must use the `socket->free()` function to release the object pointer instead of the `delete` operator. * When the socket is released, it will close the file descriptor (fd). * If you do not want the fd to be closed, use `socket->move_fd()` to relinquish ownership of the fd. */ network::Socket *make_socket(int fd, FdType fd_type); /** * The following three functions will return a null pointer if the socket creation fails. * It is essential to check the return value; * if it is nullptr, you should inspect errno to determine the cause of the error. */ network::Socket *make_socket(SocketType socket_type, FdType fd_type, int flags); network::Socket *make_socket( SocketType type, FdType fd_type, int sock_domain, int sock_type, int socket_protocol, int flags); int socket(int sock_domain, int sock_type, int socket_protocol, int flags); network::Socket *make_server_socket(SocketType socket_type, const char *address, int port = 0, int backlog = SW_BACKLOG); } // namespace swoole ================================================ FILE: include/swoole_socket_hook.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef SW_SOCKET_HOOK_H_ #define SW_SOCKET_HOOK_H_ #include #include #include #include #ifdef __cplusplus extern "C" { #endif #include "swoole_coroutine_api.h" #define socket(domain, type, protocol) swoole_coroutine_socket(domain, type, protocol) #define send(sockfd, buf, len, flags) swoole_coroutine_send(sockfd, buf, len, flags) #define read(sockfd, buf, len) swoole_coroutine_read(sockfd, buf, len) #define write(sockfd, buf, len) swoole_coroutine_write(sockfd, buf, len) #define recv(sockfd, buf, len, flags) swoole_coroutine_recv(sockfd, buf, len, flags) #define close(fd) swoole_coroutine_close(fd) #define connect(sockfd, addr, addrlen) swoole_coroutine_connect(sockfd, addr, addrlen) #define accept(sockfd, addr, addrlen) swoole_coroutine_accept(sockfd, addr, addrlen) #ifdef SW_HOOK_POLL_FAKE #define poll(fds, nfds, timeout) swoole_coroutine_poll_fake(fds, nfds, timeout) #else #define poll(fds, nfds, timeout) swoole_coroutine_poll(fds, nfds, timeout) #endif #define sendmsg(sockfd, msg, flags) swoole_coroutine_sendmsg(sockfd, msg, flags) #define recvmsg(sockfd, msg, flags) swoole_coroutine_recvmsg(sockfd, msg, flags) #define getaddrinfo(name, service, req, pai) swoole_coroutine_getaddrinfo(name, service, req, pai) #define gethostbyname(name) swoole_coroutine_gethostbyname(name) #ifdef __cplusplus } #endif #endif ================================================ FILE: include/swoole_socket_impl.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine_socket.h" #include using CoSocket = swoole::coroutine::Socket; using NetSocket = swoole::network::Socket; #ifdef SW_USE_URING_SOCKET #include "swoole_uring_socket.h" using swoole::coroutine::UringSocket; using SocketImpl = UringSocket; #else using SocketImpl = CoSocket; #endif std::shared_ptr swoole_coroutine_get_socket_object(int sockfd); std::shared_ptr swoole_coroutine_get_socket_object_ex(int sockfd); ================================================ FILE: include/swoole_ssl.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #include #include #include #include #include #include #include #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x10100000L #define SW_SUPPORT_DTLS #endif #if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3000000fL #undef SW_SUPPORT_DTLS #endif #ifdef OPENSSL_IS_BORINGSSL #define BIO_CTRL_DGRAM_SET_CONNECTED 32 #define BIO_CTRL_DGRAM_SET_PEER 44 #define BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT 45 #define BIO_dgram_get_peer(b, peer) (int) BIO_ctrl(b, BIO_CTRL_DGRAM_GET_PEER, 0, (char *) (peer)) #define OPENSSL_assert(x) assert(x) #endif enum swSSLCreateFlag { SW_SSL_SERVER = 1, SW_SSL_CLIENT = 2, }; enum swSSLState { SW_SSL_STATE_HANDSHAKE = 0, SW_SSL_STATE_READY = 1, SW_SSL_STATE_WAIT_STREAM = 2, }; enum swSSLVersion { SW_SSL_SSLv2 = 1u << 1, SW_SSL_SSLv3 = 1u << 2, SW_SSL_TLSv1 = 1u << 3, SW_SSL_TLSv1_1 = 1u << 4, SW_SSL_TLSv1_2 = 1u << 5, SW_SSL_TLSv1_3 = 1u << 6, SW_SSL_DTLS = 1u << 7, }; #define SW_SSL_ALL (SW_SSL_SSLv2 | SW_SSL_SSLv3 | SW_SSL_TLSv1 | SW_SSL_TLSv1_1 | SW_SSL_TLSv1_2 | SW_SSL_TLSv1_3) enum swSSLMethod { SW_SSLv23_METHOD = 0, SW_SSLv3_METHOD, SW_SSLv3_SERVER_METHOD, SW_SSLv3_CLIENT_METHOD, SW_SSLv23_SERVER_METHOD, SW_SSLv23_CLIENT_METHOD, SW_TLSv1_METHOD, SW_TLSv1_SERVER_METHOD, SW_TLSv1_CLIENT_METHOD, #ifdef TLS1_1_VERSION SW_TLSv1_1_METHOD, SW_TLSv1_1_SERVER_METHOD, SW_TLSv1_1_CLIENT_METHOD, #endif #ifdef TLS1_2_VERSION SW_TLSv1_2_METHOD, SW_TLSv1_2_SERVER_METHOD, SW_TLSv1_2_CLIENT_METHOD, #endif #ifdef SW_SUPPORT_DTLS SW_DTLS_CLIENT_METHOD, SW_DTLS_SERVER_METHOD, #endif }; namespace swoole { struct SSLContext { uchar http : 1; uchar http_v2 : 1; uchar prefer_server_ciphers : 1; uchar session_tickets : 1; uchar stapling : 1; uchar stapling_verify : 1; std::string ciphers; std::string ecdh_curve; std::string session_cache; std::string dhparam; std::string cert_file; std::string key_file; std::string passphrase; std::string client_cert_file; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME uchar disable_tls_host_name : 1; std::string tls_host_name; #endif #ifdef OPENSSL_IS_BORINGSSL uint8_t grease; #endif std::string cafile; std::string capath; uint8_t verify_depth; uchar disable_compress : 1; uchar verify_peer : 1; uchar allow_self_signed : 1; uint32_t protocols; uint8_t create_flag; SSL_CTX *context; SSL_CTX *get_context() const { return context; } bool ready() const { return context != nullptr; } void set_protocols(uint32_t _protocols) { protocols = _protocols; } bool set_cert_file(const std::string &_cert_file) { if (access(_cert_file.c_str(), R_OK) < 0) { swoole_warning("ssl cert file[%s] not found", _cert_file.c_str()); return false; } cert_file = _cert_file; return true; } bool set_key_file(const std::string &_key_file) { if (access(_key_file.c_str(), R_OK) < 0) { swoole_warning("ssl key file[%s] not found", _key_file.c_str()); return false; } key_file = _key_file; return true; } bool set_client_cert_file(const std::string &file) { if (access(file.c_str(), R_OK) < 0) { swoole_warning("ssl client cert file[%s] not found", file.c_str()); return false; } client_cert_file = file; return true; } bool create(); bool set_capath() const; bool set_ciphers() const; bool set_client_certificate() const; bool set_ecdh_curve() const; bool set_dhparam() const; ~SSLContext(); }; } // namespace swoole void swoole_ssl_init(); void swoole_ssl_destroy(); void swoole_ssl_lock_callback(int mode, int type, const char *file, int line); void swoole_ssl_server_http_advise(swoole::SSLContext &); const char *swoole_ssl_get_error(); int swoole_ssl_get_ex_connection_index(); int swoole_ssl_get_ex_port_index(); std::string swoole_ssl_get_version_message(); ================================================ FILE: include/swoole_static_handler.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_server.h" #include "swoole_http.h" #include "swoole_mime_type.h" #include #include namespace swoole { namespace http_server { struct RewriteRule { std::string pattern; std::string replacement; bool is_regex; }; class StaticHandler { private: Server *serv; std::string request_url; std::string original_url; std::string dir_path; std::set dir_files; std::string index_file; typedef struct { off_t offset; size_t length; char part_header[SW_HTTP_SERVER_PART_HEADER]; } task_t; std::vector tasks; size_t l_filename = 0; char filename[PATH_MAX]; struct stat file_stat; bool last = false; std::string content_type; std::string boundary; std::string end_part; size_t content_length = 0; public: int status_code = SW_HTTP_OK; StaticHandler(Server *_server, const char *url, size_t url_length) : request_url(url, url_length), original_url(url, url_length) { serv = _server; } /** * @return true: continue to execute backwards * @return false: break static handler */ bool try_serve(); bool try_serve_index_file(); bool is_modified(const std::string &date_if_modified_since) const; bool is_modified_range(const std::string &date_range) const; size_t make_index_page(String *buffer); bool get_dir_files(); bool set_filename(const std::string &filename); bool catch_error() { if (last) { status_code = SW_HTTP_NOT_FOUND; return true; } else { return false; } } bool has_index_file() const { return !index_file.empty(); } bool is_enabled_auto_index() const { return serv->http_autoindex; } static std::string get_date(); time_t get_file_mtime() const { #ifdef __MACH__ return file_stat.st_mtimespec.tv_sec; #else return file_stat.st_mtim.tv_sec; #endif } std::string get_date_last_modified() const; const char *get_filename() const { return filename; } const std::string &get_request_url() const { return request_url; } void set_request_url(const std::string &rewritten_url) { request_url = rewritten_url; } const std::string &get_boundary() { if (boundary.empty()) { boundary = std::string(SW_HTTP_SERVER_BOUNDARY_PREKEY); swoole_random_string(boundary, SW_HTTP_SERVER_BOUNDARY_TOTAL_SIZE - sizeof(SW_HTTP_SERVER_BOUNDARY_PREKEY)); } return boundary; } const std::string &get_content_type() { if (tasks.size() > 1) { content_type = std::string("multipart/byteranges; boundary=") + get_boundary(); return content_type; } else { return get_mimetype(); } } const std::string &get_mimetype() const { return mime_type::get(get_filename()); } std::string get_filename_std_string() { return {filename, l_filename}; } bool get_absolute_path(); const std::string &get_original_url() const { return original_url; } size_t get_filesize() const { return file_stat.st_size; } const std::vector &get_tasks() { return tasks; } bool is_dir() const { return S_ISDIR(file_stat.st_mode); } bool is_link() const { return S_ISLNK(file_stat.st_mode); } bool is_file() const { return S_ISREG(file_stat.st_mode); } bool is_absolute_path() { return swoole_strnpos(filename, l_filename, SW_STRL("..")) == -1; } bool is_located_in_document_root() { const std::string &document_root = serv->get_document_root(); const size_t l_document_root = document_root.length(); return l_filename > l_document_root && filename[l_document_root] == '/' && swoole_str_starts_with(filename, l_filename, document_root.c_str(), l_document_root); } size_t get_content_length() const { return content_length; } const std::string &get_end_part() { return end_part; } void parse_range(const char *range, const char *if_range); }; }; // namespace http_server }; // namespace swoole ================================================ FILE: include/swoole_string.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole.h" #include #define SW_STRINGL(s) s->str, s->length #define SW_STRINGS(s) s->str, s->size // copy value #define SW_STRINGCVL(s) s->str + s->offset, s->length - s->offset // append value #define SW_STRINGAVL(s) s->str + s->length, s->size - s->length namespace swoole { typedef std::function StringExplodeHandler; class String { private: void alloc(size_t _size, const Allocator *_allocator); void move(String &&src); void copy(const String &src); public: size_t length; size_t size; off_t offset; char *str; const Allocator *allocator; String() { length = size = offset = 0; str = nullptr; allocator = nullptr; } explicit String(size_t _size, const Allocator *_allocator = nullptr) { alloc(_size, _allocator); } String(const char *_str, size_t _length) { alloc(_length + 1, nullptr); memcpy(str, _str, _length); str[_length] = '\0'; length = _length; } explicit String(const std::string &_str) : String(_str.c_str(), _str.length()) {} String(const String &src) { copy(src); } String(String &&src) noexcept { move(std::move(src)); } String &operator=(const String &src) noexcept; String &operator=(String &&src) noexcept; ~String() { if (allocator && str) { allocator->free(str); } } char *value() const { return str; } size_t get_length() const { return length; } size_t capacity() const { return size; } std::string to_std_string() const { return {str, length}; } bool contains(const char *needle, size_t l_needle) const { return swoole_strnstr(str, length, needle, l_needle) != nullptr; } bool contains(const std::string &needle) const { return contains(needle.c_str(), needle.size()); } bool starts_with(const char *needle, size_t l_needle) const { if (length < l_needle) { return false; } return memcmp(str, needle, l_needle) == 0; } bool starts_with(const std::string &needle) const { return starts_with(needle.c_str(), needle.length()); } bool ends_with(const char *needle, size_t l_needle) const { if (length < l_needle) { return false; } return memcmp(str + length - l_needle, needle, l_needle) == 0; } bool ends_with(const std::string &needle) const { return ends_with(needle.c_str(), needle.length()); } bool equals(const char *data, size_t len) const { if (length != len) { return false; } return memcmp(str, data, len) == 0; } bool equals(const std::string &data) const { if (length != data.size()) { return false; } return memcmp(str, data.c_str(), length) == 0; } void grow(size_t incr_value); String substr(size_t offset, size_t len) const; bool empty() const { return str == nullptr || length == 0; } void clear() { length = 0; offset = 0; } void extend() { extend(size * 2); } void extend(size_t new_size) { assert(new_size > size); reserve(new_size); } void extend_align(size_t _new_size) { size_t align_size = SW_MEM_ALIGNED_SIZE(size * 2); while (align_size < _new_size) { align_size *= 2; } reserve(align_size); } void reserve(size_t new_size); /** * Transfer ownership of the string content pointer to the caller, who will capture this memory. * The caller must manage and free this memory; it will not free when the string is destructed. */ char *release(); void repeat(const char *data, size_t len, size_t n); void append(const char *append_str, size_t length); void append(const std::string &append_str) { append(append_str.c_str(), append_str.length()); } void append(const char c) { append(&c, sizeof(c)); } void append(int value); void append(const String &append_str); bool append_random_bytes(size_t length, bool base64 = false); void write(off_t _offset, const String &write_str); void write(off_t _offset, const char *write_str, size_t _length); void set_null_terminated() { if (length == size) { extend(length + 1); } str[length] = '\0'; } ssize_t split(const char *delimiter, size_t delimiter_length, const StringExplodeHandler &handler); void print(bool print_value = true) const; enum FormatFlag { FORMAT_APPEND = 1 << 0, FORMAT_GROW = 1 << 1, }; template size_t format_impl(int flags, const char *format, Args... args) { size_t _size = sw_snprintf(nullptr, 0, format, args...); if (_size == 0) { return 0; } // store \0 terminator ++_size; size_t new_size = (flags & FORMAT_APPEND) ? length + _size : _size; if (flags & FORMAT_GROW) { size_t align_size = SW_MEM_ALIGNED_SIZE(size * 2); while (align_size < new_size) { align_size *= 2; } new_size = align_size; } size_t n; if (flags & FORMAT_APPEND) { if (_size > size - length) { reserve(new_size); } n = sw_snprintf(str + length, size - length, format, args...); length += n; } else { if (_size > size) { reserve(new_size); } n = sw_snprintf(str, size, format, args...); length = n; } return n; } // This function replaces the entire string instead of appending content. template size_t format(const char *format, Args... args) { return format_impl(0, format, args...); } template size_t append_format(const char *format, Args... args) { return format_impl(FORMAT_APPEND, format, args...); } char *pop(size_t init_size); void reduce(off_t offset); }; inline String *make_string(size_t size, const Allocator *allocator = nullptr) { return new String(size, allocator); } } // namespace swoole ================================================ FILE: include/swoole_table.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_memory.h" #include "swoole_lock.h" #include #include #include //#define SW_TABLE_DEBUG 0 #define SW_TABLE_FORCE_UNLOCK_TIME 2000 // milliseconds #define SW_TABLE_USE_PHP_HASH #define SW_TABLE_MAX_ROW_SIZE 0x80000000 namespace swoole { typedef uint32_t TableStringLength; typedef uint64_t (*HashFunc)(const char *key, size_t len); struct TableColumn; struct TableRow { sw_atomic_t lock_; pid_t lock_pid; /** * 1:used, 0:empty */ uint8_t active; uint8_t key_len; /** * next slot */ TableRow *next; /** * Hash Key */ char key[SW_TABLE_KEY_SIZE]; char data[0]; void lock(); void unlock() { sw_spinlock_release(&lock_); } void clear() { sw_memset_zero((char *) &lock_pid, sizeof(TableRow) - offsetof(TableRow, lock_pid)); } void set_value(const TableColumn *col, const void *value, size_t vlen); void get_value(const TableColumn *col, double *dval) const; void get_value(const TableColumn *col, long *lval) const; void get_value(const TableColumn *col, char **strval, TableStringLength *strlen); }; struct TableIterator { size_t row_memory_size_; uint32_t absolute_index = 0; uint32_t collision_index = 0; TableRow *current_; Mutex *mutex_; explicit TableIterator(size_t row_size); ~TableIterator(); void reset(); void lock() const { mutex_->lock(); } void unlock() const { mutex_->unlock(); } }; enum TableFlag { SW_TABLE_FLAG_NEW_ROW = 1, SW_TABLE_FLAG_CONFLICT = 1u << 1, }; struct TableColumn { enum Type { TYPE_INT = 1, TYPE_FLOAT, TYPE_STRING, }; Type type; uint32_t size; std::string name; size_t index; TableColumn(const std::string &_name, Type _type, size_t _size); void clear(TableRow *row) const; }; class Table { std::unordered_map *column_map; Mutex *mutex; size_t size; size_t mask; size_t item_size; size_t memory_size; float conflict_proportion; /** * total rows that in active state(shm) */ sw_atomic_t row_num; TableRow **rows; FixedPool *pool; TableIterator *iterator; HashFunc hash_func; bool created; void *memory; public: std::vector *column_list; size_t conflict_count; sw_atomic_long_t insert_count; sw_atomic_long_t delete_count; sw_atomic_long_t update_count; uint32_t conflict_max_level; Table() = delete; ~Table() = delete; static Table *make(uint32_t rows_size, float conflict_proportion); size_t calc_memory_size() const; size_t get_memory_size() const; uint32_t get_available_slice_num() const; uint32_t get_total_slice_num() const; bool create(); bool add_column(const std::string &name, enum TableColumn::Type type, size_t size); TableColumn *get_column(const std::string &key) const; TableRow *set(const char *key, uint16_t keylen, TableRow **rowlock, int *out_flags); TableRow *get(const char *key, uint16_t keylen, TableRow **rowlock) const; bool exists(const char *key, uint16_t keylen) const; bool del(const char *key, uint16_t keylen); void forward() const; // release shared memory void destroy(); bool is_created() const { return created; } bool ready() const { return memory != nullptr; } void set_hash_func(HashFunc _fn) { hash_func = _fn; } size_t get_size() const { return size; } float get_conflict_proportion() const { return conflict_proportion; } size_t get_column_size() const { return column_map->size(); } TableRow *get_by_index(uint32_t index) const { TableRow *row = rows[index]; return row->active ? row : nullptr; } size_t count() const { return row_num; } bool exists(const std::string &key) const { return exists(key.c_str(), key.length()); } TableRow *current() const { return iterator->current_; } void rewind() const { iterator->lock(); iterator->reset(); iterator->unlock(); } void clear_row(TableRow *row) const { for (auto &i : *column_list) { i->clear(row); } } private: TableRow *hash(const char *key, int keylen) const { uint64_t hashv = hash_func(key, keylen); uint64_t index = hashv & mask; assert(index < size); return rows[index]; } TableRow *alloc_row() const { lock(); const auto new_row = static_cast(pool->alloc(0)); unlock(); return new_row; } void free_row(TableRow *tmp) const { lock(); tmp->clear(); pool->free(tmp); unlock(); } static void check_key_length(uint16_t *keylen) { if (*keylen >= SW_TABLE_KEY_SIZE) { *keylen = SW_TABLE_KEY_SIZE - 1; } } void init_row(TableRow *new_row, const char *key, int keylen) { sw_memset_zero(reinterpret_cast(new_row) + offsetof(TableRow, active), sizeof(TableRow) - offsetof(TableRow, active)); memcpy(new_row->key, key, keylen); new_row->key[keylen] = '\0'; new_row->key_len = keylen; new_row->active = 1; sw_atomic_fetch_add(&(row_num), 1); } int lock() const { return mutex->lock(); } int unlock() const { return mutex->unlock(); } }; } // namespace swoole ================================================ FILE: include/swoole_thread.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_lock.h" #include #include long swoole_thread_get_native_id(); bool swoole_thread_set_name(const char *name); bool swoole_thread_get_name(char *buf, size_t len); std::string swoole_thread_id_to_str(std::thread::id id); namespace swoole { class Thread { int exit_status = 0; bool living = false; std::thread thread; public: bool is_alive() const { return living; } bool joinable() const { return thread.joinable(); } void join() { thread.join(); } void detach() { thread.detach(); } int get_exit_status() const { return exit_status; } pthread_t get_id() { return thread.native_handle(); } template void start(Callable fn) { thread = std::thread(fn); } void enter() { exit_status = 0; living = true; } void exit(const int status) { exit_status = status; living = false; } }; } // namespace swoole ================================================ FILE: include/swoole_timer.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_heap.h" #include "swoole_reactor.h" #include "swoole_util.h" #include #define SW_TIMER_MIN_MS 1 #define SW_TIMER_MIN_SEC 0.001 #define SW_TIMER_MAX_MS LONG_MAX #define SW_TIMER_MAX_SEC ((double) LONG_MAX / 1000) namespace swoole { typedef std::function TimerDestructor; struct TimerNode { enum Type { TYPE_KERNEL, TYPE_PHP, }; long id; Type type; int64_t exec_msec; int64_t interval; uint64_t exec_count; uint64_t round; bool removed; HeapNode *heap_node; TimerCallback callback; void *data; TimerDestructor destructor; }; class Timer { /*--------------signal timer--------------*/ Reactor *reactor_ = nullptr; Heap heap; std::unordered_map map; uint64_t round; long _next_id; long _current_id; /*---------------event timer--------------*/ int64_t base_time; /** * The time when the next timer will trigger, in milliseconds. * This event will serve as the timeout for the event loop's epoll/poll/kqueue, * or be set as the trigger time for the system clock. */ int64_t next_msec_; /*----------------------------------------*/ std::function set; std::function close; void init(bool manually_trigger); void init_with_reactor(Reactor *reactor); void init_with_system_timer(); void release_node(TimerNode *tnode); public: explicit Timer(bool manually_trigger); ~Timer(); int64_t get_relative_msec() const { return get_absolute_msec() - base_time; } int64_t get_next_msec() const { return next_msec_; } static int64_t get_absolute_msec() { return time(true); } Reactor *get_reactor() const { return reactor_; } TimerNode *add(long _msec, bool persistent, void *data, const TimerCallback &callback); bool remove(TimerNode *tnode); void update(const TimerNode *tnode) const { heap.change_priority(tnode->exec_msec, tnode->heap_node); } void delay(TimerNode *tnode, long delay_ms) const { long now_ms = get_relative_msec(); tnode->exec_msec = (now_ms < 0 ? tnode->exec_msec : now_ms) + delay_ms; update(tnode); } void reinit(bool manually_trigger = false); void select(); TimerNode *get(long id) { const auto it = map.find(id); if (it == map.end()) { return nullptr; } return it->second; } TimerNode *get(long id, const TimerNode::Type type) { TimerNode *tnode = get(id); return (tnode && tnode->type == type) ? tnode : nullptr; } size_t count() const { return map.size(); } uint64_t get_round() const { return round; } bool remove(long id) { return remove(get(id)); } const std::unordered_map &get_map() { return map; } }; static inline long sec2msec(const long sec) { return sec * 1000; } static inline long sec2msec(const int sec) { return sec * 1000; } static inline long sec2msec(const double sec) { return static_cast(sec * 1000); } static inline double msec2sec(const int msec) { return static_cast(msec) / 1000.0; } } // namespace swoole ================================================ FILE: include/swoole_uring_socket.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_coroutine_socket.h" namespace swoole { namespace coroutine { class UringSocket : public Socket { BIO *rbio = nullptr; BIO *wbio = nullptr; bool ssl_bio_write(); bool ssl_bio_read(); bool ssl_bio_prepare(); bool ssl_bio_perform(int rc, const char *fn); ssize_t ssl_recv(void *_buf, size_t _n); ssize_t ssl_send(const void *_buf, size_t _n); ssize_t ssl_readv(network::IOVector *io_vector); ssize_t ssl_writev(network::IOVector *io_vector); ssize_t ssl_sendfile(const File &file, off_t *offset, size_t size); ssize_t uring_send(const void *_buf, size_t _n); ssize_t uring_recv(void *_buf, size_t _n); ssize_t uring_readv(const struct iovec *iovec, int count); ssize_t uring_writev(const struct iovec *iovec, int count); ssize_t uring_sendfile(const File &file, off_t *offset, size_t size); network::Socket *uring_accept(double timeout); bool is_ssl() { return !!socket->ssl; } public: UringSocket(SocketType sock_type) : Socket(sock_type) {} UringSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {} UringSocket(int _fd, int _domain, int _type, int _protocol) : Socket(_fd, _domain, _type, _protocol) {} UringSocket(int _fd, SocketType _type) : Socket(_fd, _type) {} UringSocket(network::Socket *sock, const UringSocket *server_sock) : Socket(sock, server_sock) {} bool connect(const std::string &_host, int _port = 0, int flags = 0) { return Socket::connect(_host, _port, flags); } ssize_t recvfrom(void *_buf, size_t _n) { return Socket::recvfrom(_buf, _n); } bool connect(const sockaddr *addr, socklen_t addrlen) override; UringSocket *accept(double timeout = 0); ssize_t read(void *_buf, size_t _n) override; ssize_t write(const void *_buf, size_t _n) override; ssize_t recvmsg(msghdr *msg, int flags) override; ssize_t sendmsg(const msghdr *msg, int flags) override; ssize_t recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) override; ssize_t sendto(const std::string &host, int port, const void *_buf, size_t _n) override; ssize_t recv(void *_buf, size_t _n) override; ssize_t send(const void *_buf, size_t _n) override; ssize_t recv_all(void *_buf, size_t _n) override; ssize_t send_all(const void *_buf, size_t _n) override; bool sendfile(const char *filename, off_t offset, size_t length) override; ssize_t readv(network::IOVector *io_vector) override; ssize_t readv_all(network::IOVector *io_vector) override; ssize_t writev(network::IOVector *io_vector) override; ssize_t writev_all(network::IOVector *io_vector) override; bool poll(EventType _type, double timeout = 0) override; bool ssl_handshake() override; }; } // namespace coroutine } // namespace swoole ================================================ FILE: include/swoole_util.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #define SW_STRUCT_MEMBER_SIZE(_s, _m) sizeof(std::declval()._m) namespace swoole { template bool in_range(T value, std::initializer_list allowed_values) { return std::find(allowed_values.begin(), allowed_values.end(), value) != allowed_values.end(); } namespace std_string { template inline std::string format(const char *format, Args... args) { size_t size = snprintf(nullptr, 0, format, args...) + 1; // Extra space for '\0' std::unique_ptr buf(new char[size]); snprintf(buf.get(), size, format, args...); return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside } inline std::string vformat(const char *format, va_list args) { va_list _args; va_copy(_args, args); size_t size = vsnprintf(nullptr, 0, format, _args) + 1; // Extra space for '\0' va_end(_args); std::unique_ptr buf(new char[size]); vsnprintf(buf.get(), size, format, args); return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside } } // namespace std_string // Keep parameter 'steady' as false for backward compatibility. template static inline long time(bool steady = false) { if (sw_likely(steady)) { auto now = std::chrono::steady_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } else { auto now = std::chrono::system_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } } static inline long get_timezone() { using namespace std::chrono; auto now_time_t = system_clock::to_time_t(system_clock::now()); std::tm local_tm{}; localtime_r(&now_time_t, &local_tm); return local_tm.tm_gmtoff; } class DeferTask { std::stack list_; public: void add(const Callback &fn) { list_.push(fn); } ~DeferTask() { while (!list_.empty()) { auto fn = list_.top(); fn(nullptr); list_.pop(); } } }; template class ScopeGuard { public: explicit ScopeGuard(Fun &&f) : _fun(std::forward(f)), _active(true) {} ~ScopeGuard() { if (_active) { _fun(); } } void dismiss() { _active = false; } ScopeGuard() = delete; ScopeGuard(const ScopeGuard &) = delete; ScopeGuard &operator=(const ScopeGuard &) = delete; ScopeGuard(ScopeGuard &&rhs) noexcept : _fun(std::move(rhs._fun)), _active(rhs._active) { rhs.dismiss(); } private: Fun _fun; bool _active; }; class BitMap { uint64_t *array_; size_t n_bits_; static size_t get_array_size(size_t n_bits) { return (((n_bits) + 63) / 64 * 8); } size_t get_offset(size_t i) const { assert(i < n_bits_); /* (i / 64) */ return i >> 6; } static uint64_t to_int(const size_t i, const size_t offset) { return static_cast(1) << (i - (offset << 6)); } public: explicit BitMap(const size_t n_bits) { assert(n_bits > 0); array_ = new uint64_t[get_array_size(n_bits)]; n_bits_ = n_bits; } ~BitMap() { delete[] array_; } void clear() const { memset(array_, 0, sizeof(uint64_t) * get_array_size(n_bits_)); } void set(const size_t i) const { const size_t off = get_offset(i); array_[off] |= to_int(i, off); } void unset(const size_t i) const { const size_t off = get_offset(i); array_[off] &= ~to_int(i, off); } bool get(const size_t i) const { const size_t off = get_offset(i); return array_[off] & to_int(i, off); } }; namespace detail { enum class ScopeGuardOnExit {}; template inline ScopeGuard operator+(ScopeGuardOnExit, Fun &&fn) { return ScopeGuard(std::forward(fn)); } } // namespace detail #define __SCOPE_GUARD_CONCATENATE_IMPL(s1, s2) s1##s2 #define __SCOPE_GUARD_CONCATENATE(s1, s2) __SCOPE_GUARD_CONCATENATE_IMPL(s1, s2) /** * Call the specified function when exiting the scope, similar to Golang's defer function. * After using this helper macro, * it is not necessary to manually release resources before the return statement of the failed branch. */ #define ON_SCOPE_EXIT \ auto __SCOPE_GUARD_CONCATENATE(ext_exitBlock_, __LINE__) = swoole::detail::ScopeGuardOnExit() + [&]() std::string intersection(const std::vector &vec1, std::set &vec2); static inline size_t ltrim(char **str, size_t len) { size_t i; for (i = 0; i < len; ++i) { if ('\0' != **str && isspace(**str)) { ++*str; } else { break; } } return len - i; } static inline size_t rtrim(char *str, size_t len) { for (size_t i = len; i > 0;) { if (isspace(str[--i])) { str[i] = 0; len--; } else { break; } } return len; } static inline size_t rtrim(const char *str, size_t len) { for (size_t i = len; i > 0;) { if (isspace(str[--i])) { len--; } else { break; } } return len; } static inline ssize_t substr_len(const char *str, size_t len, char separator, bool before = false) { const auto substr = static_cast(memchr(str, separator, len)); if (substr == nullptr) { return -1; } return before ? substr - str : str + len - substr - 1; } static inline bool starts_with(const char *haystack, size_t l_haystack, const char *needle, size_t l_needle) { if (l_needle > l_haystack) { return false; } return memcmp(haystack, needle, l_needle) == 0; } static inline bool starts_with(const std::string &str, const std::string &prefix) { if (prefix.size() > str.size()) { return false; } return std::equal(prefix.begin(), prefix.end(), str.begin()); } static inline bool ends_with(const char *haystack, size_t l_haystack, const char *needle, size_t l_needle) { if (l_needle > l_haystack) { return false; } return memcmp(haystack + l_haystack - l_needle, needle, l_needle) == 0; } } // namespace swoole ================================================ FILE: include/swoole_version.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Twosee | +----------------------------------------------------------------------+ */ #ifndef SWOOLE_VERSION_H_ #define SWOOLE_VERSION_H_ #define SWOOLE_MAJOR_VERSION 6 #define SWOOLE_MINOR_VERSION 2 #define SWOOLE_RELEASE_VERSION 0 #define SWOOLE_EXTRA_VERSION "" #define SWOOLE_VERSION "6.2.0" #define SWOOLE_VERSION_ID 60200 #define SWOOLE_API_VERSION_ID 0x202208a #define SWOOLE_BUG_REPORT \ "A process crash occurred in Swoole-v" SWOOLE_VERSION ". Please report this issue.\n" \ "You can refer to the documentation below, submit an issue to us on GitHub.\n" \ ">> https://github.com/swoole/swoole-src/blob/master/docs/ISSUE.md\n" #endif ================================================ FILE: include/swoole_websocket.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #pragma once #include "swoole_http.h" #define SW_WEBSOCKET_SEC_KEY_LEN 16 #define SW_WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define SW_WEBSOCKET_HEADER_LEN 2 #define SW_WEBSOCKET_MASK_LEN 4 #define SW_WEBSOCKET_MASK_DATA "258E" #define SW_WEBSOCKET_EXT16_MAX_LEN 0xFFFF #define SW_WEBSOCKET_EXT16_LENGTH 0x7E #define SW_WEBSOCKET_EXT64_LENGTH 0x7F #define SW_WEBSOCKET_CLOSE_CODE_LEN 2 #define SW_WEBSOCKET_CLOSE_REASON_MAX_LEN 125 #define SW_WEBSOCKET_OPCODE_MAX swoole::websocket::OPCODE_PONG #define SW_WEBSOCKET_FRAME_HEADER_SIZE (SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_MASK_LEN + sizeof(uint64_t)) #define SW_WEBSOCKET_DEFAULT_PAYLOAD_SIZE 1024 namespace swoole { namespace websocket { enum Status { STATUS_NONE = 0, STATUS_CONNECTION = 1, STATUS_HANDSHAKE = 2, STATUS_ACTIVE = 3, STATUS_CLOSING = 4, STATUS_HANDSHAKE_FAILED = 5, }; enum Flag { FLAG_FIN = 1 << 0, /* BC: must be 1 */ FLAG_COMPRESS = 1 << 1, // readonly for user FLAG_RSV1 = 1 << 2, FLAG_RSV2 = 1 << 3, FLAG_RSV3 = 1 << 4, FLAG_MASK = 1 << 5, // for encoder/decoder FLAG_ENCODE_HEADER_ONLY = 1 << 6, FLAGS_ALL = /* used to prevent overflow */ FLAG_FIN | FLAG_RSV1 | FLAG_RSV2 | FLAG_RSV3 | FLAG_MASK | FLAG_COMPRESS }; struct Header { /** * fin:1 rsv1:1 rsv2:1 rsv3:1 opcode:4 */ uchar OPCODE : 4; uchar RSV3 : 1; uchar RSV2 : 1; uchar RSV1 : 1; uchar FIN : 1; uchar LENGTH : 7; uchar MASK : 1; }; struct Frame { Header header; char mask_key[SW_WEBSOCKET_MASK_LEN]; uint16_t header_length; size_t payload_length; char *payload; uchar get_flags() const { uchar flags = 0; if (header.FIN) { flags |= FLAG_FIN; } if (header.RSV1) { flags |= FLAG_RSV1; } if (header.RSV2) { flags |= FLAG_RSV2; } if (header.RSV3) { flags |= FLAG_RSV3; } if (header.MASK) { flags |= FLAG_MASK; } return flags; } bool compressed() const { return header.RSV1; } }; #define WEBSOCKET_VERSION 13 enum Opcode { OPCODE_CONTINUATION = 0x0, OPCODE_TEXT = 0x1, OPCODE_BINARY = 0x2, OPCODE_CLOSE = 0x8, OPCODE_PING = 0x9, OPCODE_PONG = 0xa, }; enum CloseReason { CLOSE_NORMAL = 1000, CLOSE_GOING_AWAY = 1001, CLOSE_PROTOCOL_ERROR = 1002, CLOSE_DATA_ERROR = 1003, CLOSE_STATUS_ERROR = 1005, CLOSE_ABNORMAL = 1006, CLOSE_MESSAGE_ERROR = 1007, CLOSE_POLICY_ERROR = 1008, CLOSE_MESSAGE_TOO_BIG = 1009, CLOSE_EXTENSION_MISSING = 1010, CLOSE_SERVER_ERROR = 1011, CLOSE_SERVICE_RESTART = 1012, CLOSE_TRY_AGAIN_LATER = 1013, CLOSE_BAD_GATEWAY = 1014, CLOSE_TLS = 1015, }; static inline uint16_t get_ext_flags(uchar opcode, uchar flags) { uint16_t ext_flags = opcode; ext_flags = ext_flags << 8; ext_flags += flags; return ext_flags; } static inline uchar set_flags(uchar fin, uchar mask, uchar rsv1, uchar rsv2, uchar rsv3) { uchar flags = 0; if (fin) { flags |= FLAG_FIN; } if (mask) { flags |= FLAG_MASK; } if (rsv1) { flags |= FLAG_RSV1; } if (rsv2) { flags |= FLAG_RSV2; } if (rsv3) { flags |= FLAG_RSV3; } return flags; } bool encode(String *buffer, const char *data, size_t length, uint8_t opcode, uint8_t flags); bool decode(Frame *frame, char *data, size_t length); void mask(char *data, size_t len, const char *mask_key); bool pack_close_frame(String *buffer, int code, const char *reason, size_t length, uint8_t flags); void print_frame(const Frame *frame); static inline bool decode(Frame *frame, const String *str) { return decode(frame, str->str, str->length); } static inline void parse_ext_flags(uint16_t ext_flags, uchar *opcode, uchar *flags) { *opcode = (uchar) (ext_flags >> 8); *flags = (uchar) (ext_flags & 0xFF); } ssize_t get_package_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); int dispatch_frame(const Protocol *protocol, network::Socket *conn, const RecvData *rdata); } // namespace websocket } // namespace swoole ================================================ FILE: package.xml ================================================ swoole pecl.php.net Event-driven asynchronous and concurrent networking engine with high performance for PHP. Event-driven asynchronous and concurrent networking engine with high performance for PHP. - event-driven - coroutine - asynchronous non-blocking - multi-thread reactor - multi-process worker - multi-protocol - millisecond timer - built-in tcp/http/websocket/http2 server - coroutine tcp/http/websocket client - coroutine read/write file system - coroutine dns lookup - support IPv4/IPv6/UnixSocket/TCP/UDP - support SSL/TLS encrypted transmission Tianfeng Han tianfenghan rango@swoole.com yes Twosee twosee twosee@php.net yes Shen Zhe shenzhe shenzhe163@gmail.com no Lu Fei lufei lufei@php.net yes Bruce Dou doubaokun doubaokun@php.net yes 2026-03-10 6.2.0 6.0 stable stable Apache2.0 - Added coroutine-based `FTP` client. By including the `--enable-swoole-ftp` option during compilation, coroutine support for `FTP` operations can be enabled to avoid network blocking. - Added coroutine-based `SSH` client. By including the `--enable-swoole-ssh` option during compilation, coroutine support for `SSH` operations can be enabled to improve concurrency efficiency. - Added support for `io_uring` in the `HTTP` coroutine server. The `HTTP` coroutine server can now utilize the high-performance `io_uring` event mechanism. Enable it by adding the `--enable-uring_socket` option during compilation for better I/O performance. - Added the `Swoole\RemoteObject\Server` module, providing transparent coroutine operation support for `MongoDB`. - Added the `Swoole\Coroutine::setTimeLimit()` function to control coroutine execution timeout, preventing coroutines from occupying resources for extended periods. - Added `URL` rewriting support for the `HTTP` static file server. - Added coroutine support for `pdo_firebird`. - Added support for `PHP 8.5`. - Added coroutine support for the `gethostbyname` function. - Swoole\Coroutine::cancel now supports canceling iouring operations. - Optimized the implementation of `Server::shutdown()`, replacing signal communication with pipe communication in `Process` mode. - In the callback functions of Swoole's HTTP servers (including Swoole\Http\Server, Swoole\Http2\Server, and Swoole\Coroutine\Http\Server), the `server` property of the `Swoole\Http\Request` object now includes a new `server_addr` field, which identifies the server's IP address. - `SSH` and `FTP` coroutine clients cannot coexist with PHP's `ext-ssh` and `ext-ftp`. To enable these features, the PHP `ssh` and `ftp` extensions must be disabled. - Fixed an issue where the HTTP2 server session was released multiple times in a multi-threaded environment. - Fixed an issue in version `8.5` where `refresh_memory_manager()` must be executed after forking a child process or creating a child thread; otherwise, the program would crash. - Fixed an issue where the `swoole_get_local_mac` function did not work correctly on `macOS` systems. - Fixed a potential crash issue that could occur when users manually suspended coroutines in a multi-threaded environment. - Fixed compilation errors on Alpine systems. - Fixed a memory leak issue when function hooking failed. - Fixed thread safety issues during the coroutinization process of `pdo_sqlite` and `pdo_oci`. - Fixed the `sw_php_print_backtrace` function to ensure its output format matches the backtrace information format of PHP's built-in functions. - Fixed compilation failures caused by enabling --enable-uring-socket, --enable-trace-log, and --enable-error-log. - Fixed the issue where `pdo_firebird` could not be compiled due to undefined macros. - Executing `php --ri swoole` now outputs version information for `libpq`. - Optimized logic related to `io_uring` in file coroutine operations. - Added PHP call stack information output when event addition fails. - Optimized the implementation code for the `swoole_get_local_ip` and `swoole_get_local_mac` functions. - Optimized the coroutine waiting time for file locks to prevent exponentially increasing sleep times from making locks increasingly difficult to acquire. - Improved compatibility with the `Android` platform. - Removed the `--enable-openssl` compilation option; support for `OpenSSL` is now included by default. - `--enable-uring-socket` requires explicitly specifying either `--enable-iouring` or `--with-liburing-dir`. - `PHP 8.1` is no longer supported. 8.2.0 8.5.99 1.4.0 swoole ================================================ FILE: php-cs-fix ================================================ #/bin/bash ./tests/include/lib/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose $1 ================================================ FILE: php_swoole.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_H #define PHP_SWOOLE_H #ifdef __cplusplus extern "C" { #endif #include "php.h" #include "php_ini.h" #include "php_globals.h" #include "php_main.h" #include "php_streams.h" #include "php_network.h" #include "zend_variables.h" #include "zend_interfaces.h" #include "zend_closures.h" #include "zend_exceptions.h" #include "zend_attributes.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __cplusplus } #endif extern zend_module_entry swoole_module_entry; #define phpext_swoole_ptr &swoole_module_entry PHP_MINIT_FUNCTION(swoole); PHP_MSHUTDOWN_FUNCTION(swoole); PHP_RINIT_FUNCTION(swoole); PHP_RSHUTDOWN_FUNCTION(swoole); PHP_MINFO_FUNCTION(swoole); // clang-format off ZEND_BEGIN_MODULE_GLOBALS(swoole) zend_bool display_errors; zend_bool cli; zend_bool use_shortname; zend_bool enable_preemptive_scheduler; zend_bool enable_library; zend_bool enable_fiber_mock; zend_bool blocking_detection; zend_long blocking_threshold; zend_bool profile; zend_bool leak_detection; zend_long socket_buffer_size; int req_status; HashTable *in_autoload; ZEND_END_MODULE_GLOBALS(swoole) // clang-format on extern ZEND_DECLARE_MODULE_GLOBALS(swoole); #ifdef ZTS #define SWOOLE_G(v) TSRMG(swoole_globals_id, zend_swoole_globals *, v) #else #define SWOOLE_G(v) (swoole_globals.v) #endif #endif /* PHP_SWOOLE_H */ ================================================ FILE: php_swoole_api.h ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #ifndef PHP_SWOOLE_API_H #define PHP_SWOOLE_API_H #ifdef __cplusplus extern "C" { #endif #include "php.h" #include "php_network.h" int php_async_socket_connect_to_host(const char *host, unsigned short port, int socktype, int asynchronous, struct timeval *timeout, zend_string **error_string, int *error_code, const char *bindto, unsigned short bindport, long sockopts); int php_async_socket_poll(php_socket_t fd, int events, int timeout); #if PHP_VERSION_ID < 80300 static inline const char *zend_zval_value_name(const zval *arg) { ZVAL_DEREF(arg); if (Z_ISUNDEF_P(arg)) { return "null"; } if (Z_TYPE_P(arg) == IS_OBJECT) { return ZSTR_VAL(Z_OBJCE_P(arg)->name); } else if (Z_TYPE_P(arg) == IS_FALSE) { return "false"; } else if (Z_TYPE_P(arg) == IS_TRUE) { return "true"; } return zend_get_type_by_const(Z_TYPE_P(arg)); } #endif #ifdef __cplusplus } #endif #endif /* PHP_SWOOLE_API_H */ ================================================ FILE: run-core-tests.sh ================================================ #!/bin/bash __DIR__=$(cd "$(dirname "$0")" || exit;pwd) export ASAN_OPTIONS=detect_leaks=0 sudo sysctl -w kernel.randomize_va_space=0 ipcs -q cd "${__DIR__}"/core-tests/js || exit 1 npm install cd "${__DIR__}" || exit 1 tasks=$(./bin/core-tests --gtest_list_tests | awk '/\./') || exit 255 for task in $tasks; do execute_command="./bin/core-tests" if [ "$task" = "log." ]; then $execute_command --gtest_filter="$task"* else sudo -E "$execute_command" --gtest_filter="$task"* fi if [ $? -ne 0 ]; then exit 255 fi done ================================================ FILE: scripts/.gitignore ================================================ data ================================================ FILE: scripts/clear.sh ================================================ #!/bin/sh __DIR__=$(cd "$(dirname "$0")";pwd) __SRC_DIR__=$(cd "$(dirname "${__DIR__}")";pwd) set -e cd "${__SRC_DIR__}" set +e find . \( -name \*.gcno -o -name \*.gcda \) -print0 | xargs -0 rm -f find . \( -name \*.lo -o -name \*.o \) -print0 | xargs -0 rm -f find . \( -name \*.la -o -name \*.a \) -print0 | xargs -0 rm -f find . \( -name \*.dep \) -print0 | xargs -0 rm -f find . -name \*.so -print0 | xargs -0 rm -f find . -name .libs -a -type d -print0 | xargs -0 rm -rf rm -f libphp.la modules/* libs/* ================================================ FILE: scripts/code-format.sh ================================================ #!/bin/sh __DIR__=$(cd "$(dirname "$0")";pwd) __SRC_DIR__=$(cd "$(dirname "${__DIR__}")";pwd) cd $__SRC_DIR__ ## core source file clang-format -i src/core/*.cc clang-format -i src/coroutine/*.cc clang-format -i src/lock/*.cc clang-format -i src/memory/*.cc clang-format -i src/network/*.cc clang-format -i src/os/*.cc clang-format -i src/pipe/*.cc clang-format -i src/protocol/*.cc clang-format -i src/reactor/*.cc clang-format -i src/server/*.cc clang-format -i src/wrapper/*.cc ## core header file clang-format -i include/*.h ## ext source file clang-format -i *.cc clang-format -i *.h ## examples clang-format -i examples/cpp/*.cc ## core-tests source file clang-format -i core-tests/src/_lib/*.cpp clang-format -i core-tests/src/client/*.cpp clang-format -i core-tests/src/core/*.cpp clang-format -i core-tests/src/coroutine/*.cpp clang-format -i core-tests/src/lock/*.cpp clang-format -i core-tests/src/memory/*.cpp clang-format -i core-tests/src/network/*.cpp clang-format -i core-tests/src/os/*.cpp clang-format -i core-tests/src/process/*.cpp clang-format -i core-tests/src/protocol/*.cpp clang-format -i core-tests/src/reactor/*.cpp clang-format -i core-tests/src/server/*.cpp clang-format -i core-tests/src/main.cpp ================================================ FILE: scripts/code-stats.sh ================================================ #!/bin/sh -e __CURRENT__=$(pwd) __DIR__=$(cd "$(dirname "$0")";pwd) # enter the dir cd "${__DIR__}/.." cloc . --exclude-dir=thirdparty,Debug,CMakeFiles,build,.git \ --fullpath \ --not-match-d='tools/vendor' \ --not-match-d='tests/include/lib/vendor' \ --not-match-f='ext-src/php_swoole_library\.h$' ================================================ FILE: scripts/debug/swoole_info.php ================================================ swoole_version(), 'cpu_num' => swoole_cpu_num(), 'local_mac' => swoole_get_local_mac(), 'local_ip' => swoole_get_local_ip() ]); ================================================ FILE: scripts/debug/swoole_table_implements.php ================================================ /etc/ld.so.conf.d/odbc-mariadb.conf ldconfig wget -nv -O instantclient-basiclite-linux${arch}.zip https://download.oracle.com/otn_software/linux/instantclient/1930000/instantclient-basiclite-linux.${arch}-19.30.0.0.0dbru.zip unzip instantclient-basiclite-linux${arch}.zip && rm instantclient-basiclite-linux${arch}.zip wget -nv -O instantclient-sdk-linux${arch}.zip https://download.oracle.com/otn_software/linux/instantclient/1930000/instantclient-sdk-linux.${arch}-19.30.0.0.0dbru.zip unzip instantclient-sdk-linux${arch}.zip && rm instantclient-sdk-linux${arch}.zip mv instantclient_*_* ./instantclient rm ./instantclient/sdk/include/ldap.h # fix debug build warning: zend_signal: handler was replaced for signal (2) after startup echo DISABLE_INTERRUPT=on > ./instantclient/network/admin/sqlnet.ora mv ./instantclient /usr/local/ echo '/usr/local/instantclient' > /etc/ld.so.conf.d/oracle-instantclient.conf ldconfig cd "${__DIR__}/" bash ./install-liburing.sh cd - ================================================ FILE: scripts/make.sh ================================================ #!/bin/sh __CURRENT_DIR__=$(cd "$(dirname "$0")";pwd) __DIR__=$(cd "$(dirname "${__CURRENT_DIR__}")";pwd) __HAVE_ZTS__=$(php -v|grep ZTS) COMPILE_PARAMS=" \ --enable-sockets \ --enable-mysqlnd \ --enable-swoole-curl \ --enable-cares \ --enable-zstd \ --enable-swoole-pgsql \ --enable-swoole-stdext \ --with-swoole-firebird \ --enable-uring-socket \ --with-swoole-ssh2 \ --enable-swoole-ftp \ --with-swoole-odbc=unixODBC,/usr \ --enable-swoole-sqlite" TEMP=$(getopt -o ad --long asan,debug:,oci -n "$0" -- "$@") if [ $? != 0 ]; then echo "Parameter parsing failed!" >&2 exit 1 fi eval set -- "$TEMP" while true; do case "$1" in -a|--asan) ASAN=true shift ;; -d|--debug) DEBUG=true shift ;; --oci) OCI=true shift ;; --) shift break ;; *) echo "Unsupported parameters!" exit 1 ;; esac done if [ "$ASAN" = true ]; then COMPILE_PARAMS="$COMPILE_PARAMS --enable-asan" fi if [ "$DEBUG" = true ]; then COMPILE_PARAMS="$COMPILE_PARAMS --enable-debug" fi if [ "$OCI" = true ]; then COMPILE_PARAMS="$COMPILE_PARAMS --with-swoole-oracle=instantclient,/usr/local/instantclient" fi if [ -n "$__HAVE_ZTS__" ]; then COMPILE_PARAMS="$COMPILE_PARAMS --enable-swoole-thread" fi if [ "$(uname)" = "Linux" ]; then COMPILE_PARAMS="$COMPILE_PARAMS --enable-iouring" fi if [ "$(uname | grep -i darwin)"x != ""x ]; then CPU_COUNT="$(sysctl -n machdep.cpu.core_count)" else CPU_COUNT="$(/usr/bin/nproc)" fi if [ -z ${CPU_COUNT} ]; then CPU_COUNT=4 fi cd "${__DIR__}" if [ "$1" = "cmake" ] ;then phpize ./configure ${COMPILE_PARAMS} cmake . make -j ${CPU_COUNT} make install exit 0 fi if [ "$1" = "clean" ] ;then make clean phpize --clean exit 0 fi if [ "$1" = "install-module" ] ;then make ext-swoole __EXT_DIR__=$(php-config --extension-dir) cp lib/swoole.so "${__EXT_DIR__}" echo "cp lib/swoole.so ${__EXT_DIR__}" exit 0 fi if [ "$1" = "library" ] ;then set -e cd ${__DIR__} set +e echo "rm ext-src/php_swoole.lo" rm -f ext-src/php_swoole.lo echo "rm ext-src/php_swoole_library.h" rm -f ext-src/php_swoole_library.h set -e if [ "$2" = "dev" ] ;then /usr/bin/env php tools/build-library.php dev else /usr/bin/env php tools/build-library.php fi echo "remake..." make echo "done" exit 0 fi if [ "$1" = "help" ] ;then echo "./make.sh cmake" echo "./make.sh install-module" echo "./make.sh clean" echo "./make.sh debug" echo "./make.sh trace" echo "./make.sh library [dev]" echo "./make.sh" exit 0 fi phpize if [ "$1" = "debug" ] ;then ./configure ${COMPILE_PARAMS} --enable-debug-log elif [ "$1" = "trace" ] ;then ./configure ${COMPILE_PARAMS} --enable-trace-log elif [ "$1" = "config" ] ;then ./configure ${COMPILE_PARAMS} exit 0 else ./configure ${COMPILE_PARAMS} fi make clean make -j ${CPU_COUNT} make install ================================================ FILE: scripts/pecl-install.sh ================================================ #!/bin/sh -e __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) cd ${__DIR__} && cd ../ && \ pecl config-show && \ php tools/pecl-package.php && package_file="`ls | grep swoole-*tgz`" && \ echo "\n" | pecl install -f ${package_file} | tee pecl.log && \ cat pecl.log | grep "successfully" && \ php -d extension=swoole --ri swoole && \ pecl uninstall swoole && \ rm -f pecl.log ================================================ FILE: scripts/rename.php ================================================ &1 | grep "version"`"x = ""x ]; then echo "\n❌ Docker not found!" exit 1 elif [ "`docker ps 2>&1 | grep Cannot`"x != ""x ]; then echo "\n❌ Docker is not running!" exit 1 else which "docker-compose" > /dev/null if [ $? -ne 0 ]; then echo "\n🤔 Can not found docker-compose, try to install it now...\n" curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose && \ chmod +x docker-compose && \ sudo mv docker-compose /usr/local/bin which "docker-compose" > /dev/null if [ $? -ne 0 ]; then echo "\n❌ Install docker-compose failed!" exit 1 fi docker -v && docker-compose -v fi fi } create_docker_images(){ arch=`uname -m` if [ "$arch" = "aarch64" ]; then echo "\n 📢 create golang-h2demo aarch64 docker image" git clone https://github.com/swoole/golang-h2demo.git apt install -y golang cd ./golang-h2demo && GOOS=linux GOARCH=arm64 go build -o h2demo . && docker build . -t phpswoole/golang-h2demo && cd - echo "\n 📢 create ${PHP_VERSION} aarch64 docker image" git clone https://github.com/swoole/php-docker.git cd php-docker cd ${PHP_VERSION} && sed -i '/odbc-mariadb \\/d' Dockerfile && docker build . -t phpswoole/php:${PHP_VERSION} && cd - cd ../ fi } prepare_data_files(){ cd ${__DIR__} && \ remove_data_files && \ mkdir -p \ data \ data/run \ data/mysql data/run/mysqld \ data/redis data/run/redis && \ chmod -R 777 data if [ $? -ne 0 ]; then echo "\n❌ Prepare data files failed!" exit 1 fi } remove_data_files(){ cd ${__DIR__} && \ rm -rf scripts/data } start_docker_containers(){ remove_docker_containers cd ${__DIR__} && \ docker-compose up -d && \ docker ps -a if [ $? -ne 0 ]; then echo "\n❌ Create containers failed!" exit 1 fi } remove_docker_containers(){ cd ${__DIR__} && \ docker-compose kill > /dev/null 2>&1 && \ docker-compose rm -f > /dev/null 2>&1 } run_tests_in_docker(){ docker exec swoole touch /.cienv && \ docker exec swoole /swoole-src/scripts/docker-route.sh $SWOOLE_CI_TYPE code=$? if [ $code -ne 0 ]; then echo "\n❌ Run tests failed! ExitCode: $code" exit 1 fi } remove_tests_resources(){ remove_docker_containers remove_data_files } check_docker_dependency create_docker_images echo "\n📖 Prepare for files...\n" prepare_data_files echo "📦 Start docker containers...\n" start_docker_containers # && trap "remove_tests_resources" echo "\n⏳ Run tests in docker...\n" run_tests_in_docker echo "\n🚀🚀🚀Completed successfully🚀🚀🚀\n" ================================================ FILE: scripts/run-tests.sh ================================================ #!/bin/sh -e __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) [ -z "${SWOOLE_BRANCH}" ] && export SWOOLE_BRANCH="master" #-------------PHPT------------- cd ${__DIR__} && cd ../tests/ # initialization echo "" && echo "⭐️ Initialization for tests..." && echo "" if [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then echo "run in macOS, skip init database" else php ./init fi cd ./include/lib echo "composer update" composer update cd - echo "" # debug for debug_file in ${__DIR__}/debug/*.php do if test -f "${debug_file}";then debug_file_basename="`basename ${debug_file}`" echo "" && echo "====== RUN ${debug_file_basename} ======" && echo "" php "${debug_file}" echo "" && echo "========================================" && echo "" fi done # run tests @params($1=list_file, $2=options) run_tests(){ ./start.sh \ "`tr '\n' ' ' < ${1} | xargs`" \ -w ${1} \ ${2} } has_failures(){ cat tests.list } should_exit_with_error(){ if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then set +e find ./ -type f -name "*.mem" set -e else has_failures fi } touch tests.list trap "rm -f tests.list; echo ''; echo '⌛ Done on '`date "+%Y-%m-%d %H:%M:%S"`;" EXIT cpu_num="$(/usr/bin/env php -r "echo swoole_cpu_num() * 2;")" #if [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then # options="" #else # options="-j${cpu_num}" #fi options="-j${cpu_num}" echo "" && echo "🌵️️ Current branch is ${SWOOLE_BRANCH}" && echo "" if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then dir="base" options="${options} -m" elif [ "$SWOOLE_THREAD" = 1 ]; then dir="swoole_thread" elif [ "$SWOOLE_IOURING" = 1 ]; then dir="swoole_runtime/file_hook swoole_iouring swoole_http_client_coro/download_filename_bug.phpt" elif [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then dir="swoole_atomic swoole_coroutine swoole_coroutine_wait_group swoole_global swoole_http_server swoole_process_pool swoole_server_port \ swoole_websocket_server swoole_channel_coro swoole_coroutine_lock swoole_curl swoole_http2_client_coro swoole_http_server_coro \ swoole_redis_server swoole_socket_coro swoole_client_async swoole_coroutine_scheduler swoole_event swoole_http2_server \ swoole_runtime swoole_table swoole_client_coro swoole_coroutine_system swoole_feature swoole_http2_server_coro swoole_library swoole_server \ swoole_client_sync swoole_coroutine_util swoole_function swoole_http_client_coro swoole_lock swoole_process swoole_server_coro swoole_timer" else dir="swoole_*" fi echo "${dir}" echo "${dir}" > tests.list for i in 1 2 3 4 5 do if [ "`has_failures`" ]; then if [ ${i} -gt "1" ]; then sleep ${i} echo "" && echo "😮 Retry failed tests#${i}:" && echo "" fi cat tests.list timeout=`echo | expr ${i} \* 15 + 15` options="${options} --set-timeout ${timeout}" run_tests tests.list "${options}" else break fi done if [ "`should_exit_with_error`" ]; then exit 255 fi ================================================ FILE: scripts/simple-compile-on-github.sh ================================================ #!/bin/sh -e # shellcheck disable=SC2034 # shellcheck disable=SC2006 __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) if [ "${GITHUB_ACTIONS}" = true ]; then # shellcheck disable=SC2028 echo "\n❌ This script is just for Github!" exit 255 fi sudo apt-get update -y sudo apt-get install -y libcurl4-openssl-dev libc-ares-dev cd "${__DIR__}" && cd ../ && \ ./clear.sh > /dev/null && \ phpize --clean > /dev/null && \ phpize > /dev/null && \ ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares > /dev/null && \ make -j8 > /dev/null | tee /tmp/compile.log && \ (test "`cat /tmp/compile.log`"x = ""x || exit 255) && \ make install && \ php --ri curl && \ php -d extension=swoole.so --ri swoole ================================================ FILE: scripts/simple-compile.sh ================================================ #!/bin/sh -e __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) cd ${__DIR__} && cd ../ && \ ./clear.sh > /dev/null && \ phpize --clean > /dev/null && \ phpize > /dev/null && \ ./configure > /dev/null && \ make -j8 > /dev/null | tee /tmp/compile.log && \ (test "`cat /tmp/compile.log`"x = ""x || exit 255) && \ make install && \ echo "\n[swoole]\nextension=swoole.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini ================================================ FILE: src/core/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include #include #include #include #include #ifdef __MACH__ #include #endif #include #include #include #include #include "swoole_string.h" #include "swoole_signal.h" #include "swoole_memory.h" #include "swoole_protocol.h" #include "swoole_util.h" #include "swoole_async.h" #include "swoole_coroutine_system.h" #include "swoole_ssl.h" #include "swoole_api.h" #include "swoole_coroutine_api.h" using swoole::Logger; using swoole::NameResolver; using swoole::String; using swoole::coroutine::System; swoole::Global SwooleG = {}; thread_local swoole::ThreadGlobal SwooleTG = {}; thread_local char sw_error[SW_ERROR_MSG_SIZE]; std::mutex sw_thread_lock; static void swoole_fatal_error_impl(int code, const char *format, ...); swoole::Logger *sw_logger() { return SwooleG.logger; } void *sw_malloc(size_t size) { return SwooleG.std_allocator.malloc(size); } void sw_free(void *ptr) { return SwooleG.std_allocator.free(ptr); } void *sw_calloc(size_t nmemb, size_t size) { return SwooleG.std_allocator.calloc(nmemb, size); } void *sw_realloc(void *ptr, size_t size) { return SwooleG.std_allocator.realloc(ptr, size); } static void bug_report_message_init() { SwooleG.bug_report_message += "\n" + std::string(SWOOLE_BUG_REPORT) + "\n"; utsname u; if (uname(&u) != -1) { SwooleG.bug_report_message += swoole::std_string::format("OS: %s %s %s %s\n", u.sysname, u.release, u.version, u.machine); } #ifdef __VERSION__ SwooleG.bug_report_message += swoole::std_string::format("GCC_VERSION: %s\n", __VERSION__); #endif SwooleG.bug_report_message += swoole_ssl_get_version_message(); } void swoole_init() { if (SwooleG.init) { return; } SwooleG = {}; sw_memset_zero(sw_error, SW_ERROR_MSG_SIZE); SwooleG.running = 1; SwooleG.init = 1; SwooleG.enable_coroutine = 1; SwooleG.std_allocator = {malloc, calloc, realloc, free}; SwooleG.stdout_ = stdout; SwooleG.fatal_error = swoole_fatal_error_impl; SwooleG.cpu_num = SW_MAX(1, sysconf(_SC_NPROCESSORS_ONLN)); SwooleG.pagesize = getpagesize(); SwooleG.max_file_content = SW_MAX_FILE_CONTENT; // DNS options SwooleG.dns_tries = 1; SwooleG.dns_resolvconf_path = SW_DNS_RESOLV_CONF; // get system uname uname(&SwooleG.uname); // random seed srandom(time(nullptr)); if (!SwooleG.logger) { SwooleG.logger = new Logger(); } swoole_thread_init(true); #ifdef SW_DEBUG sw_logger()->set_level(0); SwooleG.trace_flags = 0x7fffffff; #else sw_logger()->set_level(SW_LOG_INFO); #endif // init global shared memory SwooleG.memory_pool = new swoole::GlobalMemory(SW_GLOBAL_MEMORY_PAGESIZE, true); SwooleG.max_sockets = SW_MAX_SOCKETS_DEFAULT; rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) < 0) { swoole_sys_warning("getrlimit() failed"); } else { SwooleG.max_sockets = SW_MAX((uint32_t) rlmt.rlim_cur, SW_MAX_SOCKETS_DEFAULT); SwooleG.max_sockets = SW_MIN((uint32_t) rlmt.rlim_cur, SW_SESSION_LIST_SIZE); } SwooleG.task_tmpfile = SW_TASK_TMP_DIR "/" SW_TASK_TMP_FILE; // init signalfd #ifdef HAVE_SIGNALFD swoole_signalfd_init(); SwooleG.enable_signalfd = 1; #endif // init bug report message bug_report_message_init(); } SW_EXTERN_C_BEGIN SW_API void swoole_add_hook(enum swGlobalHookType type, swHookFunc func, int push_back) { assert(type <= SW_GLOBAL_HOOK_END); swoole::hook_add(SwooleG.hooks, type, func, push_back); } SW_API void swoole_call_hook(enum swGlobalHookType type, void *arg) { assert(type <= SW_GLOBAL_HOOK_END); swoole::hook_call(SwooleG.hooks, type, arg); } SW_API bool swoole_isset_hook(enum swGlobalHookType type) { assert(type <= SW_GLOBAL_HOOK_END); return SwooleG.hooks[type] != nullptr; } SW_API const char *swoole_version(void) { return SWOOLE_VERSION; } SW_API int swoole_version_id(void) { return SWOOLE_VERSION_ID; } SW_API int swoole_api_version_id(void) { return SWOOLE_API_VERSION_ID; } SW_EXTERN_C_END void swoole_clean() { SW_LOOP_N(SW_MAX_HOOK_TYPE) { if (SwooleG.hooks[i]) { auto hooks = static_cast *>(SwooleG.hooks[i]); delete hooks; } } swoole_signal_clear(); swoole_thread_clean(true); if (SwooleG.logger) { SwooleG.logger->close(); } delete SwooleG.logger; delete SwooleG.memory_pool; SwooleG = {}; } SW_API void swoole_set_log_level(int level) { if (sw_logger()) { sw_logger()->set_level(level); } } SW_API void swoole_set_stdout_stream(FILE *fp) { SwooleG.stdout_ = fp; } SW_API FILE *swoole_get_stdout_stream() { return SwooleG.stdout_; } SW_API int swoole_get_log_level() { if (sw_logger()) { return sw_logger()->get_level(); } else { return SW_LOG_NONE; } } SW_API void swoole_set_log_file(const char *file) { if (sw_logger()) { sw_logger()->open(file); } } SW_API void swoole_set_trace_flags(long flags) { SwooleG.trace_flags = flags; } SW_API void swoole_set_print_backtrace_on_error(bool enable) { SwooleG.print_backtrace_on_error = enable; } bool swoole_set_task_tmpdir(const std::string &dir) { #ifdef SW_THREAD std::unique_lock _lock(sw_thread_lock); #endif if (dir.at(0) != '/') { swoole_warning("wrong absolute path '%s'", dir.c_str()); return false; } if (access(dir.c_str(), R_OK) < 0 && !swoole_mkdir_recursive(dir)) { swoole_warning("create task tmp dir('%s') failed", dir.c_str()); return false; } sw_tg_buffer()->format("%s/" SW_TASK_TMP_FILE, dir.c_str()); SwooleG.task_tmpfile = sw_tg_buffer()->to_std_string(); if (SwooleG.task_tmpfile.length() >= SW_TASK_TMP_PATH_SIZE) { swoole_warning("task tmp_dir is too large, the max size is '%d'", SW_TASK_TMP_PATH_SIZE - 1); return false; } return true; } const std::string &swoole_get_task_tmpdir() { return SwooleG.task_tmpfile; } pid_t swoole_fork_exec(const std::function &fn) { pid_t pid = fork(); switch (pid) { case -1: return false; case 0: fn(); exit(0); default: break; } return pid; } pid_t swoole_fork(int flags) { if (!(flags & SW_FORK_EXEC)) { if (swoole_coroutine_is_in()) { swoole_fatal_error(SW_ERROR_OPERATION_NOT_SUPPORT, "must be forked outside the coroutine"); } if (SwooleTG.async_threads) { swoole_trace("aio_task_num=%lu, reactor=%p", SwooleTG.async_threads->task_num, sw_reactor()); swoole_fatal_error(SW_ERROR_OPERATION_NOT_SUPPORT, "can not fork after using async-threads"); } } if (flags & SW_FORK_PRECHECK) { return 0; } pid_t pid = fork(); if (pid == 0) { if (flags & SW_FORK_DAEMON) { return pid; } /** * [!!!] All timers and event loops must be cleaned up after fork */ if (swoole_timer_is_available()) { swoole_timer_free(); } if (!(flags & SW_FORK_EXEC)) { /** * Do not release the allocated memory pages. * The global memory will be returned to the OS upon process termination. */ SwooleG.memory_pool = new swoole::GlobalMemory(SW_GLOBAL_MEMORY_PAGESIZE, true); // reopen log file sw_logger()->reopen(); // reset eventLoop if (swoole_event_is_available()) { swoole_event_free(); swoole_trace_log(SW_TRACE_REACTOR, "reactor has been destroyed"); } } else { sw_logger()->close(); } // reset signal handler swoole_signal_clear(); if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_FORK)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_FORK, nullptr); } } return pid; } bool swoole_is_main_thread() { return SwooleTG.main_thread; } void swoole_thread_init(bool main_thread) { if (!SwooleTG.buffer_stack) { SwooleTG.buffer_stack = new String(SW_STACK_BUFFER_SIZE); } if (!main_thread) { swoole_signal_block_all(); } SwooleTG.main_thread = main_thread; } void swoole_thread_clean(bool main_thread) { if (SwooleTG.timer) { swoole_timer_free(); } if (SwooleTG.reactor) { swoole_event_free(); } if (SwooleTG.buffer_stack) { delete SwooleTG.buffer_stack; SwooleTG.buffer_stack = nullptr; } } void swoole_dump_ascii(const char *data, size_t size) { for (size_t i = 0; i < size; i++) { printf("%u ", (unsigned) data[i]); } printf("\n"); } void swoole_dump_bin(const uchar *data, char type, size_t size) { int type_size = swoole_type_size(type); if (type_size <= 0) { return; } int n = size / type_size; for (int i = 0; i < n; i++) { printf("%ld,", (long) swoole_unpack(type, data + type_size * i)); } printf("\n"); } void swoole_dump_hex(const uchar *data, size_t outlen) { for (size_t i = 0; i < outlen; ++i) { if ((i & 0x0fu) == 0) { printf("%08zX: ", i); } printf("%02X ", data[i]); if (((i + 1) & 0x0fu) == 0) { printf("\n"); } } printf("\n"); } /** * Recursive directory creation */ bool swoole_mkdir_recursive(const std::string &dir) { char tmp[PATH_MAX]; size_t len = dir.length(); // PATH_MAX limit includes string trailing null character if (len + 1 > PATH_MAX) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_NAME_TOO_LONG, "mkdir() failed. Path exceeds the limit of %d characters", PATH_MAX - 1); return false; } swoole_strlcpy(tmp, dir.c_str(), PATH_MAX); if (dir[len - 1] != '/') { strcat(tmp, "/"); } len = strlen(tmp); for (size_t i = 1; i < len; i++) { if (tmp[i] == '/') { tmp[i] = 0; if (access(tmp, R_OK) != 0) { if (mkdir(tmp, 0755) == -1) { swoole_sys_warning("mkdir('%s') failed", tmp); return false; } } tmp[i] = '/'; } } return true; } int swoole_type_size(char type) { switch (type) { case 'c': case 'C': return 1; case 's': case 'S': case 'n': case 'v': return 2; case 'l': case 'L': case 'N': case 'V': return 4; case 'q': case 'Q': case 'J': case 'P': return 8; default: return 0; } } char *swoole_dec2hex(ulong_t value, int base) { assert(base > 1 && base < 37); static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char buf[(sizeof(ulong_t) << 3) + 1]; char *ptr; char *end = ptr = buf + sizeof(buf) - 1; *ptr = '\0'; do { *--ptr = digits[value % base]; value /= base; } while (ptr > buf && value); return sw_strndup(ptr, end - ptr); } ulong_t swoole_hex2dec(const char *hex, size_t *parsed_bytes) { size_t value = 0; *parsed_bytes = 0; const char *p = hex; if (strncasecmp(hex, "0x", 2) == 0) { p += 2; } while (true) { char c = *p; if ((c >= '0') && (c <= '9')) { value = value * 16 + (c - '0'); } else { c = toupper(c); if ((c >= 'A') && (c <= 'Z')) { value = value * 16 + (c - 'A') + 10; } else { break; } } p++; } *parsed_bytes = p - hex; return value; } #ifndef RAND_MAX #define RAND_MAX 2147483647 #endif int swoole_system_random(int min, int max) { static int dev_random_fd = -1; unsigned random_value; assert(max > min); if (dev_random_fd == -1) { dev_random_fd = open("/dev/urandom", O_RDONLY); if (dev_random_fd < 0) { return swoole_rand(min, max); } } auto next_random_byte = (char *) &random_value; constexpr int bytes_to_read = sizeof(random_value); if (read(dev_random_fd, next_random_byte, bytes_to_read) < bytes_to_read) { swoole_sys_warning("read() from /dev/urandom failed"); return SW_ERR; } return min + (random_value % (max - min + 1)); } void swoole_redirect_stdout(int new_fd) { if (dup2(new_fd, STDOUT_FILENO) < 0) { swoole_sys_warning("dup2(STDOUT_FILENO) failed"); } if (dup2(new_fd, STDERR_FILENO) < 0) { swoole_sys_warning("dup2(STDERR_FILENO) failed"); } } void swoole_redirect_stdout(const char *file) { auto fd = open(file, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd >= 0) { swoole_redirect_stdout(fd); close(fd); } else { swoole_sys_warning("open('%s') failed", file); } } int swoole_version_compare(const char *version1, const char *version2) { int result = 0; while (result == 0) { char *tail1; char *tail2; unsigned long ver1 = strtoul(version1, &tail1, 10); unsigned long ver2 = strtoul(version2, &tail2, 10); if (ver1 < ver2) { result = -1; } else if (ver1 > ver2) { result = +1; } else { version1 = tail1; version2 = tail2; if (*version1 == '\0' && *version2 == '\0') { break; } else if (*version1 == '\0') { result = -1; } else if (*version2 == '\0') { result = +1; } else { version1++; version2++; } } } return result; } /** * Maximum common divisor */ uint32_t swoole_common_divisor(uint32_t u, uint32_t v) { assert(u > 0); assert(v > 0); while (u > 0) { if (u < v) { uint32_t t = u; u = v; v = t; } u = u - v; } return v; } /** * The least common multiple */ uint32_t swoole_common_multiple(uint32_t u, uint32_t v) { assert(u > 0); assert(v > 0); uint32_t m_cup = u; uint32_t n_cup = v; int res = m_cup % n_cup; while (res != 0) { m_cup = n_cup; n_cup = res; res = m_cup % n_cup; } return u * v / n_cup; } size_t sw_snprintf(char *buf, size_t size, const char *format, ...) { va_list args; va_start(args, format); int retval = vsnprintf(buf, size, format, args); va_end(args); if (size == 0) { return retval; } else if (sw_unlikely(retval < 0)) { retval = 0; buf[0] = '\0'; } else if (sw_unlikely(retval >= (int) size)) { retval = size - 1; buf[retval] = '\0'; } return retval; } size_t sw_vsnprintf(char *buf, size_t size, const char *format, va_list args) { int retval = vsnprintf(buf, size, format, args); if (sw_unlikely(retval < 0)) { retval = 0; buf[0] = '\0'; } else if (sw_unlikely(retval >= (int) size)) { retval = size - 1; buf[retval] = '\0'; } return retval; } int sw_printf(const char *format, ...) { va_list args; va_start(args, format); int retval = vfprintf(SwooleG.stdout_, format, args); va_end(args); return retval; } bool sw_wait_for(const std::function &fn, int timeout_ms) { int sleep_msec = 1; while (timeout_ms >= 0) { if (fn()) { return true; } usleep(sleep_msec * 1000); sleep_msec *= 2; // Align the time so that the timeout is consistent with the user settings if (timeout_ms > 0 && timeout_ms - sleep_msec < 0) { sleep_msec = timeout_ms; timeout_ms = 0; } else { timeout_ms -= sleep_msec; } } return false; } int swoole_itoa(char *buf, long value) { long i = 0, j; long sign_mask = value >> (sizeof(long) * 8 - 1); unsigned long nn = (value + sign_mask) ^ sign_mask; do { buf[i++] = nn % 10 + '0'; } while (nn /= 10); buf[i] = '-'; i += sign_mask & 1; buf[i] = '\0'; int s_len = i; for (i = 0, j = s_len - 1; i < j; ++i, --j) { char swap = buf[i]; buf[i] = buf[j]; buf[j] = swap; } buf[s_len] = 0; return s_len; } int swoole_shell_exec(const char *command, pid_t *pid, bool get_error_stream) { pid_t child_pid; int fds[2]; if (pipe(fds) < 0) { return SW_ERR; } if ((child_pid = fork()) == -1) { swoole_sys_warning("fork() failed"); close(fds[0]); close(fds[1]); return SW_ERR; } if (child_pid == 0) { close(fds[SW_PIPE_READ]); if (get_error_stream) { if (fds[SW_PIPE_WRITE] == fileno(stdout)) { dup2(fds[SW_PIPE_WRITE], fileno(stderr)); } else if (fds[SW_PIPE_WRITE] == fileno(stderr)) { dup2(fds[SW_PIPE_WRITE], fileno(stdout)); } else { dup2(fds[SW_PIPE_WRITE], fileno(stdout)); dup2(fds[SW_PIPE_WRITE], fileno(stderr)); close(fds[SW_PIPE_WRITE]); } } else { if (fds[SW_PIPE_WRITE] != fileno(stdout)) { dup2(fds[SW_PIPE_WRITE], fileno(stdout)); close(fds[SW_PIPE_WRITE]); } } execl("/bin/sh", "sh", "-c", command, nullptr); exit(127); } else { *pid = child_pid; close(fds[SW_PIPE_WRITE]); } return fds[SW_PIPE_READ]; } char *swoole_string_format(size_t n, const char *format, ...) { char *buf = (char *) sw_malloc(n); if (!buf) { return nullptr; } va_list va_list; va_start(va_list, format); int ret = vsnprintf(buf, n, format, va_list); va_end(va_list); if (ret >= 0) { return buf; } sw_free(buf); return nullptr; } static constexpr char characters[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; void swoole_random_string(char *buf, size_t len) { size_t i = 0; for (; i < len; i++) { buf[i] = characters[swoole_rand(0, sizeof(characters) - 1)]; } buf[i] = '\0'; } void swoole_random_string(std::string &str, size_t len) { size_t i = 0; for (; i < len; i++) { str.append(1, characters[swoole_rand(0, sizeof(characters) - 1)]); } } uint64_t swoole_random_int() { static thread_local std::random_device rd; static thread_local std::mt19937_64 gen(rd()); static thread_local std::uniform_int_distribution dis; std::uniform_int_distribution::param_type params(0, UINT64_MAX); return dis(gen, params); } int swoole_rand(int min, int max) { static thread_local std::random_device rd; static thread_local std::mt19937 gen(rd()); static thread_local std::uniform_int_distribution dis; std::uniform_int_distribution::param_type params(min, max); return dis(gen, params); } int swoole_rand() { return swoole_rand(0, INT_MAX); } bool swoole_get_env(const char *name, int *value) { const char *e = getenv(name); if (!e) { return false; } *value = std::stoi(e); return true; } int swoole_get_systemd_listen_fds() { int ret; if (!swoole_get_env("LISTEN_FDS", &ret)) { swoole_warning("invalid LISTEN_FDS"); return -1; } else if (ret >= SW_MAX_LISTEN_PORT) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_TOO_MANY_LISTEN_PORT, "LISTEN_FDS is too big"); return -1; } return ret; } #ifdef HAVE_BOOST_STACKTRACE #include #include void swoole_print_backtrace() { std::cout << boost::stacktrace::stacktrace(); } #elif defined(HAVE_EXECINFO) && !defined(__ANDROID__) #include void swoole_print_backtrace() { int size = 16; void *array[16]; int stack_num = backtrace(array, size); char **stacktrace = backtrace_symbols(array, stack_num); int i; for (i = 0; i < stack_num; ++i) { printf("%s\n", stacktrace[i]); } free(stacktrace); } #else void swoole_print_backtrace() {} #endif void swoole_print_backtrace_on_error() { if (SwooleG.print_backtrace_on_error) { swoole_print_backtrace(); if (SwooleG.print_backtrace) { SwooleG.print_backtrace(); } } } static void swoole_fatal_error_impl(int code, const char *format, ...) { size_t retval = 0; va_list args; retval += sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "(ERROR %d): ", code); va_start(args, format); retval += sw_vsnprintf(sw_error + retval, SW_ERROR_MSG_SIZE - retval, format, args); va_end(args); sw_logger()->put(SW_LOG_ERROR, sw_error, retval); swoole_exit(1); } void swoole_exit(int _status) { #ifdef SW_THREAD /** * If multiple threads call exit simultaneously, it can result in a crash. * Implementing locking mechanisms can prevent concurrent calls to exit. */ std::unique_lock _lock(sw_thread_lock); #endif exit(_status); } namespace swoole { //------------------------------------------------------------------------------- size_t DataHead::dump(char *_buf, size_t _len) { return sw_snprintf(_buf, _len, "DataHead[%p]\n" "{\n" " long fd = %ld;\n" " uint64_t msg_id = %" PRIu64 ";\n" " uint32_t len = %d;\n" " int16_t reactor_id = %d;\n" " uint8_t type = %d;\n" " uint8_t flags = %d;\n" " uint16_t server_fd = %d;\n" " uint16_t ext_flags = %d;\n" " double time = %f;\n" "}\n", this, fd, msg_id, len, reactor_id, type, flags, server_fd, ext_flags, time); } void DataHead::print() { sw_tg_buffer()->length = dump(sw_tg_buffer()->str, sw_tg_buffer()->size); printf("%.*s", (int) sw_tg_buffer()->length, sw_tg_buffer()->str); } std::string dirname(const std::string &file) { size_t index = file.find_last_of('/'); if (index == std::string::npos) { return {}; } else if (index == 0) { return "/"; } return file.substr(0, index); } void hook_add(void **hooks, int type, const Callback &func, int push_back) { if (hooks[type] == nullptr) { hooks[type] = new std::list; } auto *l = static_cast *>(hooks[type]); if (push_back) { l->push_back(func); } else { l->push_front(func); } } void hook_call(void **hooks, int type, void *arg) { if (hooks[type] == nullptr) { return; } const auto *l = static_cast *>(hooks[type]); for (auto &i : *l) { i(arg); } } /** * return the first file of the intersection, in order of vec1 */ std::string intersection(const std::vector &vec1, std::set &vec2) { for (const auto &vec1_item : vec1) { if (vec2.find(vec1_item) != vec2.end()) { return vec1_item; } } return ""; } double microtime() { using namespace std::chrono; return duration_cast>(system_clock::now().time_since_epoch()).count(); } }; // namespace swoole ================================================ FILE: src/core/base64.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_base64.h" namespace swoole { // clang-format off /* BASE 64 encode table */ static char base64en[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; #define BASE64_PAD '=' #define BASE64DE_FIRST '+' #define BASE64DE_LAST 'z' /* ASCII order for BASE 64 decode, -1 in unused character */ static signed char base64de[] = { /* '+', ',', '-', '.', '/', '0', '1', '2', */ 62, -1, -1, -1, 63, 52, 53, 54, /* '3', '4', '5', '6', '7', '8', '9', ':', */ 55, 56, 57, 58, 59, 60, 61, -1, /* ';', '<', '=', '>', '?', '@', 'A', 'B', */ -1, -1, -1, -1, -1, -1, 0, 1, /* 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', */ 2, 3, 4, 5, 6, 7, 8, 9, /* 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', */ 10, 11, 12, 13, 14, 15, 16, 17, /* 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', */ 18, 19, 20, 21, 22, 23, 24, 25, /* '[', '\', ']', '^', '_', '`', 'a', 'b', */ -1, -1, -1, -1, -1, -1, 26, 27, /* 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', */ 28, 29, 30, 31, 32, 33, 34, 35, /* 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', */ 36, 37, 38, 39, 40, 41, 42, 43, /* 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', */ 44, 45, 46, 47, 48, 49, 50, 51, }; // clang-format on size_t base64_encode(const unsigned char *in, size_t inlen, char *out) { size_t i, j; for (i = j = 0; i < inlen; i++) { int s = i % 3; /* from 6/gcd(6, 8) */ switch (s) { case 0: out[j++] = base64en[(in[i] >> 2) & 0x3F]; continue; case 1: out[j++] = base64en[((in[i - 1] & 0x3) << 4) + ((in[i] >> 4) & 0xF)]; continue; case 2: out[j++] = base64en[((in[i - 1] & 0xF) << 2) + ((in[i] >> 6) & 0x3)]; out[j++] = base64en[in[i] & 0x3F]; } } /* move back */ i -= 1; /* check the last and add padding */ if ((i % 3) == 0) { out[j++] = base64en[(in[i] & 0x3) << 4]; out[j++] = BASE64_PAD; out[j++] = BASE64_PAD; } else if ((i % 3) == 1) { out[j++] = base64en[(in[i] & 0xF) << 2]; out[j++] = BASE64_PAD; } out[j] = '\0'; return j; } size_t base64_decode(const char *in, size_t inlen, char *out) { size_t j; for (size_t i = j = 0; i < inlen; i++) { int c; int s = i % 4; /* from 8/gcd(6, 8) */ if (in[i] == '=') { break; } if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST || (c = base64de[in[i] - BASE64DE_FIRST]) == -1) { return 0; } switch (s) { case 0: out[j] = c << 2; continue; case 1: out[j++] += (c >> 4) & 0x3; /* if not last char with padding */ if (i < (inlen - 3) || in[inlen - 2] != '=') out[j] = (c & 0xF) << 4; continue; case 2: out[j++] += (c >> 2) & 0xF; /* if not last char with padding */ if (i < (inlen - 2) || in[inlen - 1] != '=') out[j] = (c & 0x3) << 6; continue; case 3: out[j++] += c; } } out[j] = '\0'; return j; } } // namespace swoole ================================================ FILE: src/core/buffer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include "swoole_buffer.h" namespace swoole { BufferChunk::BufferChunk(Type type, uint32_t size) : type(type), size(size) { if (type == TYPE_DATA && size > 0) { value.str = new char[size]; } } BufferChunk::~BufferChunk() { if (type == TYPE_DATA) { delete[] value.str; } if (destroy) { destroy(this); } } Buffer::Buffer(uint32_t _chunk_size) { chunk_size = _chunk_size == 0 ? INT_MAX : _chunk_size; } BufferChunk *Buffer::alloc(BufferChunk::Type type, uint32_t size) { auto *chunk = new BufferChunk(type, size); queue_.push(chunk); return chunk; } void Buffer::pop() { BufferChunk *chunk = queue_.front(); total_length -= chunk->size; delete chunk; queue_.pop(); } Buffer::~Buffer() { while (!queue_.empty()) { pop(); } } void Buffer::append(const char *data, uint32_t size) { uint32_t _length = size; auto _pos = data; assert(size > 0); // buffer enQueue while (_length > 0) { uint32_t _n = _length >= chunk_size ? chunk_size : _length; BufferChunk *chunk = alloc(BufferChunk::TYPE_DATA, _n); total_length += _n; memcpy(chunk->value.str, _pos, _n); chunk->length = _n; swoole_trace_log( SW_TRACE_BUFFER, "chunk_n=%lu|size=%u|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); _pos += _n; _length -= _n; } } void Buffer::append(const iovec *iov, size_t iovcnt, off_t offset) { size_t _length = 0; SW_LOOP_N(iovcnt) { assert(iov[i].iov_len > 0); assert(iov[i].iov_base != nullptr); _length += iov[i].iov_len; } auto pos = static_cast(iov[0].iov_base); BufferChunk *chunk = nullptr; size_t iov_remain_len = iov[0].iov_len, chunk_remain_len; size_t i = 0; while (true) { if (chunk) { if (chunk->size == chunk->length) { chunk = nullptr; continue; } else { chunk_remain_len = chunk->size - chunk->length; } } else { if (offset > 0) { if (offset >= (off_t) iov[i].iov_len) { offset -= iov[i].iov_len; i++; continue; } else { pos = static_cast(iov[i].iov_base) + offset; iov_remain_len = iov[i].iov_len - offset; offset = 0; } } chunk_remain_len = _length >= chunk_size ? chunk_size : _length; chunk = alloc(BufferChunk::TYPE_DATA, chunk_remain_len); } size_t _n = std::min(iov_remain_len, chunk_remain_len); memcpy(chunk->value.str + chunk->length, pos, _n); total_length += _n; _length -= _n; swoole_trace_log( SW_TRACE_BUFFER, "chunk_n=%lu|size=%lu|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); chunk->length += _n; iov_remain_len -= _n; if (iov_remain_len == 0) { i++; if (i == iovcnt) { break; } iov_remain_len = iov[i].iov_len; pos = (char *) iov[i].iov_base; } else { pos += _n; } } } } // namespace swoole ================================================ FILE: src/core/channel.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" #include "swoole_channel.h" #include "swoole_lock.h" #include "swoole_pipe.h" namespace swoole { #define SW_CHANNEL_MIN_MEM (1024 * 64) struct ChannelSlice { int length; char data[0]; }; Channel *Channel::make(size_t size, size_t maxlen, int flags) { assert(size >= maxlen); void *mem; // use shared memory if (flags & SW_CHAN_SHM) { /** * overflow space */ mem = sw_shm_malloc(size + sizeof(Channel) + maxlen + sizeof(ChannelSlice)); } else { mem = sw_malloc(size + sizeof(Channel) + maxlen + sizeof(ChannelSlice)); } if (mem == nullptr) { swoole_warning("alloc(%ld) failed", size); return nullptr; } auto *object = static_cast(mem); mem = static_cast(mem) + sizeof(Channel); *object = {}; // overflow space object->size = size; object->mem = mem; object->maxlen = maxlen; object->flags = flags; if (flags & SW_CHAN_LOCK) { object->lock = new Mutex(true); } if (flags & SW_CHAN_NOTIFY) { object->notify_pipe = new Pipe(true); if (!object->notify_pipe->ready()) { swoole_warning("notify_fd init failed"); delete object->notify_pipe; return nullptr; } } return object; } /** * push data(no lock) */ int Channel::in(const void *in_data, int data_length) { assert(data_length <= maxlen); if (full()) { return SW_ERR; } ChannelSlice *item; int msize = sizeof(item->length) + data_length; if (tail < head) { // no enough memory space if ((head - tail) < msize) { return SW_ERR; } item = reinterpret_cast(static_cast(mem) + tail); tail += msize; } else { item = reinterpret_cast(static_cast(mem) + tail); tail += msize; if (tail >= (off_t) size) { tail = 0; tail_tag = 1 - tail_tag; } } num++; bytes += data_length; item->length = data_length; memcpy(item->data, in_data, data_length); return SW_OK; } /** * pop data(no lock) */ int Channel::out(void *out_buf, int buffer_length) { if (empty()) { return SW_ERR; } auto *item = reinterpret_cast(static_cast(mem) + head); assert(buffer_length >= item->length); memcpy(out_buf, item->data, item->length); head += (item->length + sizeof(item->length)); if (head >= (off_t) size) { head = 0; head_tag = 1 - head_tag; } num--; bytes -= item->length; return item->length; } /** * peek data */ int Channel::peek(void *out, int buffer_length) const { if (empty()) { return SW_ERR; } lock->lock(); auto *item = reinterpret_cast(static_cast(mem) + head); assert(buffer_length >= item->length); memcpy(out, item->data, item->length); int length = item->length; lock->unlock(); return length; } /** * wait notify */ int Channel::wait() const { assert(flags & SW_CHAN_NOTIFY); uint64_t value; return notify_pipe->read(&value, sizeof(value)) > 0 ? SW_OK : SW_ERR; } /** * new data coming, notify to customer */ int Channel::notify() const { assert(flags & SW_CHAN_NOTIFY); uint64_t value = 1; return notify_pipe->write(&value, sizeof(value)) == sizeof(value) ? SW_OK : SW_ERR; } /** * push data (lock) */ int Channel::push(const void *in_data, int data_length) { assert(flags & SW_CHAN_LOCK); lock->lock(); int ret = in(in_data, data_length); lock->unlock(); return ret; } /** * free channel */ void Channel::destroy() { if (flags & SW_CHAN_LOCK) { delete lock; } if (flags & SW_CHAN_NOTIFY) { notify_pipe->close(); delete notify_pipe; } if (flags & SW_CHAN_SHM) { sw_shm_free(this); } else { sw_free(this); } } /** * pop data (lock) */ int Channel::pop(void *out_buf, int buffer_length) { assert(flags & SW_CHAN_LOCK); lock->lock(); int n = out(out_buf, buffer_length); lock->unlock(); return n; } void Channel::print() const { printf("Channel\n{\n" " off_t head = %ld;\n" " off_t tail = %ld;\n" " size_t size = %ld;\n" " char head_tag = %d;\n" " char tail_tag = %d;\n" " int num = %d;\n" " size_t bytes = %ld;\n" " int flag = %d;\n" " int maxlen = %d;\n" "\n}\n", (long) head, (long) tail, size, tail_tag, head_tag, num, bytes, flags, maxlen); } } // namespace swoole ================================================ FILE: src/core/crc32.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include "swoole_hash.h" static constexpr int CRC32_TABLE_SIZE = 256; static uint32_t crc32_table[CRC32_TABLE_SIZE]; static bool generated = false; static void generate_table(uint32_t (&table)[CRC32_TABLE_SIZE]) { uint32_t polynomial = 0xEDB88320; for (uint32_t i = 0; i < CRC32_TABLE_SIZE; i++) { uint32_t c = i; for (size_t j = 0; j < 8; j++) { if (c & 1) { c = polynomial ^ (c >> 1); } else { c >>= 1; } } table[i] = c; } } uint32_t swoole_crc32(const char *data, size_t size) { if (sw_unlikely(!generated)) { generate_table(crc32_table); } uint32_t crcinit = 0; uint32_t crc = crcinit ^ 0xffffffff; for (; size--; ++data) { crc = ((crc >> 8) & 0x00ffffff) ^ crc32_table[(crc ^ (*data)) & 0xff]; } return (crc ^ 0xffffffff); } ================================================ FILE: src/core/error.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include static std::unordered_set ignored_errors; namespace swoole { Exception::Exception(const int code) noexcept : code(code) { msg = swoole_strerror(code); } } // namespace swoole const char *swoole_strerror(int code) { if (code < SW_ERROR_BEGIN) { return strerror(code); } /* swstrerror {{{*/ switch (code) { case SW_ERROR_MALLOC_FAIL: return "Malloc fail"; case SW_ERROR_SYSTEM_CALL_FAIL: return "System call fail"; case SW_ERROR_PHP_FATAL_ERROR: return "PHP fatal error"; case SW_ERROR_NAME_TOO_LONG: return "Name too long"; case SW_ERROR_INVALID_PARAMS: return "Invalid params"; case SW_ERROR_QUEUE_FULL: return "Queue full"; case SW_ERROR_OPERATION_NOT_SUPPORT: return "Operation not support"; case SW_ERROR_PROTOCOL_ERROR: return "Protocol error"; case SW_ERROR_WRONG_OPERATION: return "Wrong operation"; case SW_ERROR_PHP_RUNTIME_NOTICE: return "PHP runtime notice"; case SW_ERROR_FOR_TEST: return "For test"; case SW_ERROR_NO_PAYLOAD: return "No payload"; case SW_ERROR_UNDEFINED_BEHAVIOR: return "Undefined behavior"; case SW_ERROR_NOT_THREAD_SAFETY: return "Not thread safety"; case SW_ERROR_FILE_NOT_EXIST: return "File not exist"; case SW_ERROR_FILE_TOO_LARGE: return "File too large"; case SW_ERROR_FILE_EMPTY: return "File empty"; case SW_ERROR_DIR_NOT_EXIST: return "Dir not exist"; case SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST: return "DNS Lookup duplicate request"; case SW_ERROR_DNSLOOKUP_RESOLVE_FAILED: return "DNS Lookup resolve failed"; case SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT: return "DNS Lookup resolve timeout"; case SW_ERROR_DNSLOOKUP_UNSUPPORTED: return "DNS Lookup unsupported"; case SW_ERROR_DNSLOOKUP_NO_SERVER: return "DNS Lookup no server"; case SW_ERROR_BAD_IPV6_ADDRESS: return "Bad ipv6 address"; case SW_ERROR_UNREGISTERED_SIGNAL: return "Unregistered signal"; case SW_ERROR_BAD_HOST_ADDR: return "Bad host addr"; case SW_ERROR_BAD_PORT: return "Bad port"; case SW_ERROR_BAD_SOCKET_TYPE: return "Bad socket type"; case SW_ERROR_EVENT_REMOVE_FAILED: return "Event remove failed"; case SW_ERROR_EVENT_ADD_FAILED: return "Event add failed"; case SW_ERROR_EVENT_UPDATE_FAILED: return "Event update failed"; case SW_ERROR_EVENT_UNKNOWN_DATA: return "Event unknown data"; case SW_ERROR_SESSION_CLOSED_BY_SERVER: return "Session closed by server"; case SW_ERROR_SESSION_CLOSED_BY_CLIENT: return "Session closed by client"; case SW_ERROR_SESSION_CLOSING: return "Session closing"; case SW_ERROR_SESSION_CLOSED: return "Session closed"; case SW_ERROR_SESSION_NOT_EXIST: return "Session not exist"; case SW_ERROR_SESSION_INVALID_ID: return "Session invalid id"; case SW_ERROR_SESSION_DISCARD_TIMEOUT_DATA: return "Session discard timeout data"; case SW_ERROR_SESSION_DISCARD_DATA: return "Session discard data"; case SW_ERROR_OUTPUT_BUFFER_OVERFLOW: return "Output buffer overflow"; case SW_ERROR_OUTPUT_SEND_YIELD: return "Output send yield"; case SW_ERROR_SSL_NOT_READY: return "SSL not ready"; case SW_ERROR_SSL_CANNOT_USE_SENFILE: return "SSL cannot use senfile"; case SW_ERROR_SSL_EMPTY_PEER_CERTIFICATE: return "SSL empty peer certificate"; case SW_ERROR_SSL_VERIFY_FAILED: return "SSL verify failed"; case SW_ERROR_SSL_BAD_CLIENT: return "SSL bad client"; case SW_ERROR_SSL_BAD_PROTOCOL: return "SSL bad protocol"; case SW_ERROR_SSL_RESET: return "SSL reset"; case SW_ERROR_SSL_HANDSHAKE_FAILED: return "SSL handshake failed"; case SW_ERROR_SSL_CREATE_CONTEXT_FAILED: return "SSL create context failed"; case SW_ERROR_SSL_CREATE_SESSION_FAILED: return "SSL create session failed"; case SW_ERROR_PACKAGE_LENGTH_TOO_LARGE: return "Package length too large"; case SW_ERROR_PACKAGE_LENGTH_NOT_FOUND: return "Package length not found"; case SW_ERROR_DATA_LENGTH_TOO_LARGE: return "Data length too large"; case SW_ERROR_PACKAGE_MALFORMED_DATA: return "Package malformed data"; case SW_ERROR_TASK_PACKAGE_TOO_BIG: return "Task package too big"; case SW_ERROR_TASK_DISPATCH_FAIL: return "Task dispatch fail"; case SW_ERROR_TASK_TIMEOUT: return "Task timeout"; case SW_ERROR_HTTP2_STREAM_ID_TOO_BIG: return "Http2 stream id too big"; case SW_ERROR_HTTP2_STREAM_NO_HEADER: return "Http2 stream no header"; case SW_ERROR_HTTP2_STREAM_NOT_FOUND: return "Http2 stream not found"; case SW_ERROR_HTTP2_STREAM_IGNORE: return "Http2 stream ignore"; case SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED: return "Http2 send control frame failed"; case SW_ERROR_HTTP2_INTERNAL_ERROR: return "Http2 internal error"; case SW_ERROR_AIO_BAD_REQUEST: return "Aio bad request"; case SW_ERROR_AIO_CANCELED: return "Aio canceled"; case SW_ERROR_AIO_TIMEOUT: return "Aio timeout"; case SW_ERROR_CLIENT_NO_CONNECTION: return "Client no connection"; case SW_ERROR_SOCKET_CLOSED: return "Socket closed"; case SW_ERROR_SOCKET_POLL_TIMEOUT: return "Socket poll timeout"; case SW_ERROR_SOCKET_NOT_EXISTS: return "Socket not exists"; case SW_ERROR_SOCKS5_UNSUPPORT_VERSION: return "Socks5 unsupport version"; case SW_ERROR_SOCKS5_UNSUPPORT_METHOD: return "Socks5 unsupport method"; case SW_ERROR_SOCKS5_AUTH_FAILED: return "Socks5 auth failed"; case SW_ERROR_SOCKS5_SERVER_ERROR: return "Socks5 server error"; case SW_ERROR_SOCKS5_HANDSHAKE_FAILED: return "Socks5 handshake failed"; case SW_ERROR_SOCKS5_CONNECT_FAILED: return "Socks5 connect failed"; case SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR: return "Http proxy handshake error"; case SW_ERROR_HTTP_INVALID_PROTOCOL: return "Http invalid protocol"; case SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED: return "Http proxy handshake failed"; case SW_ERROR_HTTP_PROXY_BAD_RESPONSE: return "Http proxy bad response"; case SW_ERROR_HTTP_CONFLICT_HEADER: return "Http conflict header"; case SW_ERROR_HTTP_CONTEXT_UNAVAILABLE: return "Http context unavailable"; case SW_ERROR_HTTP_COOKIE_UNAVAILABLE: return "Http cookie unavailable"; case SW_ERROR_WEBSOCKET_BAD_CLIENT: return "Websocket bad client"; case SW_ERROR_WEBSOCKET_BAD_OPCODE: return "Websocket bad opcode"; case SW_ERROR_WEBSOCKET_UNCONNECTED: return "Websocket unconnected"; case SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED: return "Websocket handshake failed"; case SW_ERROR_WEBSOCKET_PACK_FAILED: return "Websocket pack failed"; case SW_ERROR_WEBSOCKET_UNPACK_FAILED: return "Websocket unpack failed"; case SW_ERROR_WEBSOCKET_INCOMPLETE_PACKET: return "Websocket incomplete packet"; case SW_ERROR_SERVER_MUST_CREATED_BEFORE_CLIENT: return "Server must created before client"; case SW_ERROR_SERVER_TOO_MANY_SOCKET: return "Server too many socket"; case SW_ERROR_SERVER_WORKER_TERMINATED: return "Server worker terminated"; case SW_ERROR_SERVER_INVALID_LISTEN_PORT: return "Server invalid listen port"; case SW_ERROR_SERVER_TOO_MANY_LISTEN_PORT: return "Server too many listen port"; case SW_ERROR_SERVER_PIPE_BUFFER_FULL: return "Server pipe buffer full"; case SW_ERROR_SERVER_NO_IDLE_WORKER: return "Server no idle worker"; case SW_ERROR_SERVER_ONLY_START_ONE: return "Server only start one"; case SW_ERROR_SERVER_SEND_IN_MASTER: return "Server send in master"; case SW_ERROR_SERVER_INVALID_REQUEST: return "Server invalid request"; case SW_ERROR_SERVER_CONNECT_FAIL: return "Server connect fail"; case SW_ERROR_SERVER_INVALID_COMMAND: return "Server invalid command"; case SW_ERROR_SERVER_IS_NOT_REGULAR_FILE: return "Server is not regular file"; case SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT: return "Server send to woker timeout"; case SW_ERROR_SERVER_INVALID_CALLBACK: return "Server invalid callback"; case SW_ERROR_SERVER_UNRELATED_THREAD: return "Server unrelated thread"; case SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT: return "Server worker exit timeout"; case SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA: return "Server worker abnormal pipe data"; case SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA: return "Server worker unprocessed data"; case SW_ERROR_CO_OUT_OF_COROUTINE: return "Coroutine out of coroutine"; case SW_ERROR_CO_HAS_BEEN_BOUND: return "Coroutine has been bound"; case SW_ERROR_CO_HAS_BEEN_DISCARDED: return "Coroutine has been discarded"; case SW_ERROR_CO_MUTEX_DOUBLE_UNLOCK: return "Coroutine mutex double unlock"; case SW_ERROR_CO_BLOCK_OBJECT_LOCKED: return "Coroutine block object locked"; case SW_ERROR_CO_BLOCK_OBJECT_WAITING: return "Coroutine block object waiting"; case SW_ERROR_CO_YIELD_FAILED: return "Coroutine yield failed"; case SW_ERROR_CO_GETCONTEXT_FAILED: return "Coroutine getcontext failed"; case SW_ERROR_CO_SWAPCONTEXT_FAILED: return "Coroutine swapcontext failed"; case SW_ERROR_CO_MAKECONTEXT_FAILED: return "Coroutine makecontext failed"; case SW_ERROR_CO_IOCPINIT_FAILED: return "Coroutine iocpinit failed"; case SW_ERROR_CO_PROTECT_STACK_FAILED: return "Coroutine protect stack failed"; case SW_ERROR_CO_STD_THREAD_LINK_ERROR: return "Coroutine std thread link error"; case SW_ERROR_CO_DISABLED_MULTI_THREAD: return "Coroutine disabled multi thread"; case SW_ERROR_CO_CANNOT_CANCEL: return "Coroutine cannot cancel"; case SW_ERROR_CO_NOT_EXISTS: return "Coroutine not exists"; case SW_ERROR_CO_CANCELED: return "Coroutine canceled"; case SW_ERROR_CO_TIMEDOUT: return "Coroutine timedout"; case SW_ERROR_CO_SOCKET_CLOSE_WAIT: return "Coroutine socket close wait"; default: static char buffer[32]; #ifndef __MACH__ snprintf(buffer, sizeof(buffer), "Unknown error %d", code); #else snprintf(buffer, sizeof(buffer), "Unknown error: %d", code); #endif return buffer; } /*}}}*/ } void swoole_throw_error(int code) { throw swoole::Exception(code); } void swoole_ignore_error(int code) { ignored_errors.insert(code); } bool swoole_is_ignored_error(const int code) { return ignored_errors.find(code) != ignored_errors.end(); } void swoole_clear_last_error_msg() { sw_error[0] = '\0'; } const char *swoole_get_last_error_msg() { return sw_error; } ================================================ FILE: src/core/heap.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include "swoole_heap.h" #define left(i) ((i) << 1) #define right(i) (((i) << 1) + 1) #define parent(i) ((i) >> 1) namespace swoole { Heap::Heap(size_t _n, Type _type) { if (!((nodes = static_cast(sw_malloc((_n + 1) * sizeof(void *)))))) { throw std::bad_alloc(); } num = 1; size = (_n + 1); type = _type; } Heap::~Heap() { for (uint32_t i = 1; i < num; i++) { if (nodes[i]) { delete nodes[i]; } } sw_free(nodes); } int Heap::compare(uint64_t a, uint64_t b) const { if (type == MIN_HEAP) { return a > b; } else { return a < b; } } uint32_t Heap::maxchild(uint32_t i) const { uint32_t child_i = left(i); if (child_i >= num) { return 0; } HeapNode *child_node = nodes[child_i]; if ((child_i + 1) < num && compare(child_node->priority, nodes[child_i + 1]->priority)) { child_i++; } return child_i; } void Heap::bubble_up(uint32_t i) const { HeapNode *moving_node = nodes[i]; uint32_t parent_i; for (parent_i = parent(i); (i > 1) && compare(nodes[parent_i]->priority, moving_node->priority); i = parent_i, parent_i = parent(i)) { nodes[i] = nodes[parent_i]; nodes[i]->position = i; } nodes[i] = moving_node; moving_node->position = i; } void Heap::percolate_down(uint32_t i) const { uint32_t child_i; HeapNode *moving_node = nodes[i]; while (((child_i = maxchild(i))) && compare(moving_node->priority, nodes[child_i]->priority)) { nodes[i] = nodes[child_i]; nodes[i]->position = i; i = child_i; } nodes[i] = moving_node; moving_node->position = i; } HeapNode *Heap::push(uint64_t priority, void *data) { HeapNode **tmp; uint32_t i; if (num >= size) { uint32_t newsize = size * 2; if (!((tmp = static_cast(sw_realloc(nodes, sizeof(HeapNode *) * newsize))))) { return nullptr; } nodes = tmp; size = newsize; } auto *node = new HeapNode; node->priority = priority; node->data = data; i = num++; nodes[i] = node; bubble_up(i); return node; } void Heap::change_priority(uint64_t new_priority, HeapNode *node) const { uint32_t pos = node->position; uint64_t old_pri = node->priority; node->priority = new_priority; if (compare(old_pri, new_priority)) { bubble_up(pos); } else { percolate_down(pos); } } void Heap::remove(HeapNode *node) { uint32_t pos = node->position; nodes[pos] = nodes[--num]; if (compare(node->priority, nodes[pos]->priority)) { bubble_up(pos); } else { percolate_down(pos); } delete node; } void *Heap::pop() { if (count() == 0) { return nullptr; } HeapNode *head = nodes[1]; nodes[1] = nodes[--num]; percolate_down(1); void *data = head->data; delete head; return data; } void *Heap::peek() const { if (num == 1) { return nullptr; } HeapNode *node = nodes[1]; if (!node) { return nullptr; } return node->data; } void Heap::print() const { for (uint32_t i = 1; i < num; i++) { printf("#%u\tpriority=%ld, data=%p\n", i, (long) nodes[i]->priority, nodes[i]->data); } } } // namespace swoole ================================================ FILE: src/core/log.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_process_pool.h" #include #include #include #include // NOLINT [build/c++11] namespace swoole { std::string Logger::get_pretty_name(const std::string &pretty_function, bool strip) { const size_t brackets = pretty_function.find_first_of('('); if (brackets == std::string::npos) { return ""; } const size_t begin = pretty_function.substr(0, brackets).rfind(' ') + 1; const size_t end = brackets - begin; if (!strip) { return pretty_function.substr(begin, end); } auto method_name = pretty_function.substr(begin, end); size_t count = 0, index = method_name.length(); while (true) { index = method_name.rfind("::", index); if (index == std::basic_string::npos) { if (count == 1) { return method_name.substr(method_name.rfind("::") + 2); } break; } count++; if (count == 2) { return method_name.substr(index + 2); } index -= 2; } return method_name; } bool Logger::open(const char *_log_file) { if (opened) { close(); } log_file = _log_file; if (log_rotation) { log_real_file = gen_real_file(log_file); } else { log_real_file = log_file; } auto log_fd = ::open(log_real_file.c_str(), O_APPEND | O_RDWR | O_CREAT, 0666); if (log_fd < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SYSTEM_CALL_FAIL, "open('%s') failed. Error: %s[%d]", log_real_file.c_str(), strerror(errno), errno); opened = false; log_file = ""; log_real_file = ""; return false; } else { opened = true; log_fp = fdopen(log_fd, "a"); return true; } } void Logger::set_stream(FILE *stream) { if (opened) { close(); } log_fp = stream; } void Logger::close() { if (opened) { fclose(log_fp); log_fp = stdout; log_file = ""; opened = false; } } int Logger::get_level() const { return log_level; } void Logger::set_level(int level) { if (level < SW_LOG_DEBUG) { level = SW_LOG_DEBUG; } if (level > SW_LOG_NONE) { level = SW_LOG_NONE; } log_level = level; } void Logger::set_rotation(int _rotation) { log_rotation = _rotation; } bool Logger::redirect_stdout_and_stderr(bool enable) { if (enable) { if (!opened) { swoole_warning("no log file opened"); return false; } if (redirected) { swoole_warning("has been redirected"); return false; } if ((stdout_fd = dup(STDOUT_FILENO)) < 0) { swoole_sys_warning("dup(STDOUT_FILENO) failed"); return false; } if ((stderr_fd = dup(STDERR_FILENO)) < 0) { swoole_sys_warning("dup(STDERR_FILENO) failed"); return false; } swoole_redirect_stdout(fileno(log_fp)); redirected = true; } else { if (!redirected) { swoole_warning("no redirected"); return false; } if (dup2(stdout_fd, STDOUT_FILENO) < 0) { swoole_sys_warning("dup2(STDOUT_FILENO) failed"); } if (dup2(stderr_fd, STDERR_FILENO) < 0) { swoole_sys_warning("dup2(STDERR_FILENO) failed"); } ::close(stdout_fd); ::close(stderr_fd); stdout_fd = -1; stderr_fd = -1; redirected = false; } return true; } void Logger::reset() { date_format = SW_LOG_DEFAULT_DATE_FORMAT; date_with_microseconds = false; log_rotation = SW_LOG_ROTATION_SINGLE; log_level = SW_LOG_INFO; } bool Logger::set_date_format(const char *format) { char date_str[SW_LOG_DATE_STRLEN]; time_t now_sec; now_sec = ::time(nullptr); size_t l_data_str = std::strftime(date_str, sizeof(date_str), format, std::localtime(&now_sec)); if (l_data_str == 0) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); swoole_error_log( SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "The date format string[length=%ld] is too long", strlen(format)); return false; } else { date_format = format; return true; } } void Logger::set_date_with_microseconds(bool enable) { date_with_microseconds = enable; } void Logger::reopen_without_lock() { if (!opened) { return; } std::string new_log_file(log_file); close(); open(new_log_file.c_str()); if (redirected) { swoole_redirect_stdout(fileno(log_fp)); } } void Logger::reopen() { std::unique_lock _lock(lock); reopen_without_lock(); } const char *Logger::get_real_file() { return log_real_file.c_str(); } const char *Logger::get_file() const { return log_file.c_str(); } std::string Logger::gen_real_file(const std::string &file) const { char date_str[16]; auto now_sec = ::time(nullptr); const char *fmt; switch (log_rotation) { case SW_LOG_ROTATION_MONTHLY: fmt = "%Y%m"; break; case SW_LOG_ROTATION_HOURLY: fmt = "%Y%m%d%H"; break; case SW_LOG_ROTATION_EVERY_MINUTE: fmt = "%Y%m%d%H%M"; break; case SW_LOG_ROTATION_DAILY: default: fmt = "%Y%m%d"; break; } size_t l_data_str = std::strftime(date_str, sizeof(date_str), fmt, std::localtime(&now_sec)); std::string real_file = file + "." + std::string(date_str, l_data_str); return real_file; } bool Logger::is_opened() const { return opened; } void Logger::put(int level, const char *content, size_t length) { const char *level_str; char date_str[SW_LOG_DATE_STRLEN]; char log_str[SW_LOG_BUFFER_SIZE]; if (level < log_level) { return; } switch (level) { case SW_LOG_DEBUG: level_str = "DEBUG"; break; case SW_LOG_TRACE: level_str = "TRACE"; break; case SW_LOG_NOTICE: level_str = "NOTICE"; break; case SW_LOG_WARNING: level_str = "WARNING"; break; case SW_LOG_ERROR: level_str = "ERROR"; break; case SW_LOG_INFO: default: level_str = "INFO"; break; } auto now = std::chrono::system_clock::now(); auto now_sec = std::chrono::system_clock::to_time_t(now); size_t l_data_str = std::strftime(date_str, sizeof(date_str), date_format.c_str(), std::localtime(&now_sec)); if (log_rotation) { std::string tmp = gen_real_file(log_file); /** * If the current thread fails to acquire the lock, it will forgo executing the log rotation. */ if (tmp != log_real_file && lock.try_lock()) { reopen_without_lock(); lock.unlock(); } } if (date_with_microseconds) { auto now_us = std::chrono::duration_cast(now.time_since_epoch()).count(); l_data_str += sw_snprintf( date_str + l_data_str, SW_LOG_DATE_STRLEN - l_data_str, "<.%lld>", (long long) now_us - now_sec * 1000000); } int worker_id = swoole_get_worker_id(); pid_t worker_pid = swoole_get_worker_pid(); if (worker_pid == 0) { worker_pid = getpid(); } char worker_symbol = swoole_get_worker_symbol(); size_t n = sw_snprintf(log_str, SW_LOG_BUFFER_SIZE, "[%.*s %c%d.%d]\t%s\t%.*s\n", static_cast(l_data_str), date_str, worker_symbol, worker_pid, worker_id, level_str, static_cast(length), content); lock.lock(); if (opened) { flockfile(log_fp); } fwrite(log_str, n, 1, log_fp); fflush(log_fp); if (opened) { funlockfile(log_fp); } lock.unlock(); if (display_backtrace_) { swoole_print_backtrace(); } } } // namespace swoole ================================================ FILE: src/core/misc.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole.h" #include void sw_spinlock(sw_atomic_t *lock) { uint32_t i, n; while (true) { if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { return; } if (SW_CPU_NUM > 1) { for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1) { for (i = 0; i < n; i++) { sw_atomic_cpu_pause(); } if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { return; } } } std::this_thread::yield(); } } #ifdef HAVE_FUTEX #include #include int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout) { if (sw_atomic_cmp_set(atomic, 1, 0)) { return 0; } int ret; timespec _timeout; if (timeout > 0) { _timeout.tv_sec = static_cast(timeout); _timeout.tv_nsec = (timeout - _timeout.tv_sec) * 1000 * 1000 * 1000; ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, &_timeout, NULL, 0); } else { ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, NULL, NULL, 0); } if (ret == 0 && sw_atomic_cmp_set(atomic, 1, 0)) { return 0; } else { return -1; } } int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n) { if (sw_atomic_cmp_set(atomic, 0, 1)) { return syscall(SYS_futex, atomic, FUTEX_WAKE, n, NULL, NULL, 0); } else { return 0; } } #else int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout) { if (sw_atomic_cmp_set(atomic, (sw_atomic_t) 1, (sw_atomic_t) 0)) { return 0; } timeout = timeout <= 0 ? INT_MAX : timeout; int32_t i = (int32_t) sw_atomic_sub_fetch(atomic, 1); while (timeout > 0) { if ((int32_t) *atomic > i) { return 0; } else { usleep(1000); timeout -= 0.001; } } sw_atomic_fetch_add(atomic, 1); return -1; } int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n) { if (1 == (int32_t) *atomic) { return 0; } sw_atomic_fetch_add(atomic, n); return 0; } #endif /* {{{ DJBX33A (Daniel J. Bernstein, Times 33 with Addition) * * This is Daniel J. Bernstein's popular `times 33' hash function as * posted by him years ago on comp->lang.c. It basically uses a function * like ``hash(i) = hash(i-1) * 33 + str[i]''. This is one of the best * known hash functions for strings. Because it is both computed very * fast and distributes very well. * * The magic of number 33, i.e. why it works better than many other * constants, prime or not, has never been adequately explained by * anyone. So I try an explanation: if one experimentally tests all * multipliers between 1 and 256 (as RSE did now) one detects that even * numbers are not useable at all. The remaining 128 odd numbers * (except for the number 1) work more or less all equally well. They * all distribute in an acceptable way and this way fill a hash table * with an average percent of approx. 86%. * * If one compares the Chi^2 values of the variants, the number 33 not * even has the best value. But the number 33 and a few other equally * good numbers like 17, 31, 63, 127 and 129 have nevertheless a great * advantage to the remaining numbers in the large set of possible * multipliers: their multiply operation can be replaced by a faster * operation based on just one shift plus either a single addition * or subtraction operation. And because a hash function has to both * distribute good _and_ has to be very fast to compute, those few * numbers should be preferred and seems to be the reason why Daniel J. * Bernstein also preferred it. * * -- Ralf S. Engelschall */ uint64_t swoole_hash_php(const char *key, size_t len) { ulong_t hash = 5381; /* variant with the hash unrolled eight times */ for (; len >= 8; len -= 8) { hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; hash = ((hash << 5) + hash) + *key++; } switch (len) { case 7: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 6: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 5: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 4: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 3: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 2: hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ /* no break */ case 1: hash = ((hash << 5) + hash) + *key++; break; case 0: break; default: break; } return hash; } #define HASH_JEN_MIX(a, b, c) \ do { \ a -= b; \ a -= c; \ a ^= (c >> 13); \ b -= c; \ b -= a; \ b ^= (a << 8); \ c -= a; \ c -= b; \ c ^= (b >> 13); \ a -= b; \ a -= c; \ a ^= (c >> 12); \ b -= c; \ b -= a; \ b ^= (a << 16); \ c -= a; \ c -= b; \ c ^= (b >> 5); \ a -= b; \ a -= c; \ a ^= (c >> 3); \ b -= c; \ b -= a; \ b ^= (a << 10); \ c -= a; \ c -= b; \ c ^= (b >> 15); \ } while (0) /** * MurmurHash2(Austin Appleby) */ uint64_t swoole_hash_jenkins(const char *key, size_t keylen) { unsigned j; uint64_t hashv = 0xfeedbeef; unsigned i = j = 0x9e3779b9; auto k = (unsigned) (keylen); while (k >= 12) { i += (key[0] + ((unsigned) key[1] << 8) + ((unsigned) key[2] << 16) + ((unsigned) key[3] << 24)); j += (key[4] + ((unsigned) key[5] << 8) + ((unsigned) key[6] << 16) + ((unsigned) key[7] << 24)); hashv += (key[8] + ((unsigned) key[9] << 8) + ((unsigned) key[10] << 16) + ((unsigned) key[11] << 24)); HASH_JEN_MIX(i, j, hashv); key += 12; k -= 12; } hashv += keylen; switch (k) { case 11: hashv += ((unsigned) key[10] << 24); /* no break */ case 10: hashv += ((unsigned) key[9] << 16); /* no break */ case 9: hashv += ((unsigned) key[8] << 8); /* no break */ case 8: j += ((unsigned) key[7] << 24); /* no break */ case 7: j += ((unsigned) key[6] << 16); /* no break */ case 6: j += ((unsigned) key[5] << 8); /* no break */ case 5: j += key[4]; /* no break */ case 4: i += ((unsigned) key[3] << 24); /* no break */ case 3: i += ((unsigned) key[2] << 16); /* no break */ case 2: i += ((unsigned) key[1] << 8); /* no break */ case 1: i += key[0]; } HASH_JEN_MIX(i, j, hashv); return hashv; } /** * MurmurHash2(Austin Appleby) */ uint64_t swoole_hash_austin(const char *key, size_t keylen) { uint64_t h = 0 ^ keylen; while (keylen >= 4) { uint64_t k = key[0]; k |= key[1] << 8; k |= key[2] << 16; k |= key[3] << 24; k *= 0x5bd1e995; k ^= k >> 24; k *= 0x5bd1e995; h *= 0x5bd1e995; h ^= k; key += 4; keylen -= 4; } switch (keylen) { case 3: h ^= key[2] << 16; /* no break */ case 2: h ^= key[1] << 8; /* no break */ case 1: h ^= key[0]; h *= 0x5bd1e995; } h ^= h >> 13; h *= 0x5bd1e995; h ^= h >> 15; return h; } ================================================ FILE: src/core/string.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_base64.h" #include namespace swoole { void String::alloc(size_t _size, const Allocator *_allocator) { if (_allocator == nullptr) { _allocator = sw_std_allocator(); } _size = SW_MEM_ALIGNED_SIZE(_size); length = 0; size = _size; offset = 0; str = (char *) _allocator->malloc(_size); allocator = _allocator; if (str == nullptr) { throw std::bad_alloc(); } } void String::move(String &&src) { str = src.str; length = src.length; offset = src.offset; size = src.size; allocator = src.allocator; src.str = nullptr; src.length = 0; src.size = 0; src.offset = 0; } String &String::operator=(const String &src) noexcept { if (&src == this) { return *this; } if (allocator && str) { allocator->free(str); } copy(src); return *this; } void String::copy(const String &src) { alloc(src.size, src.allocator); memcpy(str, src.str, src.length); length = src.length; offset = src.offset; } String &String::operator=(String &&src) noexcept { if (&src == this) { return *this; } if (allocator && str) { allocator->free(str); } move(std::move(src)); return *this; } void String::append(const String &append_str) { size_t new_size = length + append_str.length; if (new_size > size) { reserve(new_size); } memcpy(str + length, append_str.str, append_str.length); length += append_str.length; } void String::write(off_t _offset, const String &write_str) { write(_offset, write_str.str, write_str.length); } void String::write(off_t _offset, const char *write_str, size_t _length) { size_t new_length = _offset + _length; if (new_length > size) { reserve(swoole_size_align(new_length * 2, swoole_pagesize())); } memcpy(str + _offset, write_str, _length); if (new_length > length) { length = new_length; } } void String::grow(size_t incr_value) { length += incr_value; if (length == size) { reserve(size * 2); } } String String::substr(size_t _offset, size_t len) const { if (_offset + len > length) { return {}; } String _substr(len); _substr.append(str + _offset, len); return _substr; } char *String::pop(size_t init_size) { assert(length >= (size_t) offset); char *val = str; size_t _length = length - offset; size_t alloc_size = SW_MEM_ALIGNED_SIZE(_length == 0 ? init_size : SW_MAX(_length, init_size)); auto new_val = static_cast(allocator->malloc(alloc_size)); if (new_val == nullptr) { return nullptr; } str = new_val; size = alloc_size; length = _length; if (length > 0) { memcpy(new_val, val + offset, length); } offset = 0; return val; } /** * migrate data to head, [offset, length - offset] -> [0, length - offset] */ void String::reduce(off_t _offset) { assert(_offset >= 0 && (size_t) _offset <= length); if (sw_unlikely(_offset == 0)) { return; } length -= _offset; offset = 0; if (length == 0) { return; } memmove(str, str + _offset, length); } void String::print(bool print_value) const { if (print_value) { printf("String[length=%zu,size=%zu,offset=%jd]=%.*s\n", length, size, (intmax_t) offset, (int) length, str); } else { printf("String[length=%zu,size=%zu,offset=%jd]=%p\n", length, size, (intmax_t) offset, str); } } void String::append(int value) { char buf[16]; int s_len = swoole_itoa(buf, value); size_t new_size = length + s_len; if (new_size > size) { reserve(new_size); } memcpy(str + length, buf, s_len); length += s_len; } void String::append(const char *append_str, size_t _length) { size_t new_size = length + _length; if (new_size > size) { reserve(new_size); } memcpy(str + length, append_str, _length); length += _length; } bool String::append_random_bytes(size_t _length, bool base64) { size_t new_size = length + _length; size_t base_encode_size; if (base64) { base_encode_size = BASE64_ENCODE_OUT_SIZE(_length) + 1; new_size += base_encode_size; } if (new_size > size) { reserve(swoole_size_align(new_size * 2, swoole_pagesize())); } size_t n = swoole_random_bytes(str + length, _length); if (n != _length) { return false; } if (base64) { std::unique_ptr out(new char[base_encode_size]); n = base64_encode((uchar *) str + length, _length, out.get()); memcpy(str + length, out.get(), n); } length += n; return true; } void String::reserve(size_t new_size) { if (size == 0) { alloc(new_size, nullptr); return; } new_size = SW_MEM_ALIGNED_SIZE(new_size); auto new_str = static_cast(allocator->realloc(str, new_size)); if (new_str == nullptr) { throw std::bad_alloc(); } str = new_str; size = new_size; } char *String::release() { char *tmp = str; str = nullptr; size = 0; length = 0; offset = 0; return tmp; } void String::repeat(const char *data, size_t len, size_t n) { assert(n > 0); if (len == 1) { if ((size < length + n)) { reserve(length + n); } memset(str + length, data[0], n); length += n; } else { for (size_t i = 0; i < n; i++) { append(data, len); } } } /** * @return retval * 1. less than zero, the execution of the string_split function was terminated prematurely * 2. equal to zero, eof was not found in the target string * 3. greater than zero, 0 to retval has eof in the target string, and the position of retval is eof */ ssize_t String::split(const char *delimiter, size_t delimiter_length, const StringExplodeHandler &handler) { #ifdef SW_LOG_TRACE_OPEN static int count; count++; #endif const char *start_addr = str + offset; const char *delimiter_addr = swoole_strnstr(start_addr, length - offset, delimiter, delimiter_length); off_t _offset = offset; swoole_trace_log(SW_TRACE_EOF_PROTOCOL, "#[0] count=%d, length=%ld, size=%ld, offset=%jd", count, length, size, (intmax_t) offset); while (delimiter_addr) { size_t _length = delimiter_addr - start_addr + delimiter_length; swoole_trace_log(SW_TRACE_EOF_PROTOCOL, "#[4] count=%d, length=%zu", count, (size_t) (_length + offset)); if (handler((char *) start_addr - _offset, _length + _offset) == false) { return -1; } offset += _length; start_addr = str + offset; delimiter_addr = swoole_strnstr(start_addr, length - offset, delimiter, delimiter_length); _offset = 0; } /** * not found eof in str */ if (_offset == offset) { /** * why is offset not equal to length, * because the length may contain part of eof and the other part in the next recv */ offset = length - delimiter_length; } size_t ret = start_addr - str - _offset; if (ret > 0 && ret < length) { swoole_trace_log( SW_TRACE_EOF_PROTOCOL, "#[5] count=%d, remaining_length=%zu", count, (size_t) (length - offset)); } else if (ret >= length) { swoole_trace_log( SW_TRACE_EOF_PROTOCOL, "#[3] length=%ld, size=%zu, offset=%jd", length, size, (intmax_t) offset); } return ret; } } // namespace swoole ================================================ FILE: src/core/timer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_reactor.h" #include "swoole_timer.h" namespace swoole { Timer::Timer(bool manually_trigger) : heap(1024, Heap::MIN_HEAP) { _current_id = -1; next_msec_ = -1; _next_id = 1; round = 0; base_time = get_absolute_msec(); init(manually_trigger); } void Timer::init(bool manually_trigger) { if (manually_trigger) { set = [](Timer *, long) -> int { return SW_OK; }; close = [](Timer *) {}; return; } if (SwooleTG.reactor) { init_with_reactor(SwooleTG.reactor); } else { init_with_system_timer(); } } void Timer::release_node(TimerNode *tnode) { if (tnode->destructor) { tnode->destructor(tnode); } delete tnode; } void Timer::init_with_reactor(Reactor *reactor) { reactor_ = reactor; set = [](Timer *timer, long exec_msec) -> int { timer->reactor_->timeout_msec = exec_msec; return SW_OK; }; close = [](Timer *timer) { timer->set(timer, -1); }; reactor->set_end_callback(Reactor::PRIORITY_TIMER, [this](Reactor *) { select(); }); reactor->set_exit_condition(Reactor::EXIT_CONDITION_TIMER, [this](Reactor *reactor, size_t &event_num) -> bool { return count() == 0; }); reactor->add_destroy_callback([](void *) { if (swoole_timer_is_available()) { swoole_timer_free(); } }); } void Timer::reinit(bool manually_trigger) { close(this); init(manually_trigger); set(this, next_msec_); } Timer::~Timer() { if (close) { close(this); } for (const auto &iter : map) { release_node(iter.second); } } TimerNode *Timer::add(long _msec, bool persistent, void *data, const TimerCallback &callback) { if (sw_unlikely(_msec <= 0)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "msec value[%ld] is invalid", _msec); return nullptr; } auto *tnode = new TimerNode(); tnode->id = _next_id++; tnode->data = data; tnode->type = TimerNode::TYPE_KERNEL; tnode->exec_msec = get_relative_msec() + _msec; tnode->interval = persistent ? _msec : 0; tnode->removed = false; tnode->callback = callback; tnode->round = round; tnode->destructor = nullptr; if (next_msec_ < 0 || next_msec_ > _msec) { set(this, _msec); next_msec_ = _msec; } tnode->heap_node = heap.push(tnode->exec_msec, tnode); if (sw_unlikely(tnode->heap_node == nullptr)) { release_node(tnode); return nullptr; } map.emplace(tnode->id, tnode); swoole_trace_log(SW_TRACE_TIMER, "id=%ld, exec_msec=%" PRId64 ", msec=%ld, round=%" PRIu64 ", exist=%lu", tnode->id, tnode->exec_msec, _msec, tnode->round, count()); return tnode; } bool Timer::remove(TimerNode *tnode) { if (sw_unlikely(!tnode || tnode->removed)) { return false; } if (sw_unlikely(_current_id > 0 && tnode->id == _current_id)) { tnode->removed = true; swoole_trace_log(SW_TRACE_TIMER, "set-remove: id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu", tnode->id, tnode->exec_msec, tnode->round, count()); return true; } if (sw_unlikely(!map.erase(tnode->id))) { return false; } if (tnode->heap_node) { heap.remove(tnode->heap_node); } swoole_trace_log(SW_TRACE_TIMER, "id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu", tnode->id, tnode->exec_msec, tnode->round, count()); release_node(tnode); return true; } void Timer::select() { const int64_t now_msec = get_relative_msec(); TimerNode *tnode = nullptr; HeapNode *tmp; swoole_trace_log(SW_TRACE_TIMER, "select begin: now_msec=%" PRId64 ", round=%" PRId64, now_msec, round); while ((tmp = heap.top())) { tnode = static_cast(tmp->data); if (tnode->exec_msec > now_msec || tnode->round == round) { break; } _current_id = tnode->id; if (!tnode->removed) { swoole_trace_log(SW_TRACE_TIMER, "execute callback [id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu]", tnode->id, tnode->exec_msec, tnode->round, count() - 1); tnode->callback(this, tnode); } _current_id = -1; // persistent timer if (tnode->interval > 0 && !tnode->removed) { while (tnode->exec_msec <= now_msec) { tnode->exec_msec += tnode->interval; } tnode->exec_count++; heap.change_priority(tnode->exec_msec, tmp); continue; } heap.pop(); map.erase(tnode->id); release_node(tnode); tnode = nullptr; } if (!tnode || !tmp) { next_msec_ = -1; set(this, -1); } else { next_msec_ = tnode->exec_msec - now_msec; if (next_msec_ <= 0) { next_msec_ = 1; } set(this, next_msec_); } round++; } }; // namespace swoole ================================================ FILE: src/coroutine/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_coroutine.h" #include "swoole_coroutine_api.h" namespace swoole { SW_THREAD_LOCAL Coroutine *Coroutine::current = nullptr; SW_THREAD_LOCAL long Coroutine::last_cid = 0; SW_THREAD_LOCAL long Coroutine::socket_bound_cid = 0; SW_THREAD_LOCAL std::unordered_map Coroutine::coroutines; SW_THREAD_LOCAL uint64_t Coroutine::peak_num = 0; SW_THREAD_LOCAL bool Coroutine::activated = false; SW_THREAD_LOCAL size_t Coroutine::stack_size = SW_DEFAULT_C_STACK_SIZE; SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_yield = nullptr; SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_resume = nullptr; SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_close = nullptr; SW_THREAD_LOCAL Coroutine::BailoutCallback Coroutine::on_bailout = nullptr; #ifdef SW_USE_THREAD_CONTEXT namespace coroutine { void thread_context_init(); void thread_context_clean(); } // namespace coroutine #endif void Coroutine::activate() { #ifdef SW_USE_THREAD_CONTEXT coroutine::thread_context_init(); #endif activated = true; on_bailout = nullptr; } void Coroutine::deactivate() { #ifdef SW_USE_THREAD_CONTEXT coroutine::thread_context_clean(); #endif activated = false; on_bailout = []() { // The coroutine scheduler has been destroyed, // Can not resume any coroutine // Expect that never here swoole_error("have been bailout, can not resume any coroutine"); }; } #ifdef SW_CORO_TIME void Coroutine::calc_execute_usec(Coroutine *yield_coroutine, Coroutine *resume_coroutine) { long current_usec = time(true); if (yield_coroutine) { yield_coroutine->execute_usec += current_usec - yield_coroutine->switch_usec; } if (resume_coroutine) { resume_coroutine->switch_usec = current_usec; } } #endif Coroutine::Coroutine(const CoroutineFunc &fn, void *private_data) : ctx(stack_size, fn, private_data) { cid = ++last_cid; coroutines[cid] = this; if (sw_unlikely(count() > peak_num)) { peak_num = count(); } if (!activated) { activate(); } } void Coroutine::check_end() { if (ctx.is_end()) { close(); } else if (sw_unlikely(on_bailout)) { SW_ASSERT(current == nullptr); on_bailout(); } } long Coroutine::run() { const long _cid = cid; origin = current; current = this; CALC_EXECUTE_USEC(origin, nullptr); state = STATE_RUNNING; ctx.swap_in(); check_end(); return _cid; } void Coroutine::yield() { SW_ASSERT(current == this || on_bailout != nullptr); state = STATE_WAITING; resume_code_ = RC_OK; if (sw_likely(on_yield && task)) { on_yield(task); } current = origin; CALC_EXECUTE_USEC(this, current); ctx.swap_out(); } void Coroutine::yield(CancelFunc *cancel_fn) { set_cancel_fn(cancel_fn); yield(); set_cancel_fn(nullptr); } bool Coroutine::yield_ex(double timeout) { TimerNode *timer = nullptr; TimerCallback timer_callback = [this](Timer *timer, TimerNode *tnode) { resume_code_ = RC_TIMEDOUT; resume(); }; if (timeout > 0) { timer = swoole_timer_add(timeout, false, timer_callback, nullptr); } CancelFunc cancel_fn = [](Coroutine *co) { co->resume(); return true; }; yield(&cancel_fn); if (is_timedout()) { swoole_set_last_error(SW_ERROR_CO_TIMEDOUT); return false; } if (timer) { swoole_timer_del(timer); } if (is_canceled()) { swoole_set_last_error(SW_ERROR_CO_CANCELED); return false; } return true; } void Coroutine::resume() { SW_ASSERT(current != this); if (sw_unlikely(on_bailout)) { return; } state = STATE_RUNNING; if (sw_likely(on_resume && task)) { on_resume(task); } origin = current; current = this; CALC_EXECUTE_USEC(origin, this); ctx.swap_in(); check_end(); } bool Coroutine::cancel() { if (!cancel_fn_) { swoole_set_last_error(SW_ERROR_CO_CANNOT_CANCEL); return false; } auto fn = *cancel_fn_; set_cancel_fn(nullptr); resume_code_ = RC_CANCELED; return fn(this); } void Coroutine::close() { SW_ASSERT(current == this); state = STATE_END; if (on_close && task) { on_close(task); } #if !defined(SW_USE_THREAD_CONTEXT) && defined(SW_CONTEXT_DETECT_STACK_USAGE) swoole_trace_log( SW_TRACE_CONTEXT, "coroutine#%ld stack memory use less than %ld bytes", get_cid(), ctx.get_stack_usage()); #endif current = origin; coroutines.erase(cid); delete this; } void Coroutine::print_list() { for (auto &coroutine : coroutines) { const char *state; switch (coroutine.second->state) { case STATE_INIT: state = "[INIT]"; break; case STATE_WAITING: state = "[WAITING]"; break; case STATE_RUNNING: state = "[RUNNING]"; break; case STATE_END: state = "[END]"; break; default: abort(); return; } sw_printf("Coroutine\t%ld\t%s\n", coroutine.first, state); } } void Coroutine::print_socket_bound_error(int sock_fd, const char *event_str, long bound_cid) { socket_bound_cid = bound_cid; swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, "Socket#%d has already been bound to another coroutine#%ld, " "%s of the same socket in coroutine#%ld at the same time is not allowed", sock_fd, socket_bound_cid, event_str, get_current_cid()); } void Coroutine::set_on_yield(const SwapCallback func) { on_yield = func; } void Coroutine::set_on_resume(const SwapCallback func) { on_resume = func; } void Coroutine::set_on_close(const SwapCallback func) { on_close = func; } void Coroutine::bailout(const BailoutCallback &func) { Coroutine *co = current; if (!co) { // marks that it can no longer resume any coroutine static BailoutCallback fn = []() { // expect that never here swoole_error("have been bailout, can not resume any coroutine"); }; on_bailout = fn; return; } if (!func) { swoole_error("bailout without callback function"); } on_bailout = func; // find the coroutine which is closest to the main while (co->origin) { co = co->origin; } // it will jump to main context directly (it also breaks contexts) co->yield(); // expect that never here exit(SW_CORO_BAILOUT_EXIT_CODE); } namespace coroutine { bool run(const CoroutineFunc &fn, void *arg) { if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { return false; } Coroutine::activate(); long cid = Coroutine::create(fn, arg); swoole_event_wait(); Coroutine::deactivate(); return cid > 0; } } // namespace coroutine } // namespace swoole uint8_t swoole_coroutine_is_in() { return !!swoole::Coroutine::get_current(); } long swoole_coroutine_create(void (*routine)(void *), void *arg) { if (sw_likely(swoole_event_is_available())) { return swoole::Coroutine::create(routine, arg); } else { if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { return -1; } swoole::Coroutine::activate(); long cid = swoole::Coroutine::create(routine, arg); swoole_event_wait(); swoole::Coroutine::deactivate(); return cid; } } long swoole_coroutine_get_id() { return swoole::Coroutine::get_current_cid(); } swoole::Coroutine *swoole_coroutine_get(long cid) { auto i = swoole::Coroutine::coroutines.find(cid); if (i == swoole::Coroutine::coroutines.end()) { return nullptr; } else { return i->second; } } size_t swoole_coroutine_count() { return swoole::Coroutine::coroutines.size(); } /** * for gdb */ static std::unordered_map::iterator gdb_iterator_; void swoole_coroutine_iterator_reset() { gdb_iterator_ = swoole::Coroutine::coroutines.begin(); } swoole::Coroutine *swoole_coroutine_iterator_each() { if (gdb_iterator_ == swoole::Coroutine::coroutines.end()) { return nullptr; } swoole::Coroutine *co = gdb_iterator_->second; ++gdb_iterator_; return co; } ================================================ FILE: src/coroutine/channel.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_coroutine_channel.h" namespace swoole { namespace coroutine { void Channel::timer_callback(Timer *timer, TimerNode *tnode) { auto *msg = static_cast(tnode->data); msg->error = true; msg->timer = nullptr; if (msg->type == CONSUMER) { msg->chan->consumer_remove(msg->co); } else { msg->chan->producer_remove(msg->co); } msg->co->resume(); } void Channel::yield(Opcode type) { Coroutine *co = Coroutine::get_current_safe(); if (type == PRODUCER) { producer_queue.push_back(co); swoole_trace_log(SW_TRACE_CHANNEL, "producer cid=%ld", co->get_cid()); } else { consumer_queue.push_back(co); swoole_trace_log(SW_TRACE_CHANNEL, "consumer cid=%ld", co->get_cid()); } Coroutine::CancelFunc cancel_fn = [this, type](Coroutine *co) { if (type == CONSUMER) { consumer_remove(co); } else { producer_remove(co); } co->resume(); return true; }; co->yield(&cancel_fn); } void *Channel::pop(double timeout) { Coroutine *current_co = Coroutine::get_current_safe(); if (closed && is_empty()) { error_ = ERROR_CLOSED; return nullptr; } if (is_empty() || !consumer_queue.empty()) { TimeoutMessage msg; msg.error = false; msg.timer = nullptr; if (timeout > 0) { msg.chan = this; msg.type = CONSUMER; msg.co = current_co; msg.timer = swoole_timer_add(timeout, false, timer_callback, &msg); } yield(CONSUMER); if (msg.timer) { swoole_timer_del(msg.timer); } if (current_co->is_canceled()) { error_ = ERROR_CANCELED; return nullptr; } if (msg.error) { error_ = ERROR_TIMEOUT; return nullptr; } if (closed && is_empty()) { error_ = ERROR_CLOSED; return nullptr; } } /** * pop data */ void *data = data_queue.front(); data_queue.pop(); /** * notify producer */ if (!producer_queue.empty()) { Coroutine *co = pop_coroutine(PRODUCER); co->resume(); } return data; } bool Channel::push(void *data, double timeout) { Coroutine *current_co = Coroutine::get_current_safe(); if (closed) { error_ = ERROR_CLOSED; return false; } if (is_full() || !producer_queue.empty()) { TimeoutMessage msg; msg.error = false; msg.timer = nullptr; if (timeout > 0) { msg.chan = this; msg.type = PRODUCER; msg.co = current_co; msg.timer = swoole_timer_add(timeout, false, timer_callback, &msg); } yield(PRODUCER); if (msg.timer) { swoole_timer_del(msg.timer); } if (current_co->is_canceled()) { error_ = ERROR_CANCELED; return false; } if (msg.error) { error_ = ERROR_TIMEOUT; return false; } if (closed) { error_ = ERROR_CLOSED; return false; } } /** * push data */ data_queue.push(data); swoole_trace_log(SW_TRACE_CHANNEL, "push data to channel, count=%ld", length()); /** * notify consumer */ if (!consumer_queue.empty()) { Coroutine *co = pop_coroutine(CONSUMER); co->resume(); } return true; } bool Channel::close() { if (closed) { return false; } swoole_trace_log(SW_TRACE_CHANNEL, "channel closed"); closed = true; while (!producer_queue.empty()) { Coroutine *co = pop_coroutine(PRODUCER); co->resume(); } while (!consumer_queue.empty()) { Coroutine *co = pop_coroutine(CONSUMER); co->resume(); } return true; } } // namespace coroutine } // namespace swoole ================================================ FILE: src/coroutine/context.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_coroutine_context.h" #ifdef SW_CONTEXT_PROTECT_STACK_PAGE #include #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif #endif #ifndef SW_USE_THREAD_CONTEXT #define MAGIC_STRING "swoole_coroutine#5652a7fb2b38be" #define START_OFFSET (64 * 1024) namespace swoole { namespace coroutine { Context::Context(size_t stack_size, CoroutineFunc fn, void *private_data) : fn_(std::move(fn)), stack_size_(stack_size), private_data_(private_data) { end_ = false; #ifdef SW_CONTEXT_PROTECT_STACK_PAGE int mapflags = MAP_PRIVATE | MAP_ANONYMOUS; #ifdef __OpenBSD__ // no-op for Linux and NetBSD, not to enable on FreeBSD as the semantic differs. // However, necessary on OpenBSD. mapflags |= MAP_STACK; #endif stack_ = (char *) ::mmap(0, stack_size_, PROT_READ | PROT_WRITE, mapflags, -1, 0); #else stack_ = (char *) sw_malloc(stack_size_); #endif if (!stack_) { swoole_fatal_error(SW_ERROR_MALLOC_FAIL, "failed to malloc stack memory."); exit(254); } swoole_trace_log(SW_TRACE_COROUTINE, "alloc stack: size=%u, ptr=%p", stack_size_, stack_); void *sp = (void *) ((char *) stack_ + stack_size_); #ifdef USE_VALGRIND valgrind_stack_id = VALGRIND_STACK_REGISTER(sp, stack_); #endif #ifdef USE_UCONTEXT if (-1 == getcontext(&ctx_)) { swoole_throw_error(SW_ERROR_CO_GETCONTEXT_FAILED); sw_free(stack_); return; } ctx_.uc_stack.ss_sp = stack_; ctx_.uc_stack.ss_size = stack_size; ctx_.uc_link = nullptr; makecontext(&ctx_, (void (*)(void)) & context_func, 1, this); #else ctx_ = swoole_make_fcontext(sp, stack_size_, (void (*)(transfer_t)) & context_func); swap_ctx_ = nullptr; #endif #ifdef SW_CONTEXT_DETECT_STACK_USAGE size_t offset = START_OFFSET; while (offset <= stack_size) { memcpy((char *) sp - offset + (sizeof(MAGIC_STRING) - 1), SW_STRL(MAGIC_STRING)); offset *= 2; } #endif #ifdef SW_CONTEXT_PROTECT_STACK_PAGE mprotect(stack_, SwooleG.pagesize, PROT_NONE); #endif } Context::~Context() { if (stack_) { swoole_trace_log(SW_TRACE_COROUTINE, "free stack: ptr=%p", stack_); #ifdef USE_VALGRIND VALGRIND_STACK_DEREGISTER(valgrind_stack_id); #endif #ifdef SW_CONTEXT_PROTECT_STACK_PAGE ::munmap(stack_, stack_size_); #else sw_free(stack_); #endif stack_ = nullptr; } } #ifdef SW_CONTEXT_DETECT_STACK_USAGE ssize_t Context::get_stack_usage() { size_t offset = START_OFFSET; size_t retval = START_OFFSET; void *sp = (void *) ((char *) stack_ + stack_size_); while (offset < stack_size_) { if (memcmp((char *) sp - offset + (sizeof(MAGIC_STRING) - 1), SW_STRL(MAGIC_STRING)) != 0) { retval = offset * 2; } offset *= 2; } return retval; } #endif bool Context::swap_in() { #ifdef USE_UCONTEXT return 0 == swapcontext(&swap_ctx_, &ctx_); #else coroutine_transfer_t transfer_data = swoole_jump_fcontext(ctx_, (void *) this); ctx_ = transfer_data.fctx; return true; #endif } bool Context::swap_out() { #ifdef USE_UCONTEXT return 0 == swapcontext(&ctx_, &swap_ctx_); #else coroutine_transfer_t transfer_data = swoole_jump_fcontext(swap_ctx_, (void *) this); swap_ctx_ = transfer_data.fctx; return true; #endif } void Context::context_func(coroutine_transfer_t arg) { #if defined(USE_UCONTEXT) || defined(SW_USE_THREAD_CONTEXT) auto *_this = (Context *) arg; #else auto *_this = (Context *) arg.data; _this->swap_ctx_ = arg.fctx; #endif _this->fn_(_this->private_data_); _this->end_ = true; _this->swap_out(); } } // namespace coroutine } // namespace swoole #endif ================================================ FILE: src/coroutine/file.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "swoole_file.h" #include "swoole_coroutine_api.h" namespace swoole { AsyncFile::AsyncFile(const std::string &path, int flags, int mode) { open(path, flags, mode); } AsyncFile::~AsyncFile() { close(); } bool AsyncFile::open(const std::string &path, int flags, mode_t mode) { close(); flags_ = flags; mode_ = mode; path_ = path; fd = swoole_coroutine_open(path.c_str(), flags, mode); return fd > 0; } bool AsyncFile::close() const { if (sw_unlikely(fd == -1)) { return false; } return swoole_coroutine_close(fd) == 0; } ssize_t AsyncFile::read(void *buf, size_t count) const { return swoole_coroutine_read(fd, buf, count); } ssize_t AsyncFile::write(const void *buf, size_t count) const { return swoole_coroutine_write(fd, buf, count); } bool AsyncFile::sync() const { return swoole_coroutine_fsync(fd) == 0; } bool AsyncFile::truncate(off_t length) const { return swoole_coroutine_ftruncate(fd, length) == 0; } bool AsyncFile::stat(FileStatus *statbuf) const { return swoole_coroutine_fstat(fd, statbuf) == 0; } off_t AsyncFile::get_offset() const { return swoole_coroutine_lseek(fd, 0, SEEK_CUR); } off_t AsyncFile::set_offset(off_t offset) const { return swoole_coroutine_lseek(fd, offset, SEEK_SET); } } // namespace swoole ================================================ FILE: src/coroutine/file_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include #include "swoole_coroutine.h" #include "swoole_coroutine_api.h" using swoole::Coroutine; using swoole::coroutine::wait_for; static inline int do_lock(int fd, int operation) { int retval = 0; auto success = wait_for([&retval, operation, fd]() { auto rv = flock(fd, operation | LOCK_NB); if (rv == 0) { retval = 0; } else if (rv == -1 && errno == EWOULDBLOCK) { return false; } else { retval = -1; } return true; }); return success ? retval : -1; } static inline int lock_ex(int fd) { return do_lock(fd, LOCK_EX); } static inline int lock_sh(int fd) { return do_lock(fd, LOCK_SH); } static inline int lock_release(int fd) { return flock(fd, LOCK_UN); } int swoole_coroutine_flock(int fd, int operation) { Coroutine *co = Coroutine::get_current(); if (sw_unlikely(SwooleTG.reactor == nullptr || !co)) { return ::flock(fd, operation); } if (operation & LOCK_NB) { return ::flock(fd, operation); } switch (operation) { case LOCK_EX: return lock_ex(fd); case LOCK_SH: return lock_sh(fd); case LOCK_UN: return lock_release(fd); default: break; } errno = EINVAL; swoole_set_last_error(EINVAL); return -1; } ================================================ FILE: src/coroutine/hook.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include #include #include #include #include #include #include #include #include "swoole_coroutine_system.h" #include "swoole_socket_impl.h" #include "swoole_iouring.h" using swoole::AsyncEvent; using swoole::Coroutine; using swoole::EventType; using swoole::translate_events_from_poll; using swoole::translate_events_to_poll; using swoole::async::dispatch; using swoole::coroutine::async; using swoole::coroutine::PollSocket; using swoole::coroutine::System; #ifdef SW_USE_IOURING using swoole::Iouring; #else #define SW_USE_ASYNC 1 #endif static std::unordered_map> socket_map; static std::mutex socket_map_lock; #if defined(__APPLE__) || defined(__MACH__) static int fdatasync(int fd) { return fcntl(fd, F_FULLFSYNC); } #endif static sw_inline bool is_no_coro() { return SwooleTG.reactor == nullptr || !Coroutine::get_current(); } static sw_inline std::shared_ptr get_socket(int sockfd) { std::unique_lock _lock(socket_map_lock); auto socket_iterator = socket_map.find(sockfd); if (socket_iterator == socket_map.end()) { errno = ENOTSOCK; return nullptr; } return socket_iterator->second; } static sw_inline std::shared_ptr get_socket_ex(int sockfd) { if (sw_unlikely(is_no_coro())) { errno = EWOULDBLOCK; return nullptr; } return get_socket(sockfd); } std::shared_ptr swoole_coroutine_get_socket_object(int sockfd) { return get_socket(sockfd); } std::shared_ptr swoole_coroutine_get_socket_object_ex(int sockfd) { return get_socket_ex(sockfd); } SW_EXTERN_C_BEGIN int swoole_coroutine_socket(int domain, int type, int protocol) { if (sw_unlikely(is_no_coro())) { return ::socket(domain, type, protocol); } auto socket = std::make_shared(domain, type, protocol); int fd = socket->get_fd(); if (sw_unlikely(fd < 0)) { return -1; } else { std::unique_lock _lock(socket_map_lock); socket_map[fd] = socket; } return fd; } ssize_t swoole_coroutine_send(int sockfd, const void *buf, size_t len, int flags) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::send(sockfd, buf, len, flags); } return socket->send(buf, len); } ssize_t swoole_coroutine_sendmsg(int sockfd, const struct msghdr *msg, int flags) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::sendmsg(sockfd, msg, flags); } return socket->sendmsg(msg, flags); } ssize_t swoole_coroutine_recvmsg(int sockfd, struct msghdr *msg, int flags) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::recvmsg(sockfd, msg, flags); } return socket->recvmsg(msg, flags); } ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::recv(sockfd, buf, len, flags); } if (flags & MSG_PEEK) { return socket->peek(buf, len); } else { return socket->recv(buf, len); } } int swoole_coroutine_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::connect(sockfd, addr, addrlen); } return socket->connect(addr, addrlen) ? 0 : -1; } int swoole_coroutine_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { return ::accept(sockfd, addr, addrlen); } auto conn = socket->accept(0); if (!conn) { return -1; } *addrlen = conn->get_socket()->info.len; memcpy(addr, &conn->get_socket()->info.addr.ss, *addrlen); std::unique_lock _lock(socket_map_lock); socket_map.emplace(conn->get_fd(), std::shared_ptr(conn)); return conn->get_fd(); } int swoole_coroutine_poll_fake(struct pollfd *fds, nfds_t nfds, int timeout) { if (nfds != 1) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); swoole_warning("fake poll() implementation, only supports one fd"); return -1; } auto socket = get_socket_ex(fds[0].fd); if (sw_unlikely(timeout == 0 || socket == nullptr)) { return poll(fds, nfds, timeout); } socket->set_timeout((double) timeout / 1000); if (fds[0].events & POLLIN) { fds[0].revents |= POLLIN; } if (fds[0].events & POLLOUT) { fds[0].revents |= POLLOUT; } return 1; } int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout) { if (sw_unlikely(is_no_coro() || timeout == 0)) { return poll(fds, nfds, timeout); } #ifdef SW_USE_IOURING if (nfds == 1) { return Iouring::poll(fds, nfds, timeout); } #endif std::unordered_map _fds; for (nfds_t i = 0; i < nfds; i++) { _fds.emplace(fds[i].fd, PollSocket(translate_events_from_poll(fds[i].events), &fds[i])); } if (!System::socket_poll(_fds, (double) timeout / 1000)) { return -1; } int retval = 0; for (auto &i : _fds) { int revents = i.second.revents; auto *_fd = static_cast(i.second.ptr); _fd->revents = translate_events_to_poll(revents); if (revents > 0) { retval++; } } return retval; } int swoole_coroutine_socket_create(int fd) { if (sw_unlikely(is_no_coro())) { return -1; } auto socket = std::make_shared(fd, SW_SOCK_RAW); int _fd = socket->get_fd(); if (sw_unlikely(_fd < 0)) { return -1; } socket->get_socket()->set_nonblock(); std::unique_lock _lock(socket_map_lock); socket_map[fd] = socket; return 0; } int swoole_coroutine_socket_unwrap(int fd) { if (sw_unlikely(is_no_coro())) { return -1; } auto socket = get_socket(fd); if (socket == nullptr) { return -1; } std::unique_lock _lock(socket_map_lock); socket->move_fd(); socket_map.erase(fd); return 0; } uint8_t swoole_coroutine_socket_exists(int fd) { return socket_map.find(fd) != socket_map.end(); } FILE *swoole_coroutine_fopen(const char *pathname, const char *mode) { if (sw_unlikely(is_no_coro())) { return fopen(pathname, mode); } FILE *retval = nullptr; async([&]() { retval = fopen(pathname, mode); }); return retval; } FILE *swoole_coroutine_fdopen(int fd, const char *mode) { if (sw_unlikely(is_no_coro())) { return fdopen(fd, mode); } FILE *retval = nullptr; async([&]() { retval = fdopen(fd, mode); }); return retval; } FILE *swoole_coroutine_freopen(const char *pathname, const char *mode, FILE *stream) { if (sw_unlikely(is_no_coro())) { return freopen(pathname, mode, stream); } FILE *retval = nullptr; async([&]() { retval = freopen(pathname, mode, stream); }); return retval; } size_t swoole_coroutine_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { if (sw_unlikely(is_no_coro())) { return fread(ptr, size, nmemb, stream); } size_t retval = 0; async([&]() { retval = fread(ptr, size, nmemb, stream); }); return retval; } size_t swoole_coroutine_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (sw_unlikely(is_no_coro())) { return fwrite(ptr, size, nmemb, stream); } size_t retval = 0; async([&]() { retval = fwrite(ptr, size, nmemb, stream); }); return retval; } char *swoole_coroutine_fgets(char *s, int size, FILE *stream) { if (sw_unlikely(is_no_coro())) { return fgets(s, size, stream); } char *retval = nullptr; async([&]() { retval = fgets(s, size, stream); }); return retval; } int swoole_coroutine_fputs(const char *s, FILE *stream) { if (sw_unlikely(is_no_coro())) { return fputs(s, stream); } int retval = -1; async([&]() { retval = fputs(s, stream); }); return retval; } int swoole_coroutine_feof(FILE *stream) { if (sw_unlikely(is_no_coro())) { return feof(stream); } int retval = -1; async([&]() { retval = feof(stream); }); return retval; } int swoole_coroutine_fflush(FILE *stream) { if (sw_unlikely(is_no_coro())) { return fflush(stream); } int retval = -1; async([&]() { retval = fflush(stream); }); return retval; } int swoole_coroutine_fclose(FILE *stream) { if (sw_unlikely(is_no_coro())) { return fclose(stream); } int retval = -1; async([&]() { retval = fclose(stream); }); return retval; } DIR *swoole_coroutine_opendir(const char *name) { if (sw_unlikely(is_no_coro())) { return opendir(name); } DIR *retval = nullptr; async([&]() { retval = opendir(name); }); return retval; } struct dirent *swoole_coroutine_readdir(DIR *dirp) { if (sw_unlikely(is_no_coro())) { return readdir(dirp); } struct dirent *retval; async([&retval, dirp]() { retval = readdir(dirp); }); return retval; } int swoole_coroutine_closedir(DIR *dirp) { if (sw_unlikely(is_no_coro())) { return closedir(dirp); } int retval = -1; async([&]() { retval = closedir(dirp); }); return retval; } void swoole_coroutine_sleep(int sec) { System::sleep((double) sec); } void swoole_coroutine_usleep(int usec) { System::sleep((double) usec / 1024 / 1024); } int swoole_coroutine_socket_set_timeout(int sockfd, int which, double timeout) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { errno = EINVAL; return -1; } if (which == SO_RCVTIMEO) { socket->set_timeout(timeout, SW_TIMEOUT_READ); return 0; } else if (which == SO_SNDTIMEO) { socket->set_timeout(timeout, SW_TIMEOUT_WRITE); return 0; } else { errno = EINVAL; return -1; } } int swoole_coroutine_socket_set_connect_timeout(int sockfd, double timeout) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { errno = EINVAL; return -1; } socket->set_timeout(timeout, SW_TIMEOUT_DNS | SW_TIMEOUT_CONNECT); return 0; } int swoole_coroutine_socket_wait_event(int sockfd, int event, double timeout) { auto socket = get_socket_ex(sockfd); if (sw_unlikely(socket == nullptr)) { pollfd poll_ev{}; poll_ev.fd = sockfd; poll_ev.events = translate_events_to_poll(event); return poll(&poll_ev, 1, (int) (timeout * 1000)) == 1 ? SW_OK : SW_ERR; } double ori_timeout = socket->get_timeout(event == SW_EVENT_READ ? SW_TIMEOUT_READ : SW_TIMEOUT_WRITE); socket->set_timeout(timeout); bool retval = socket->poll(static_cast(event)); socket->set_timeout(ori_timeout); return retval ? SW_OK : SW_ERR; } int swoole_coroutine_getaddrinfo(const char *name, const char *service, const addrinfo *req, addrinfo **pai) { int retval = -1; async([&]() { retval = getaddrinfo(name, service, req, pai); }); return retval; } hostent *swoole_coroutine_gethostbyname(const char *name) { hostent *retval = nullptr; int _tmp_h_errno = 0; async([&]() { retval = gethostbyname(name); _tmp_h_errno = h_errno; }); h_errno = _tmp_h_errno; return retval; } int swoole_coroutine_open(const char *pathname, int flags, mode_t mode) { if (sw_unlikely(is_no_coro())) { return open(pathname, flags, mode); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = open(pathname, flags, mode); }); return ret; #else return Iouring::open(pathname, flags, mode); #endif } int swoole_coroutine_close(int sockfd) { if (sw_unlikely(is_no_coro())) { return close(sockfd); } auto socket = get_socket(sockfd); if (socket != nullptr) { if (socket->close()) { std::unique_lock _lock(socket_map_lock); socket_map.erase(sockfd); return 0; } return -1; } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = close(sockfd); }); return ret; #else return Iouring::close(sockfd); #endif } ssize_t swoole_coroutine_read(int sockfd, void *buf, size_t count) { if (sw_unlikely(is_no_coro())) { return read(sockfd, buf, count); } auto socket = get_socket(sockfd); if (socket != nullptr) { return socket->read(buf, count); } #ifdef SW_USE_ASYNC ssize_t ret = -1; NetSocket sock = {}; sock.fd = sockfd; sock.nonblock = 1; sock.read_timeout = -1; async([&]() { ret = sock.read_sync(buf, count); }); return ret; #else return Iouring::read(sockfd, buf, count); #endif } ssize_t swoole_coroutine_write(int sockfd, const void *buf, size_t count) { if (sw_unlikely(is_no_coro())) { return write(sockfd, buf, count); } auto socket = get_socket(sockfd); if (socket != nullptr) { return socket->write(buf, count); } #ifdef SW_USE_ASYNC ssize_t ret = -1; NetSocket sock = {}; sock.fd = sockfd; sock.nonblock = 1; sock.write_timeout = -1; async([&]() { ret = sock.write_sync(buf, count); }); return ret; #else return Iouring::write(sockfd, buf, count); #endif } int swoole_coroutine_fstat(int fd, struct stat *statbuf) { if (sw_unlikely(is_no_coro())) { return fstat(fd, statbuf); } #if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) int ret = -1; async([&]() { ret = fstat(fd, statbuf); }); return ret; #else return Iouring::fstat(fd, statbuf); #endif } int swoole_coroutine_stat(const char *path, struct stat *statbuf) { if (sw_unlikely(is_no_coro())) { return stat(path, statbuf); } #if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) int ret = -1; async([&]() { ret = stat(path, statbuf); }); return ret; #else return Iouring::stat(path, statbuf); #endif } int swoole_coroutine_lstat(const char *path, struct stat *statbuf) { if (sw_unlikely(is_no_coro())) { return lstat(path, statbuf); } #if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) int ret = -1; async([&]() { ret = lstat(path, statbuf); }); return ret; #else return Iouring::stat(path, statbuf); #endif } int swoole_coroutine_unlink(const char *pathname) { if (sw_unlikely(is_no_coro())) { return unlink(pathname); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = unlink(pathname); }); return ret; #else return Iouring::unlink(pathname); #endif } int swoole_coroutine_mkdir(const char *pathname, mode_t mode) { if (sw_unlikely(is_no_coro())) { return mkdir(pathname, mode); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = mkdir(pathname, mode); }); return ret; #else return Iouring::mkdir(pathname, mode); #endif } int swoole_coroutine_rmdir(const char *pathname) { if (sw_unlikely(is_no_coro())) { return rmdir(pathname); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = rmdir(pathname); }); return ret; #else return Iouring::rmdir(pathname); #endif } int swoole_coroutine_rename(const char *oldpath, const char *newpath) { if (sw_unlikely(is_no_coro())) { return rename(oldpath, newpath); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = rename(oldpath, newpath); }); return ret; #else return Iouring::rename(oldpath, newpath); #endif } int swoole_coroutine_fsync(int fd) { if (sw_unlikely(is_no_coro())) { return fsync(fd); } #ifdef SW_USE_ASYNC int ret = -1; async([&]() { ret = fsync(fd); }); return ret; #else return Iouring::fsync(fd); #endif } int swoole_coroutine_fdatasync(int fd) { if (sw_unlikely(is_no_coro())) { #ifdef HAVE_FDATASYNC return fdatasync(fd); #else return fsync(fd); #endif } #ifdef SW_USE_ASYNC int ret = -1; #ifdef HAVE_FDATASYNC async([&]() { ret = fdatasync(fd); }); #else async([&]() { ret = fsync(fd); }); #endif return ret; #else return Iouring::fdatasync(fd); #endif } int swoole_coroutine_ftruncate(int fd, off_t length) { if (sw_unlikely(is_no_coro())) { return ftruncate(fd, length); } #if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_FTRUNCATE) int ret = -1; async([&]() { ret = ftruncate(fd, length); }); return ret; #else return Iouring::ftruncate(fd, length); #endif } off_t swoole_coroutine_lseek(int fd, off_t offset, int whence) { if (sw_unlikely(is_no_coro())) { return lseek(fd, offset, whence); } off_t ret = -1; async([&]() { ret = lseek(fd, offset, whence); }); return ret; } ssize_t swoole_coroutine_readlink(const char *pathname, char *buf, size_t len) { if (sw_unlikely(is_no_coro())) { return readlink(pathname, buf, len); } ssize_t ret = -1; async([&]() { ret = readlink(pathname, buf, len); }); return ret; } int swoole_coroutine_statvfs(const char *path, struct statvfs *buf) { if (sw_unlikely(is_no_coro())) { return statvfs(path, buf); } int ret = -1; async([&]() { ret = statvfs(path, buf); }); return ret; } int swoole_coroutine_access(const char *pathname, int mode) { if (sw_unlikely(is_no_coro())) { return access(pathname, mode); } int ret = -1; async([&]() { ret = access(pathname, mode); }); return ret; } SW_EXTERN_C_END ================================================ FILE: src/coroutine/iouring.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author NathanFreeman | | Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_iouring.h" #include "swoole_coroutine_system.h" #ifdef SW_USE_IOURING #ifdef HAVE_IOURING_FUTEX #ifndef FUTEX2_SIZE_U32 #define FUTEX2_SIZE_U32 0x02 #endif #include #endif #if IO_URING_VERSION_MAJOR < 2 || (IO_URING_VERSION_MAJOR == 2 && IO_URING_VERSION_MINOR < 8) #error "The version of liburing required must be greater than or equal to 2.8." #endif #include #define DOUBLE_TO_TIMESPEC(seconds, ts) \ do { \ double __int_part; \ double __frac_part = modf((seconds), &__int_part); \ (ts)->tv_sec = (__kernel_time64_t) __int_part; \ (ts)->tv_nsec = (long long) (__frac_part * 1000000000.0); \ if ((ts)->tv_nsec >= 1000000000) { \ (ts)->tv_sec += 1; \ (ts)->tv_nsec = 0; \ } \ } while (0) #define TIMEOUT_EVENT (-1) using swoole::Coroutine; using swoole::coroutine::System; namespace swoole { //------------------------------------------------------------------------------- enum IouringEventFlag { SW_URING_TIMEOUT = 1U << 0, }; struct IouringEvent { Coroutine *coroutine; io_uring_sqe data; ssize_t result; IouringTimeout timeout; int flags; void set_timeout(double seconds) { if (seconds > 0) { flags |= SW_URING_TIMEOUT; DOUBLE_TO_TIMESPEC(seconds, &timeout); } } }; static void parse_kernel_version(const char *release, int *major, int *minor) { char copy[SW_STRUCT_MEMBER_SIZE(utsname, release)]; strcpy(copy, release); char *token = strtok(copy, ".-"); *major = token ? sw_atoi(token) : 0; token = strtok(nullptr, ".-"); *minor = token ? sw_atoi(token) : 0; } Iouring::Iouring(Reactor *_reactor) { reactor = _reactor; if (SwooleG.iouring_entries > 0) { uint32_t i = 6; while ((1U << i) < SwooleG.iouring_entries) { i++; } entries = 1 << i; } int ret = io_uring_queue_init(entries, &ring, (SwooleG.iouring_flag == IORING_SETUP_SQPOLL ? IORING_SETUP_SQPOLL : 0)); if (ret < 0) { errno = -ret; swoole_sys_error("Failed to initialize io_uring instance"); return; } if (SwooleG.iouring_workers > 0) { uint32_t workers[2] = {SwooleG.iouring_workers, SwooleG.iouring_workers}; ret = io_uring_register_iowq_max_workers(&ring, workers); if (ret < 0) { errno = -ret; swoole_sys_error("Failed to set the maximum of io_uring async workers"); return; } } int major, minor; parse_kernel_version(SwooleG.uname.release, &major, &minor); #ifdef HAVE_IOURING_FUTEX if (!(major >= 6 && minor >= 7)) { swoole_error("The Iouring::futex_wait()/Iouring::futex_wakeup() requires `6.7` or higher Linux kernel"); } #endif #ifdef HAVE_IOURING_FTRUNCATE if (!(major >= 6 && minor >= 9)) { swoole_error("The Iouring::ftruncate() requires `6.9` or higher Linux kernel"); } #endif ring_socket = make_socket(ring.ring_fd, SW_FD_IOURING); ring_socket->object = this; reactor->set_exit_condition(Reactor::EXIT_CONDITION_IOURING, [](Reactor *reactor, size_t &event_num) -> bool { if (SwooleTG.iouring && SwooleTG.iouring->get_task_num() == 0 && SwooleTG.iouring->is_empty_waiting_tasks()) { event_num--; } return true; }); reactor->add_destroy_callback([](void *data) { if (!SwooleTG.iouring) { return; } delete SwooleTG.iouring; SwooleTG.iouring = nullptr; }); reactor->set_end_callback(Reactor::PRIORITY_IOURING_SUBMIT, [](Reactor *reactor) { if (!SwooleTG.iouring) { return; } SwooleTG.iouring->submit(true); }); reactor->iouring_interrupt_handler = [this](Reactor *reactor) { wakeup(); }; if (reactor->add(ring_socket, SW_EVENT_READ) == SW_ERR) { swoole_sys_error("Failed to add io_uring ring fd to the event loop"); } } Iouring::~Iouring() { if (!ring_socket) { return; } if (!ring_socket->removed) { reactor->del(ring_socket); } ring_socket->move_fd(); ring_socket->free(); ring_socket = nullptr; io_uring_queue_exit(&ring); } bool Iouring::ready() const { return ring_socket && reactor->exists(ring_socket); } void Iouring::yield(IouringEvent *event) { ++task_num; Coroutine::CancelFunc cancel_fn = [event, this](Coroutine *) { return cancel(event); }; event->coroutine->yield(&cancel_fn); } void Iouring::resume(IouringEvent *event) { --task_num; event->coroutine->resume(); } bool Iouring::wakeup() { IouringEvent *waiting_task = nullptr; while (true) { auto count = io_uring_peek_batch_cqe(&ring, cqes, SW_IOURING_QUEUE_SIZE); if (count == 0) { break; } uint32_t ready_count = 0; SW_LOOP_N(count) { auto cqe = cqes[i]; auto event = static_cast(io_uring_cqe_get_data(cqe)); // The user data for the timeout request is -1, this event should be ignored. if (event == reinterpret_cast(TIMEOUT_EVENT)) { swoole_trace( "timeout, cqe.flags=%d, ceq.res=%d, error=`%s`", cqe->flags, cqe->res, strerror(-cqe->res)); continue; } event->result = cqe->res; ready_events[ready_count++] = event; swoole_trace("opcode=%s, cqe.flags=%d, ceq.res=%d, error=`%s`", get_opcode_name((io_uring_op) event->data.opcode), cqe->flags, cqe->res, strerror(cqe->res < 0 ? errno : 0)); } io_uring_cq_advance(&ring, count); /** * After the harvest is completed, the operating system's unprocessed task queue is reduced. * Before resuming the coroutine ready for IO, it should extract the queued SQE to the SQEs queue for processing * by the operating system. This can achieve a relatively good balance. If the submission is made after resuming * the coroutine, the kernel may become idle. */ SW_LOOP_N(ready_count) { if (get_sq_space_left() == 0 || is_empty_waiting_tasks()) { break; } waiting_task = waiting_tasks.front(); waiting_tasks.pop(); dispatch(waiting_task); } SW_LOOP_N(ready_count) { auto event = ready_events[i]; if (event->result < 0) { errno = -(event->result); /** * After a timeout, iouring will set errno to `ECANCELED`, but in the async implementation, * the errno after a timeout is `ETIMEDOUT`. * To maintain compatibility, numerical conversion is necessary. */ errno = errno == ECANCELED ? ETIMEDOUT : errno; event->result = -1; } resume(ready_events[i]); } } return true; } const char *Iouring::get_opcode_name(io_uring_op opcode) { switch (opcode) { case IORING_OP_SOCKET: return "SOCKET"; case IORING_OP_OPENAT: return "OPENAT"; case IORING_OP_ACCEPT: return "ACCEPT"; case IORING_OP_CONNECT: return "CONNECT"; case IORING_OP_BIND: return "BIND"; case IORING_OP_LISTEN: return "LISTEN"; case IORING_OP_SEND: return "SEND"; case IORING_OP_RECV: return "RECV"; case IORING_OP_READV: return "READV"; case IORING_OP_WRITEV: return "WRITEV"; case IORING_OP_SENDMSG: return "SENDMSG"; case IORING_OP_RECVMSG: return "RECVMSG"; case IORING_OP_SHUTDOWN: return "SHUTDOWN"; case IORING_OP_CLOSE: return "CLOSE"; case IORING_OP_STATX: return "STATX"; case IORING_OP_READ: return "READ"; case IORING_OP_WRITE: return "WRITE"; case IORING_OP_RENAMEAT: return "RENAMEAT"; case IORING_OP_MKDIRAT: return "MKDIRAT"; case IORING_OP_UNLINKAT: return "UNLINKAT"; case IORING_OP_FSYNC: return "FSYNC"; #ifdef HAVE_IOURING_FUTEX case IORING_OP_FUTEX_WAIT: return "FUTEX_WAIT"; case IORING_OP_FUTEX_WAKE: return "FUTEX_WAKE"; #endif #ifdef HAVE_IOURING_FTRUNCATE case IORING_OP_FTRUNCATE: return "FTRUNCATE"; #endif case IORING_OP_POLL_ADD: return "POLL_ADD"; case IORING_OP_POLL_REMOVE: return "POLL_REMOVE"; case IORING_OP_ASYNC_CANCEL: return "CANCEL"; default: return "unknown"; } } std::unordered_map Iouring::list_all_opcode() { std::unordered_map opcodes; for (int i = IORING_OP_NOP; i < IORING_OP_LAST; i++) { auto name = get_opcode_name((io_uring_op) i); if (strcmp(name, "unknown") == 0) { continue; } opcodes[name] = i; } return opcodes; } void Iouring::submit(bool immediately) { /** * Submit SQEs in batches to reduce the number of system calls. * If the number of SQEs is still less than SW_IOURING_SQE_BATCH_SIZE at the end of this event loop, * it will be automatically submitted at the end of the event loop. */ if (!immediately && swoole_event_is_running() && get_sq_used() < SW_IOURING_SQE_BATCH_SIZE) { return; } do { /** * If the value returned by io_uring_submit() is less than the current length of the SQE queue, * it indicates that only a part of the SQEs has been submitted and needs to be retried after the next event * loop. */ int ret = io_uring_submit(&ring); if (ret < 0) { /** * Returned EAGAIN error, ignoring this error, will retry on the next submit. */ if (-ret == EAGAIN) { return; } else if (-ret == EBUSY) { usleep(10000); continue; } else if (-ret == EINTR) { continue; } else { swoole_sys_error("io_uring_submit() failed"); return; } } break; } while (true); } Iouring *Iouring::get_instance() { if (sw_unlikely(!SwooleTG.iouring)) { if (!swoole_event_is_available()) { swoole_error("The event loop is unavailable, unable to create io_uring instance."); } SwooleTG.iouring = new Iouring(sw_reactor()); } return SwooleTG.iouring; } ssize_t Iouring::execute(IouringEvent *event) { auto iouring = get_instance(); iouring->dispatch(event); iouring->yield(event); return event->result; } void Iouring::dispatch(IouringEvent *event) { io_uring_sqe *sqe = alloc_sqe(); if (sw_unlikely(!sqe)) { waiting_tasks.push(event); return; } swoole_trace("opcode=%s, timeout[tv_sec=%ld, tv_nsec=%ld]", get_opcode_name((io_uring_op) event->data.opcode), event->timeout.tv_sec, event->timeout.tv_nsec); memcpy(sqe, &event->data, sizeof(event->data)); io_uring_sqe_set_data(sqe, (void *) event); if (event->flags & SW_URING_TIMEOUT) { auto timeout_sqe = alloc_sqe(); if (sw_likely(timeout_sqe)) { memset(timeout_sqe, 0, sizeof(*timeout_sqe)); io_uring_prep_link_timeout(timeout_sqe, reinterpret_cast<__kernel_timespec *>(&event->timeout), 0); io_uring_sqe_set_data(timeout_sqe, reinterpret_cast(TIMEOUT_EVENT)); sqe->flags |= IOSQE_IO_LINK; } else { swoole_warning("timeout setting failed, the iouring queue[%d] is full", ring.ring_fd); } } submit(false); } #define INIT_EVENT(op) \ IouringEvent event{}; \ event.coroutine = Coroutine::get_current_safe(); int Iouring::open(const char *pathname, int flags, mode_t mode) { INIT_EVENT(IORING_OP_OPENAT); io_uring_prep_open(&event.data, pathname, flags | O_CLOEXEC, mode); return static_cast(execute(&event)); } bool Iouring::cancel(IouringEvent *prev_event) { INIT_EVENT(IORING_OP_ASYNC_CANCEL); io_uring_prep_cancel(&event.data, (void *) prev_event, 0); return static_cast(execute(&event)) == 0; } int Iouring::socket(int domain, int type, int protocol, int flags) { INIT_EVENT(IORING_OP_SOCKET); io_uring_prep_socket(&event.data, domain, type, protocol, flags); return static_cast(execute(&event)); } int Iouring::connect(int fd, const struct sockaddr *addr, socklen_t len, double timeout) { INIT_EVENT(IORING_OP_CONNECT); io_uring_prep_connect(&event.data, fd, addr, len); event.set_timeout(timeout); return static_cast(execute(&event)); } int Iouring::bind(int fd, const struct sockaddr *addr, socklen_t len) { #if 1 return ::bind(fd, addr, len); #else INIT_EVENT(IORING_OP_BIND); io_uring_prep_bind(&event.data, fd, (struct sockaddr *) addr, len); return static_cast(execute(&event)); #endif } int Iouring::listen(int fd, int backlog) { #if 1 return ::listen(fd, backlog); #else io_uring_prep_listen(sqe, fd, backlog); #endif } int Iouring::sleep(double seconds) { IouringTimeout ts; DOUBLE_TO_TIMESPEC(seconds, &ts); return sleep(ts.tv_sec, ts.tv_nsec, 0); } int Iouring::sleep(int tv_sec, int tv_nsec, int flags) { struct __kernel_timespec ts { tv_sec, tv_nsec, }; INIT_EVENT(IORING_OP_TIMEOUT); io_uring_prep_timeout(&event.data, &ts, 0, flags); execute(&event); return errno == ETIME ? SW_OK : SW_ERR; } int Iouring::accept(int fd, struct sockaddr *addr, socklen_t *len, int flags, double timeout) { INIT_EVENT(IORING_OP_ACCEPT); io_uring_prep_accept(&event.data, fd, addr, len, flags); event.set_timeout(timeout); return static_cast(execute(&event)); } ssize_t Iouring::recv(int fd, void *buf, size_t len, int flags, double timeout) { INIT_EVENT(IORING_OP_RECV); io_uring_prep_recv(&event.data, fd, buf, len, flags); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::send(int fd, const void *buf, size_t len, int flags, double timeout) { INIT_EVENT(IORING_OP_SEND); io_uring_prep_send(&event.data, fd, buf, len, flags); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::recvmsg(int fd, struct msghdr *message, int flags, double timeout) { INIT_EVENT(IORING_OP_RECVMSG); io_uring_prep_recvmsg(&event.data, fd, message, flags); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::sendmsg(int fd, const struct msghdr *message, int flags, double timeout) { INIT_EVENT(IORING_OP_SENDMSG); io_uring_prep_sendmsg(&event.data, fd, message, flags); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::sendto( int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t len, double timeout) { INIT_EVENT(IORING_OP_SENDTO); io_uring_prep_sendto(&event.data, fd, buf, n, flags, addr, len); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::recvfrom(int fd, void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen, double timeout) { auto rv = recv(fd, _buf, _n, MSG_PEEK, timeout); if (rv > 0) { return ::recvfrom(fd, _buf, _n, MSG_DONTWAIT, _addr, _socklen); } else { return rv; } } ssize_t Iouring::readv(int fd, const struct iovec *iovec, int count, double timeout) { INIT_EVENT(IORING_OP_READV); io_uring_prep_readv(&event.data, fd, iovec, count, -1); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::writev(int fd, const struct iovec *iovec, int count, double timeout) { INIT_EVENT(IORING_OP_WRITEV); io_uring_prep_writev(&event.data, fd, iovec, count, -1); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::sendfile(int out_fd, int in_fd, off_t *offset, size_t size, double timeout) { if (size == 0) { return 0; } INIT_EVENT(IORING_OP_SPLICE); event.set_timeout(timeout); #ifndef MSG_SPLICE_PAGES int pipe_fds[2]; if (pipe(pipe_fds) < 0) { return -1; } fcntl(pipe_fds[1], F_SETPIPE_SZ, 1024 * 1024); int pipe_size = fcntl(pipe_fds[0], F_GETPIPE_SZ); if (pipe_size < 0) { pipe_size = 65536; } size_t total_sent = 0; off_t current_offset = offset ? *offset : 0; while (total_sent < size) { size_t remaining = size - total_sent; size_t to_send = std::min(remaining, (size_t) pipe_size); event.data = {}; io_uring_prep_splice( &event.data, in_fd, offset ? (current_offset + total_sent) : -1, pipe_fds[1], -1, to_send, 0); ssize_t ret1 = execute(&event); if (ret1 <= 0) { if (total_sent > 0) { break; } int saved_errno = errno; close(pipe_fds[0]); close(pipe_fds[1]); errno = saved_errno; return -1; } event.data = {}; io_uring_prep_splice(&event.data, pipe_fds[0], -1, out_fd, -1, ret1, 0); ssize_t ret2 = execute(&event); if (ret2 <= 0) { if (total_sent > 0) { break; } int saved_errno = errno; close(pipe_fds[0]); close(pipe_fds[1]); errno = saved_errno; return -1; } total_sent += ret2; if (ret2 < ret1) { break; } } close(pipe_fds[0]); close(pipe_fds[1]); if (total_sent > 0 && offset) { *offset += total_sent; } return total_sent; #else struct msghdr msg = {0}; struct iovec iov = {0}; iov.iov_base = NULL; iov.iov_len = size; msg.msg_iov = &iov; msg.msg_iovlen = 1; io_uring_prep_sendmsg(&event.data, out_fd, &msg, MSG_SPLICE_PAGES); event.data.len = size; event.data.splice_fd_in = in_fd; event.data.addr2 = offset ? (__u64) *offset : (__u64) -1; ssize_t ret = execute(&event); if (ret > 0 && offset) { *offset += ret; } return ret; #endif } int Iouring::shutdown(int fd, int how) { INIT_EVENT(IORING_OP_SHUTDOWN); io_uring_prep_shutdown(&event.data, fd, how); return static_cast(execute(&event)); } int Iouring::close(int fd) { INIT_EVENT(IORING_OP_CLOSE); io_uring_prep_close(&event.data, fd); return static_cast(execute(&event)); } ssize_t Iouring::read(int fd, void *buf, size_t size, double timeout) { INIT_EVENT(IORING_OP_READ); io_uring_prep_read(&event.data, fd, buf, size, -1); event.set_timeout(timeout); return execute(&event); } ssize_t Iouring::write(int fd, const void *buf, size_t size, double timeout) { INIT_EVENT(IORING_OP_WRITE); io_uring_prep_write(&event.data, fd, buf, size, -1); event.set_timeout(timeout); return execute(&event); } int Iouring::rename(const char *oldpath, const char *newpath) { INIT_EVENT(IORING_OP_RENAMEAT); io_uring_prep_rename(&event.data, oldpath, newpath); return static_cast(execute(&event)); } int Iouring::mkdir(const char *pathname, mode_t mode) { INIT_EVENT(IORING_OP_MKDIRAT); io_uring_prep_mkdir(&event.data, pathname, mode); return static_cast(execute(&event)); } int Iouring::unlink(const char *pathname) { INIT_EVENT(IORING_OP_UNLINK_FILE); io_uring_prep_unlink(&event.data, pathname, 0); return static_cast(execute(&event)); } int Iouring::rmdir(const char *pathname) { INIT_EVENT(IORING_OP_UNLINK_DIR); io_uring_prep_unlink(&event.data, pathname, AT_REMOVEDIR); return static_cast(execute(&event)); } int Iouring::fsync(int fd) { INIT_EVENT(IORING_OP_FSYNC); io_uring_prep_fsync(&event.data, fd, 0); return static_cast(execute(&event)); } int Iouring::fdatasync(int fd) { INIT_EVENT(IORING_OP_FDATASYNC); io_uring_prep_fsync(&event.data, fd, IORING_FSYNC_DATASYNC); return static_cast(execute(&event)); } #ifdef HAVE_IOURING_FTRUNCATE int Iouring::ftruncate(int fd, off_t length) { INIT_EVENT(IORING_OP_FTRUNCATE); io_uring_prep_ftruncate(&event.data, fd, length); return static_cast(execute(&event)); } #endif static inline int siginfo_to_status(const siginfo_t *info) { int status = 0; switch (info->si_code) { case CLD_EXITED: status = (info->si_status & 0xFF) << 8; break; case CLD_KILLED: status = info->si_status & 0x7F; break; case CLD_DUMPED: status = (info->si_status & 0x7F) | 0x80; break; case CLD_STOPPED: status = ((info->si_status & 0xFF) << 8) | 0x7F; break; case CLD_CONTINUED: status = 0xFFFF; break; } return status; } #ifdef HAVE_IOURING_STATX static void swoole_statx_to_stat(const struct statx *statxbuf, struct stat *statbuf) { statbuf->st_dev = (((unsigned int) statxbuf->stx_dev_major) << 8) | (unsigned int) statxbuf->stx_dev_minor; statbuf->st_mode = statxbuf->stx_mode; statbuf->st_nlink = statxbuf->stx_nlink; statbuf->st_uid = statxbuf->stx_uid; statbuf->st_gid = statxbuf->stx_gid; statbuf->st_rdev = (((unsigned int) statxbuf->stx_rdev_major) << 8) | (unsigned int) statxbuf->stx_rdev_minor; statbuf->st_ino = statxbuf->stx_ino; statbuf->st_size = statxbuf->stx_size; statbuf->st_blksize = statxbuf->stx_blksize; statbuf->st_blocks = statxbuf->stx_blocks; statbuf->st_atim.tv_sec = statxbuf->stx_atime.tv_sec; statbuf->st_atim.tv_nsec = statxbuf->stx_atime.tv_nsec; statbuf->st_mtim.tv_sec = statxbuf->stx_mtime.tv_sec; statbuf->st_mtim.tv_nsec = statxbuf->stx_mtime.tv_nsec; statbuf->st_ctim.tv_sec = statxbuf->stx_ctime.tv_sec; statbuf->st_ctim.tv_nsec = statxbuf->stx_ctime.tv_nsec; } int Iouring::fstat(int fd, struct stat *statbuf) { struct statx statxbuf; INIT_EVENT(IORING_OP_FSTAT); io_uring_prep_statx(&event.data, fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS | STATX_BTIME, &statxbuf); auto retval = execute(&event); if (retval == 0) { swoole_statx_to_stat(&statxbuf, statbuf); } return retval; } int Iouring::stat(const char *path, struct stat *statbuf) { struct statx statxbuf; INIT_EVENT(IORING_OP_FSTAT); io_uring_prep_statx( &event.data, AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, &statxbuf); auto retval = execute(&event); if (retval == 0) { swoole_statx_to_stat(&statxbuf, statbuf); } return retval; } #endif #ifdef HAVE_IOURING_FUTEX int Iouring::futex_wait(uint32_t *futex) { INIT_EVENT(IORING_OP_FUTEX_WAIT); io_uring_prep_futex_wait(&event.data, futex, 1, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0); return static_cast(execute(&event)); } int Iouring::futex_wakeup(uint32_t *futex) { INIT_EVENT(IORING_OP_FUTEX_WAKE); io_uring_prep_futex_wake(&event.data, futex, 1, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0); return static_cast(execute(&event)); } #endif pid_t Iouring::wait(int *stat_loc, double timeout) { return waitpid(-1, stat_loc, 0, timeout); } pid_t Iouring::waitpid(pid_t _pid, int *stat_loc, int options, double timeout) { if (options & WNOHANG) { return ::waitpid(_pid, stat_loc, options); } INIT_EVENT(IORING_OP_WAITID); siginfo_t info{}; idtype_t idtype = _pid > 0 ? P_PID : P_ALL; id_t id = _pid > 0 ? _pid : 0; options = options == 0 ? WEXITED : options; io_uring_prep_waitid(&event.data, idtype, id, &info, options, 0); event.set_timeout(timeout); int rc = static_cast(execute(&event)); if (rc != -1) { *stat_loc = siginfo_to_status(&info); return info.si_pid; } return rc; } int Iouring::poll(struct pollfd *fds, nfds_t nfds, int timeout) { if (nfds != 1) { errno = EINVAL; swoole_error_log( SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "incomplete poll() implementation, only supports one fd"); return -1; } if (timeout == 0) { return ::poll(fds, nfds, timeout); } INIT_EVENT(IORING_OP_POLL); io_uring_prep_poll_add(&event.data, fds[0].fd, fds[0].events); event.set_timeout((double) timeout / 1000); int rc = static_cast(execute(&event)); if (rc > 0) { fds[0].revents = rc; return 1; } return rc; } int Iouring::callback(Reactor *reactor, Event *event) { auto *iouring = static_cast(event->socket->object); return iouring->wakeup() ? SW_OK : SW_ERR; } } // namespace swoole #endif ================================================ FILE: src/coroutine/socket.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_util.h" #include "swoole_reactor.h" #include "swoole_base64.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" namespace swoole { namespace coroutine { TimeoutType Socket::timeout_type_list[4] = {SW_TIMEOUT_DNS, SW_TIMEOUT_CONNECT, SW_TIMEOUT_READ, SW_TIMEOUT_WRITE}; void Socket::timer_callback(Timer *timer, TimerNode *tnode) { auto *socket = static_cast(tnode->data); socket->set_err(ETIMEDOUT); if (sw_likely(tnode == socket->read_timer)) { socket->read_timer = nullptr; socket->read_co->resume(); } else if (tnode == socket->write_timer) { socket->write_timer = nullptr; socket->write_co->resume(); } else { abort(); } } int Socket::readable_event_callback(Reactor *reactor, Event *event) { auto *socket = static_cast(event->socket->object); socket->set_err(0); if (sw_unlikely(socket->want_event != SW_EVENT_NULL)) { if (socket->want_event == SW_EVENT_READ) { socket->write_co->resume(); } } else { if (socket->recv_barrier && (*socket->recv_barrier)() && !event->socket->event_hup) { return SW_OK; } socket->read_co->resume(); } return SW_OK; } int Socket::writable_event_callback(Reactor *reactor, Event *event) { auto *socket = static_cast(event->socket->object); socket->set_err(0); if (sw_unlikely(socket->want_event != SW_EVENT_NULL)) { if (socket->want_event == SW_EVENT_WRITE) { socket->read_co->resume(); } } else { if (socket->send_barrier && (*socket->send_barrier)() && !event->socket->event_hup) { return SW_OK; } socket->write_co->resume(); } return SW_OK; } int Socket::error_event_callback(Reactor *reactor, Event *event) { auto *socket = static_cast(event->socket->object); if (socket->write_co) { socket->set_err(0); socket->write_co->resume(); } // Notice: socket maybe deleted in write coroutine if (event->socket->object == socket && !event->socket->removed && socket->read_co) { socket->set_err(0); socket->read_co->resume(); } return SW_OK; } bool Socket::add_event(const EventType event) { bool ret = true; if (sw_likely(!(socket->events & event))) { if (socket->removed) { ret = swoole_event_add(socket, event) == SW_OK; } else { ret = swoole_event_set(socket, socket->events | event) == SW_OK; } } set_err(ret ? 0 : errno); return ret; } #ifdef SW_LOG_TRACE_OPEN static const char *get_trigger_event_name(Socket *socket, EventType added_event) { if (socket->is_closed()) { return "CLOSE"; } if (socket->errCode) { return socket->errCode == ETIMEDOUT ? "TIMEOUT" : "ERROR"; } return added_event == SW_EVENT_READ ? "READ" : "WRITE"; } static const char *get_wait_event_name(Socket *socket, EventType event) { if (socket->get_socket()->ssl_want_read) { return "SSL READ"; } else if (socket->get_socket()->ssl_want_write) { return "SSL WRITE"; } else { return event == SW_EVENT_READ ? "READ" : "WRITE"; } } #endif /** * If an exception occurs while waiting for an event, false is returned. * For example, when waiting for a read event, timeout, connection closed, are exceptions to the interrupt event. * And these exceptions will actively set the errCode, We don't need to set the exception's errCode ourselves. * We only need to set the errCode for the socket operation when wait_event returns true, * which means that the exception's error code priority is greater than the current event error priority. */ bool Socket::wait_event(const EventType event, const void **_buf, size_t _n) { EventType added_event = event; Coroutine *co = Coroutine::get_current_safe(); if (!co) { return false; } if (sw_unlikely(socket->close_wait)) { set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); return false; } if (socket->has_kernel_nobufs()) { if (sw_likely(event == SW_EVENT_READ)) { read_co = co; System::sleep(0.01); read_co = nullptr; } else { write_co = co; System::sleep(0.01); write_co = nullptr; } return !is_closed(); } // clear the last errCode set_err(0); if (sw_unlikely(socket->ssl && ((event == SW_EVENT_READ && socket->ssl_want_write) || (event == SW_EVENT_WRITE && socket->ssl_want_read)))) { if (sw_likely(socket->ssl_want_write && add_event(SW_EVENT_WRITE))) { want_event = SW_EVENT_WRITE; } else if (socket->ssl_want_read && add_event(SW_EVENT_READ)) { want_event = SW_EVENT_READ; } else { return false; } added_event = want_event; } else if (sw_unlikely(!add_event(event))) { return false; } swoole_trace_log(SW_TRACE_SOCKET, "socket#%d blongs to cid#%ld is waiting for %s event", sock_fd, co->get_cid(), get_wait_event_name(this, event)); Coroutine::CancelFunc cancel_fn = [this, event](Coroutine *co) { return cancel(event); }; if (sw_likely(event == SW_EVENT_READ)) { read_co = co; read_co->yield(&cancel_fn); read_co = nullptr; } else if (event == SW_EVENT_WRITE) { if (sw_unlikely(!zero_copy && _n > 0 && *_buf != get_write_buffer()->str)) { write_buffer->clear(); write_buffer->append(static_cast(*_buf), _n); *_buf = write_buffer->str; } write_co = co; write_co->yield(&cancel_fn); write_co = nullptr; } else { assert(0); return false; } // maybe read_co and write_co are all waiting for the same event when we use SSL if (sw_likely(want_event == SW_EVENT_NULL || !has_bound())) { Reactor *reactor = SwooleTG.reactor; if (sw_likely(added_event == SW_EVENT_READ)) { reactor->remove_read_event(socket); } else { reactor->remove_write_event(socket); } } want_event = SW_EVENT_NULL; swoole_trace_log(SW_TRACE_SOCKET, "socket#%d blongs to cid#%ld trigger %s event", sock_fd, co->get_cid(), get_trigger_event_name(this, added_event)); return !is_closed() && !errCode; } bool Socket::socks5_handshake() { Socks5Proxy *ctx = socks5_proxy.get(); const auto len = ctx->pack_negotiate_request(); if (send(ctx->buf, len) < 0) { return false; } auto send_fn = [this](const char *buf, size_t len) { return send(buf, len); }; char recv_buf[512]; ctx->state = SW_SOCKS5_STATE_HANDSHAKE; while (true) { const ssize_t n = recv(recv_buf, sizeof(recv_buf)); if (n > 0 && ctx->handshake(recv_buf, n, send_fn)) { if (ctx->state == SW_SOCKS5_STATE_READY) { return true; } continue; } break; } return false; } bool Socket::http_proxy_handshake() { auto target_host = get_http_proxy_host_name(); String *send_buffer = get_write_buffer(); ON_SCOPE_EXIT { send_buffer->clear(); }; size_t n = http_proxy->pack(send_buffer, target_host); send_buffer->length = n; swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy request: <str); if (send(send_buffer->str, n) != (ssize_t) n) { return false; } String *recv_buffer = get_read_buffer(); ON_SCOPE_EXIT { recv_buffer->clear(); }; ProtocolSwitch ps(this); open_eof_check = true; open_length_check = false; protocol.package_eof_len = sizeof("\r\n\r\n") - 1; memcpy(protocol.package_eof, SW_STRS("\r\n\r\n")); if (recv_packet() <= 0) { return false; } swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy response: <str); if (!http_proxy->handshake(recv_buffer)) { set_err(SW_ERROR_HTTP_PROXY_BAD_RESPONSE, std::string("wrong http_proxy response received, \n[Request]: ") + send_buffer->to_std_string() + "\n[Response]: " + send_buffer->to_std_string()); return false; } return true; } void Socket::init_sock_type(SocketType _type) { type = _type; network::Socket::get_domain_and_type(_type, &sock_domain, &sock_type); } bool Socket::init_sock() { socket = make_socket(type, SW_FD_CO_SOCKET, sock_domain, sock_type, sock_protocol, SW_SOCK_NONBLOCK | SW_SOCK_CLOEXEC); if (socket == nullptr) { return false; } sock_fd = socket->fd; socket->object = this; socket->info.type = type; return true; } bool Socket::reinit_sock(SocketType _type) { int _sock_domain; int _sock_type; network::Socket::get_domain_and_type(_type, &_sock_domain, &_sock_type); auto new_sock = make_socket( _type, SW_FD_CO_SOCKET, _sock_domain, _sock_type, sock_protocol, SW_SOCK_NONBLOCK | SW_SOCK_CLOEXEC); if (socket == nullptr) { return false; } socket->free(); socket = new_sock; socket->object = this; socket->info.type = type; sock_domain = _sock_domain; sock_type = _sock_type; type = _type; sock_fd = socket->fd; return true; } bool Socket::init_reactor_socket(int _fd) { socket = make_socket(_fd, SW_FD_CO_SOCKET); sock_fd = _fd; socket->object = this; socket->socket_type = type; socket->nonblock = 1; socket->cloexec = 1; socket->info.type = type; return true; } Socket::Socket(int _domain, int _type, int _protocol) : sock_domain(_domain), sock_type(_type), sock_protocol(_protocol) { type = network::Socket::convert_to_type(_domain, _type); if (sw_unlikely(!init_sock())) { return; } init_options(); } Socket::Socket(SocketType _type) { init_sock_type(_type); if (sw_unlikely(!init_sock())) { return; } init_options(); } Socket::Socket(int _fd, SocketType _type) { init_sock_type(_type); if (sw_unlikely(!init_reactor_socket(_fd))) { return; } if (_type == SW_SOCK_RAW) { return; } socket->set_nonblock(); init_options(); } Socket::Socket(int _fd, int _domain, int _type, int _protocol) : sock_domain(_domain), sock_type(_type), sock_protocol(_protocol) { type = network::Socket::convert_to_type(_domain, _type); if (sw_unlikely(!init_reactor_socket(_fd))) { return; } socket->set_nonblock(); init_options(); } /** * Only used as accept member method */ Socket::Socket(network::Socket *sock, const Socket *server_sock) { type = server_sock->type; sock_domain = server_sock->sock_domain; sock_type = server_sock->sock_type; sock_protocol = server_sock->sock_protocol; sock_fd = sock->fd; socket = sock; socket->object = this; socket->socket_type = type; socket->fd_type = SW_FD_CO_SOCKET; init_options(); /* inherits server socket options */ socket->dns_timeout = server_sock->get_socket()->dns_timeout; socket->connect_timeout = server_sock->get_socket()->connect_timeout; socket->read_timeout = server_sock->get_socket()->read_timeout; socket->write_timeout = server_sock->get_socket()->write_timeout; open_length_check = server_sock->open_length_check; open_eof_check = server_sock->open_eof_check; http2 = server_sock->http2; protocol = server_sock->protocol; connected = true; ssl_context = server_sock->ssl_context; ssl_is_server = server_sock->ssl_is_server; if (server_sock->ssl_is_enable() && !ssl_create(server_sock->get_ssl_context())) { close(); } } bool Socket::getsockname() const { return socket->get_name() == SW_OK; } bool Socket::getpeername(network::Address *sa) { sa->len = sizeof(sa->addr); if (::getpeername(sock_fd, reinterpret_cast(&sa->addr), &sa->len) != 0) { set_err(errno); return false; } sa->type = type; return true; } double Socket::get_timeout(TimeoutType _type) const { return socket->get_timeout(_type); } String *Socket::get_read_buffer() { if (sw_unlikely(!read_buffer)) { read_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); } return read_buffer; } String *Socket::get_write_buffer() { if (sw_unlikely(!write_buffer)) { write_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); } return write_buffer; } String *Socket::pop_read_buffer() { if (sw_unlikely(!read_buffer)) { return nullptr; } auto tmp = read_buffer; read_buffer = nullptr; return tmp; } String *Socket::pop_write_buffer() { if (sw_unlikely(!write_buffer)) { return nullptr; } auto tmp = write_buffer; write_buffer = nullptr; return tmp; } void Socket::set_timeout(double timeout, int _type) const { socket->set_timeout(timeout, _type); } const char *Socket::get_event_str(const EventType event) const { if (event == SW_EVENT_READ) { return "reading"; } else if (event == SW_EVENT_WRITE) { return "writing"; } else { return read_co && write_co ? "reading or writing" : (read_co ? "reading" : "writing"); } } bool Socket::set_option(int level, int optname, int optval) const { return set_option(level, optname, &optval, sizeof(optval)); } bool Socket::get_option(int level, int optname, int *optval) const { socklen_t optval_size = sizeof(*optval); return get_option(level, optname, optval, &optval_size); } bool Socket::set_option(int level, int optname, const void *optval, socklen_t optlen) const { if (socket->set_option(level, optname, optval, optlen) < 0) { swoole_sys_warning("setsockopt(%d, %d, %d, %u) failed", sock_fd, level, optname, optlen); return false; } return true; } bool Socket::get_option(int level, int optname, void *optval, socklen_t *optlen) const { if (socket->get_option(level, optname, optval, optlen) < 0) { swoole_sys_warning("getsockopt(%d, %d, %d) failed", sock_fd, level, optname); return false; } return true; } void Socket::set_socks5_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { socks5_proxy.reset(Socks5Proxy::create(type, host, port, user, pwd)); } void Socket::set_http_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { http_proxy.reset(HttpProxy::create(host, port, user, pwd)); } bool Socket::connect(const sockaddr *addr, socklen_t addrlen) { if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } int retval; do { retval = ::connect(sock_fd, addr, addrlen); } while (retval < 0 && errno == EINTR); if (retval < 0) { if (errno != EINPROGRESS) { set_err(errno); return false; } else { TimerController timer(&write_timer, socket->connect_timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_WRITE)) { if (is_closed()) { set_err(ECONNABORTED); } return false; } else { if (socket->get_option(SOL_SOCKET, SO_ERROR, &errCode) < 0 || errCode != 0) { set_err(errCode); return false; } } } } connected = true; socket->get_name(); set_err(0); return true; } bool Socket::connect(const std::string &_host, int _port, int flags) { if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } if (ssl_context && (socks5_proxy || http_proxy)) { /* If the proxy is enabled, the host will be replaced with the proxy ip, * so we have to handle the host first, * if the host is not an ip, assign it to ssl_host_name */ if (!network::Address::verify_ip(sock_domain, _host)) { ssl_host_name = _host; } } if (socks5_proxy) { socks5_proxy->target_host = _host; socks5_proxy->target_port = _port; connect_host = socks5_proxy->host; connect_port = socks5_proxy->port; } else if (http_proxy) { http_proxy->target_host = _host; http_proxy->target_port = _port; connect_host = http_proxy->host; connect_port = http_proxy->port; } else { connect_host = _host; connect_port = _port; } if (socks5_proxy || http_proxy) { if (socket->is_inet6()) { if (network::Address::verify_ip(AF_INET, connect_host) && !reinit_sock(SW_SOCK_TCP)) { return false; } } else if (socket->is_inet4()) { if (network::Address::verify_ip(AF_INET6, connect_host) && !reinit_sock(SW_SOCK_TCP6)) { return false; } } } NameResolver::Context *ctx = resolve_context_; NameResolver::Context _ctx{}; if (ctx == nullptr) { ctx = &_ctx; } ctx->timeout = socket->dns_timeout; std::once_flag oc; auto name_resolve_fn = [ctx, &oc, this](int _type) -> bool { ctx->type = _type; std::call_once(oc, [this]() { if (ssl_context && !(socks5_proxy || http_proxy)) { ssl_host_name = connect_host; } }); /* locked like wait_event */ read_co = write_co = Coroutine::get_current_safe(); ON_SCOPE_EXIT { read_co = write_co = nullptr; }; std::string addr = swoole_name_resolver_lookup(connect_host, ctx); if (addr.empty()) { set_err(swoole_get_last_error()); return false; } if (ctx->with_port) { char delimiter = _type == AF_INET6 ? '@' : ':'; auto port_pos = addr.find_first_of(delimiter); if (port_pos != std::string::npos) { connect_port = std::stoi(addr.substr(port_pos + 1)); connect_host = addr.substr(0, port_pos); return true; } } connect_host = addr; return true; }; network::Address server_addr; for (int i = 0; i < 2; i++) { if (!server_addr.assign(type, connect_host, connect_port, false)) { if (swoole_get_last_error() != SW_ERROR_BAD_HOST_ADDR) { set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); return false; } if (!name_resolve_fn(sock_domain)) { set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); return false; } continue; } break; } if (!connect(&server_addr.addr.ss, server_addr.len)) { return false; } // socks5 proxy if (socks5_proxy && !socks5_handshake()) { set_err(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); return false; } // http proxy if (http_proxy && !http_proxy->dont_handshake && !http_proxy_handshake()) { set_err(SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); return false; } ssl_is_server = false; if (ssl_context && !ssl_handshake()) { if (swoole_get_last_error() == 0) { set_err(SW_ERROR_SSL_HANDSHAKE_FAILED); } return false; } return true; } bool Socket::check_liveness() { if (is_closed()) { set_err(EBADF); return false; } if (!socket->check_liveness()) { set_err(errno ? errno : ECONNRESET); return false; } set_err(0); return true; } ssize_t Socket::peek(void *_buf, size_t _n) { ssize_t retval = socket->peek(_buf, _n, 0); check_return_value(retval); return retval; } bool Socket::poll(EventType _type, double timeout) { if (sw_unlikely(!is_available(_type))) { return false; } TimerNode **timer_pp = _type == SW_EVENT_READ ? &read_timer : &write_timer; if (timeout == 0) { timeout = _type == SW_EVENT_READ ? socket->read_timeout : socket->write_timeout; } TimerController timer(timer_pp, timeout, this, timer_callback); if (timer.start() && wait_event(_type)) { return true; } else { return false; } } ssize_t Socket::recv(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->recv(_buf, _n, 0); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } ssize_t Socket::send(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = socket->send(_buf, _n, 0); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE, &_buf, _n)); check_return_value(retval); return retval; } ssize_t Socket::read(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->read(_buf, _n); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } ssize_t Socket::recv_line(void *_buf, size_t maxlen) { size_t n = 0; ssize_t m = 0; auto t = static_cast(_buf); *t = '\0'; while (*t != '\n' && *t != '\r' && n < maxlen) { if (m > 0) { t++; n++; } if (n < maxlen) { m = recv_with_buffer((void *) t, 1); if (m < 0) { return -1; } else if (m == 0) { return n > 0 ? n : 0; } } } if (n < maxlen) { n++; } return n; } ssize_t Socket::recv_with_buffer(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } String *buffer = get_read_buffer(); size_t buffer_bytes = buffer->length - buffer->offset; if (_n <= buffer_bytes) { memcpy(_buf, buffer->str + buffer->offset, _n); buffer->offset += _n; return _n; } if (buffer_bytes > 0) { memcpy(_buf, buffer->str + buffer->offset, buffer_bytes); buffer->offset += buffer_bytes; } if ((size_t) buffer->offset >= buffer->size / 2) { buffer->reduce(buffer->offset); } ssize_t retval = recv(buffer->str + buffer->length, buffer->size - buffer->length); if (retval <= 0) { return buffer_bytes > 0 ? buffer_bytes : retval; } buffer->length += retval; size_t copy_bytes = SW_MIN(_n - buffer_bytes, buffer->length - buffer->offset); memcpy((char *) _buf + buffer_bytes, buffer->str + buffer->offset, copy_bytes); buffer->offset += copy_bytes; return buffer_bytes + copy_bytes; } ssize_t Socket::write(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = socket->write((void *) _buf, _n); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE, &_buf, _n)); check_return_value(retval); return retval; } ssize_t Socket::readv(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->readv(io_vector); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } ssize_t Socket::readv_all(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t total_bytes = 0; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); ssize_t retval = socket->readv(io_vector); swoole_trace_log(SW_TRACE_SOCKET, "readv %ld bytes, errno=%d", retval, errno); if (retval < 0 && socket->catch_read_error(errno) != SW_WAIT) { set_err(errno); return retval; } if (retval == 0) { return retval; } total_bytes += retval > 0 ? retval : 0; if (io_vector->get_remain_count() == 0) { // iov should not be modified, prevent valgrind from checking for invalid read return retval; } EventBarrier barrier = [&io_vector, &total_bytes, &retval, this]() -> bool { do { retval = socket->readv(io_vector); if (retval <= 0) { break; } total_bytes += retval; } while (retval > 0 && io_vector->get_remain_count() > 0); return retval < 0 && socket->catch_read_error(errno) == SW_WAIT; }; recv_barrier = &barrier; if (timer.start() && wait_event(SW_EVENT_READ)) { check_return_value(retval); } recv_barrier = nullptr; return total_bytes; } ssize_t Socket::writev(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = socket->writev(io_vector); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE)); check_return_value(retval); return retval; } ssize_t Socket::writev_all(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t total_bytes = 0; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); ssize_t retval = socket->writev(io_vector); swoole_trace_log(SW_TRACE_SOCKET, "writev %ld bytes, errno=%d", retval, errno); if (retval < 0 && socket->catch_write_error(errno) != SW_WAIT) { set_err(errno); return retval; } if (retval == 0) { return retval; } total_bytes += retval > 0 ? retval : 0; if (io_vector->get_remain_count() == 0) { // iov should not be modified, prevent valgrind from checking for invalid read return retval; } EventBarrier barrier = [&io_vector, &total_bytes, &retval, this]() -> bool { do { retval = socket->writev(io_vector); if (retval <= 0) { break; } total_bytes += retval; } while (retval > 0 && io_vector->get_remain_count() > 0); return retval < 0 && socket->catch_write_error(errno) == SW_WAIT; }; send_barrier = &barrier; if (timer.start() && wait_event(SW_EVENT_WRITE)) { check_return_value(retval); } send_barrier = nullptr; return total_bytes; } ssize_t Socket::recv_all(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval = 0; size_t total_bytes = 0; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); retval = socket->recv(_buf, _n, 0); if (retval == 0 || retval == (ssize_t) _n) { return retval; } if (retval < 0 && socket->catch_read_error(errno) != SW_WAIT) { set_err(errno); return retval; } total_bytes = retval > 0 ? retval : 0; retval = -1; EventBarrier barrier = [&_n, &total_bytes, &retval, &_buf, this]() -> bool { retval = socket->recv((char *) _buf + total_bytes, _n - total_bytes, 0); return (retval < 0 && socket->catch_read_error(errno) == SW_WAIT) || (retval > 0 && (total_bytes += retval) < _n); }; recv_barrier = &barrier; if (timer.start() && wait_event(SW_EVENT_READ)) { check_return_value(retval); } recv_barrier = nullptr; return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } ssize_t Socket::send_all(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval = 0; size_t total_bytes = 0; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); retval = socket->send(_buf, _n, 0); if (retval == 0 || retval == (ssize_t) _n) { return retval; } if (retval < 0 && socket->catch_write_error(errno) != SW_WAIT) { set_err(errno); return retval; } total_bytes = retval > 0 ? retval : 0; retval = -1; EventBarrier barrier = [&_n, &total_bytes, &retval, &_buf, this]() -> bool { retval = socket->send((char *) _buf + total_bytes, _n - total_bytes, 0); return (retval < 0 && socket->catch_write_error(errno) == SW_WAIT) || (retval > 0 && (total_bytes += retval) < _n); }; send_barrier = &barrier; if (timer.start() && wait_event(SW_EVENT_WRITE)) { check_return_value(retval); } send_barrier = nullptr; return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } ssize_t Socket::recvmsg(struct msghdr *msg, int flags) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = ::recvmsg(sock_fd, msg, flags); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } /** * Notice: you must use non-global buffer here (or else it may be changed after yield) */ ssize_t Socket::sendmsg(const struct msghdr *msg, int flags) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = ::sendmsg(sock_fd, msg, flags); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE)); check_return_value(retval); return retval; } bool Socket::bind(const sockaddr *sa, socklen_t len) { if (socket->bind(sa, len) < 0) { set_err(); return false; } return true; } bool Socket::bind(const std::string &address, const int port) { if (sw_unlikely(!is_available(SW_EVENT_NULL))) { return false; } swoole_clear_last_error(); if (socket->set_reuse_addr() < 0) { swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", get_fd()); } if (socket->bind(address, port) < 0) { set_err(); return false; } if (socket->get_name() < 0) { set_err(); return false; } return true; } bool Socket::listen(int backlog) { if (sw_unlikely(!is_available(SW_EVENT_NULL))) { return false; } this->backlog = backlog <= 0 ? SW_BACKLOG : backlog; if (socket->listen(this->backlog) < 0) { set_err(errno); return false; } ssl_is_server = true; return true; } Socket *Socket::accept(double timeout) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return nullptr; } if (ssl_is_enable() && sw_unlikely(ssl_context->context == nullptr) && !ssl_context_create()) { return nullptr; } network::Socket *conn = socket->accept(); if (conn == nullptr && errno == EAGAIN) { TimerController timer(&read_timer, timeout == 0 ? socket->read_timeout : timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_READ)) { return nullptr; } conn = socket->accept(); } if (conn == nullptr) { set_err(errno); return nullptr; } auto *client_sock = new Socket(conn, this); if (sw_unlikely(client_sock->get_fd() < 0)) { swoole_sys_warning("new Socket() failed"); set_err(errno); delete client_sock; return nullptr; } return client_sock; } bool Socket::ssl_context_create() { if (socket->is_dgram()) { #ifdef SW_SUPPORT_DTLS socket->dtls = 1; ssl_context->protocols = SW_SSL_DTLS; socket->chunk_size = SW_SSL_BUFFER_SIZE; #else swoole_warning("DTLS support require openssl-1.1 or later"); return false; #endif } ssl_context->http_v2 = http2; if (!ssl_context->create()) { set_err(); return false; } socket->ssl_send_ = 1; return true; } bool Socket::ssl_create(SSLContext *ssl_context) { if (socket->ssl) { return true; } if (socket->ssl_create(ssl_context, 0) < 0) { set_err(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } #ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER SSL_set_mode(socket->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (!ssl_context->tls_host_name.empty()) { SSL_set_tlsext_host_name(socket->ssl, ssl_context->tls_host_name.c_str()); } else if (!ssl_context->disable_tls_host_name && !ssl_host_name.empty()) { SSL_set_tlsext_host_name(socket->ssl, ssl_host_name.c_str()); } #endif return true; } bool Socket::ssl_handshake() { if (ssl_handshaked) { set_err(SW_ERROR_WRONG_OPERATION); return false; } if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } /** * If the ssl_context is empty, it indicates that this socket was not a connection * returned by a server socket accept, and a new ssl_context needs to be created. */ if (ssl_context->context == nullptr && !ssl_context_create()) { return false; } if (!ssl_create(get_ssl_context())) { return false; } /** * The server will use ssl_accept to complete the SSL handshake, * while the client will use ssl_connect. */ if (!ssl_is_server) { while (true) { if (socket->ssl_connect() < 0) { set_err(); return false; } if (socket->ssl_state == SW_SSL_STATE_WAIT_STREAM) { TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_READ)) { return false; } } else if (socket->ssl_state == SW_SSL_STATE_READY) { break; } } } else { ReturnCode retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->ssl_accept(); } while (retval == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); if (retval != SW_READY) { set_err(SW_ERROR_SSL_BAD_CLIENT); return false; } } if (ssl_context->verify_peer) { if (!ssl_verify(ssl_context->allow_self_signed)) { return false; } } ssl_handshaked = true; return true; } bool Socket::ssl_verify(bool allow_self_signed) { if (!socket->ssl_verify(allow_self_signed)) { set_err(SW_ERROR_SSL_VERIFY_FAILED); return false; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (!ssl_context->tls_host_name.empty() && !socket->ssl_check_host(ssl_context->tls_host_name.c_str())) { set_err(SW_ERROR_SSL_VERIFY_FAILED); return false; } #endif return true; } std::string Socket::ssl_get_peer_cert() { if (!socket->ssl_get_peer_certificate(sw_tg_buffer())) { set_err(SW_ERROR_SSL_EMPTY_PEER_CERTIFICATE); return ""; } else { return sw_tg_buffer()->to_std_string(); } } bool Socket::sendfile(const char *filename, off_t offset, size_t length) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return false; } File file(filename, O_RDONLY); if (!file.ready()) { set_err(errno, std_string::format("open(%s) failed, %s", filename, strerror(errno))); return false; } if (length == 0) { FileStatus file_stat; if (!file.stat(&file_stat)) { set_err(errno, std_string::format("fstat(%s) failed, %s", filename, strerror(errno))); return false; } length = file_stat.st_size; } else { // total length of the file length = offset + length; } TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); while ((size_t) offset < length) { ssize_t sent_bytes = (length - offset > SW_SENDFILE_CHUNK_SIZE) ? SW_SENDFILE_CHUNK_SIZE : length - offset; ssize_t n = socket->sendfile(file, &offset, sent_bytes); if (n > 0) { continue; } else if (n == 0) { set_err(SW_ERROR_SYSTEM_CALL_FAIL, "sendfile return zero"); return false; } else if (errno != EAGAIN) { set_err(errno, std_string::format("sendfile(%d, %s) failed, %s", sock_fd, filename, strerror(errno))); return false; } if (!timer.start() || !wait_event(SW_EVENT_WRITE)) { return false; } } return true; } ssize_t Socket::sendto(const std::string &host, int port, const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval = 0; network::Address addr; if (!socket->is_dgram()) { set_err(EPROTONOSUPPORT); return -1; } auto ip_addr = host; SW_LOOP_N(2) { if (!addr.assign(type, ip_addr, port, false)) { if (swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { ip_addr = System::gethostbyname(host, sock_domain, socket->dns_timeout); if (!ip_addr.empty()) { continue; } } set_err(); return -1; } break; } TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = socket->sendto(addr, _buf, _n, 0); swoole_trace_log(SW_TRACE_SOCKET, "sendto %ld/%ld bytes, errno=%d", retval, _n, errno); } while (retval < 0 && (errno == EINTR || (socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE, &_buf, _n)))); check_return_value(retval); return retval; } ssize_t Socket::recvfrom(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } socket->info.len = sizeof(socket->info.addr); return recvfrom(_buf, _n, reinterpret_cast(&socket->info.addr), &socket->info.len); } ssize_t Socket::recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = ::recvfrom(sock_fd, _buf, _n, 0, _addr, _socklen); swoole_trace_log(SW_TRACE_SOCKET, "recvfrom %ld/%ld bytes, errno=%d", retval, _n, errno); } while (retval < 0 && ((errno == EINTR) || (socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)))); check_return_value(retval); return retval; } ssize_t Socket::recv_packet_with_length_protocol() { ssize_t packet_len = SW_BUFFER_SIZE_STD; ssize_t retval; PacketLength pl; uint32_t header_len = protocol.package_length_offset + protocol.package_length_size; if (read_buffer->length > 0) { if (read_buffer->length >= header_len || (protocol.package_length_size == 0 && protocol.package_length_type == '\0') // custom package_length_func ) { goto _get_length; } else { goto _recv_header; } } _recv_header: retval = recv(read_buffer->str + read_buffer->length, header_len - read_buffer->length); if (retval <= 0) { return retval; } else { read_buffer->length += retval; } _get_length: pl.header_len = 0; pl.buf = read_buffer->str; pl.buf_size = (uint32_t) read_buffer->length; packet_len = protocol.get_package_length(&protocol, socket, &pl); swoole_trace_log(SW_TRACE_SOCKET, "packet_len=%ld, length=%ld", packet_len, read_buffer->length); if (packet_len < 0) { set_err(SW_ERROR_PACKAGE_LENGTH_NOT_FOUND, "get package length failed"); return 0; } else if (packet_len == 0) { if (pl.header_len != 0) { header_len = pl.header_len; } goto _recv_header; } else if (packet_len > protocol.package_max_length) { read_buffer->clear(); swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "packet length is too big, remote_addr=%s:%d, length=%zu", socket->get_addr(), socket->get_port(), packet_len); set_err(SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, sw_error); return -1; } read_buffer->offset = packet_len; if ((size_t) packet_len <= read_buffer->length) { return packet_len; } if ((size_t) packet_len > read_buffer->size) { read_buffer->extend(packet_len); } retval = recv_all(read_buffer->str + read_buffer->length, packet_len - read_buffer->length); if (retval > 0) { read_buffer->length += retval; if (read_buffer->length != (size_t) packet_len) { retval = 0; } else { return packet_len; } } return retval; } ssize_t Socket::recv_packet_with_eof_protocol() { ssize_t retval, eof = -1; char *buf = nullptr; size_t l_buf = 0; if (read_buffer->length > 0) { goto _find_eof; } while (true) { buf = read_buffer->str + read_buffer->length; l_buf = read_buffer->size - read_buffer->length; if (l_buf > SW_BUFFER_SIZE_BIG) { l_buf = SW_BUFFER_SIZE_BIG; } retval = recv(buf, l_buf); if (retval <= 0) { read_buffer->clear(); return retval; } read_buffer->length += retval; if (read_buffer->length < protocol.package_eof_len) { continue; } _find_eof: eof = swoole_strnpos(read_buffer->str, read_buffer->length, protocol.package_eof, protocol.package_eof_len); if (eof >= 0) { return (read_buffer->offset = eof + protocol.package_eof_len); } if (read_buffer->length == protocol.package_max_length) { read_buffer->clear(); set_err(SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "no package eof, package_max_length exceeded"); return -1; } if (read_buffer->length == read_buffer->size && read_buffer->size < protocol.package_max_length) { size_t new_size = read_buffer->size * 2; if (new_size > protocol.package_max_length) { new_size = protocol.package_max_length; } read_buffer->extend(new_size); } } assert(0); return -1; } /** * Recv packet with protocol * Returns the length of the packet, [return value == read_buffer->offset] * ---------------------------------------Usage--------------------------------------------- * ssize_t l = sock.recv_packet(); * String *pkt = sock.get_read_buffer(); * a) memcpy(result_buf, pkt->str, l); //copy data to new buffer * b) result_buf = sock.pop_packet(); //pop packet data, create a new buffer memory * ---------------------------------------read_buffer--------------------------------------- * [read_buffer->length > read_buffer->offset] : may be unprocessed data in the buffer * [read_buffer->length == read_buffer->offset] : no data in the buffer */ ssize_t Socket::recv_packet(double timeout) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } TimerController timer(&read_timer, timeout == 0 ? socket->read_timeout : timeout, this, timer_callback); if (sw_unlikely(!timer.start())) { return 0; } get_read_buffer(); // unprocessed data if (read_buffer->offset > 0) { read_buffer->reduce(read_buffer->offset); } ssize_t recv_bytes; if (open_length_check) { recv_bytes = recv_packet_with_length_protocol(); } else if (open_eof_check) { recv_bytes = recv_packet_with_eof_protocol(); } else { recv_bytes = recv(read_buffer->str, read_buffer->size); if (recv_bytes > 0) { read_buffer->length = read_buffer->offset = recv_bytes; } } if (recv_bytes <= 0) { read_buffer->clear(); } return recv_bytes; } bool Socket::shutdown(int _how) { set_err(0); if (!is_connected() || (_how == SHUT_RD && shutdown_read) || (_how == SHUT_WR && shutdown_write)) { errno = ENOTCONN; } else { if (socket->ssl) { socket->ssl_shutdown(); } if (::shutdown(sock_fd, _how) == 0 || errno == ENOTCONN) { if (errno == ENOTCONN) { // connection reset by server side _how = SHUT_RDWR; } switch (_how) { case SHUT_RD: shutdown_read = true; break; case SHUT_WR: shutdown_write = true; break; default: shutdown_read = shutdown_write = true; break; } if (shutdown_read && shutdown_write) { connected = false; } return true; } } set_err(errno); return false; } void Socket::ssl_close() const { socket->ssl_close(); } bool Socket::cancel(const EventType event) { if (!has_bound(event)) { return false; } if (event == SW_EVENT_READ) { set_err(ECANCELED); read_co->resume(); return true; } else if (event == SW_EVENT_WRITE) { set_err(ECANCELED); write_co->resume(); return true; } else { set_err(EINVAL); return false; } } /** * @return bool * If true is returned, the related resources of this socket can be released * If false is returned, it means that other coroutines are still referencing this socket, * and need to wait for the coroutine bound to readable or writable event to execute close, * and release when all references are 0 */ bool Socket::close() { if (is_closed()) { set_err(EBADF); return false; } if (connected) { shutdown(); } if (sw_unlikely(has_bound())) { socket->close_wait = 1; cancel(SW_EVENT_WRITE); cancel(SW_EVENT_READ); set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); return false; } else { sock_fd = SW_BAD_SOCKET; if (dtor_ != nullptr) { auto dtor = dtor_; dtor_ = nullptr; dtor(this); } return true; } } /** * Warn: * the destructor should only be called in following two cases: * 1. construct failed * 2. called close() and it returns true * 3. called close() and it returns false, but it will not be accessed anywhere else */ Socket::~Socket() { #ifdef SW_DEBUG if (SwooleG.running) { SW_ASSERT(!has_bound() && socket->removed); } #endif if (dtor_ != nullptr) { dtor_(this); } delete read_buffer; delete write_buffer; if (socket == nullptr) { return; } /* {{{ release socket resources */ if (socket->ssl) { ssl_close(); } socket->free(); } bool Socket::TimerController::start() { if (timeout != 0 && !*timer_pp) { enabled = true; if (timeout > 0) { *timer_pp = swoole_timer_add(timeout, false, callback, socket_); return *timer_pp != nullptr; } *timer_pp = reinterpret_cast(-1); } return true; } Socket::TimerController::~TimerController() { if (enabled && *timer_pp) { if (*timer_pp != reinterpret_cast(-1)) { swoole_timer_del(*timer_pp); } *timer_pp = nullptr; } } Socket::TimeoutSetter::TimeoutSetter(Socket *socket, double _timeout, TimeoutType _type) : socket_(socket), timeout(_timeout), type(_type) { if (_timeout == 0) { return; } for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { if (_type & timeout_type_list[i]) { original_timeout[i] = socket->get_timeout(timeout_type_list[i]); if (_timeout != original_timeout[i]) { socket->set_timeout(_timeout, timeout_type_list[i]); } } } } Socket::TimeoutSetter::~TimeoutSetter() { if (timeout == 0) { return; } for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { if (type & timeout_type_list[i]) { if (timeout != original_timeout[i]) { socket_->set_timeout(original_timeout[i], timeout_type_list[i]); } } } } bool Socket::TimeoutController::has_timedout(TimeoutType _type) { SW_ASSERT_1BYTE(_type); if (timeout > 0) { if (sw_unlikely(startup_time == 0)) { startup_time = microtime(); } else { double used_time = microtime() - startup_time; if (sw_unlikely(timeout - used_time < SW_TIMER_MIN_SEC)) { socket_->set_err(ETIMEDOUT); return true; } socket_->set_timeout(timeout - used_time, _type); } } return false; } } // namespace coroutine } // namespace swoole ================================================ FILE: src/coroutine/system.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_coroutine_system.h" #include "swoole_lru_cache.h" #include "swoole_signal.h" #include "swoole_iouring.h" #include "swoole_socket_impl.h" namespace swoole { namespace coroutine { static struct { size_t capacity; time_t expire; LRUCache *data; size_t miss_count; size_t hit_count; } dns_cache = { 1000, 60, nullptr, 0, 0, }; void System::set_dns_cache_expire(time_t expire) { dns_cache.expire = expire; } void System::set_dns_cache_capacity(size_t capacity) { dns_cache.capacity = capacity; clear_dns_cache(); delete dns_cache.data; dns_cache.data = nullptr; } void System::clear_dns_cache() { if (dns_cache.data) { dns_cache.data->clear(); } dns_cache.miss_count = 0; dns_cache.hit_count = 0; } float System::get_dns_cache_hit_ratio() { auto total = dns_cache.hit_count + dns_cache.miss_count; if (total == 0) { return 0; } return (float) dns_cache.hit_count / (float) total; } int System::sleep(double sec) { #if SW_USE_IOURING return Iouring::sleep(sec); #endif Coroutine *co = Coroutine::get_current_safe(); if (sec < SW_TIMER_MIN_SEC) { sec = SW_TIMER_MIN_SEC; } co->yield_ex(sec); return co->is_canceled() ? SW_ERR : SW_OK; } std::shared_ptr System::read_file(const char *file, bool lock) { std::shared_ptr result; async([&result, file, lock]() { File fp(file, O_RDONLY); if (!fp.ready()) { swoole_sys_warning("open(%s, O_RDONLY) failed", file); return; } if (lock && !fp.lock(LOCK_SH)) { swoole_sys_warning("flock(%s, LOCK_SH) failed", file); return; } ssize_t filesize = fp.get_size(); if (filesize > 0) { auto content = make_string(filesize + 1); content->length = fp.read_all(content->str, filesize); content->str[content->length] = 0; result = std::shared_ptr(content); } else { result = fp.read_content(); } if (lock && !fp.unlock()) { swoole_sys_warning("flock(%s, LOCK_UN) failed", file); } }); return result; } ssize_t System::write_file(const char *file, const char *buf, size_t length, bool lock, int flags) { ssize_t retval = -1; int file_flags = flags | O_CREAT | O_WRONLY; async([&]() { File _file(file, file_flags, 0644); if (!_file.ready()) { swoole_sys_warning("open(%s, %d) failed", file, file_flags); return; } if (lock && !_file.lock(LOCK_EX)) { swoole_sys_warning("flock(%s, LOCK_EX) failed", file); return; } size_t bytes = _file.write_all(buf, length); if ((file_flags & SW_AIO_WRITE_FSYNC) && !_file.sync()) { swoole_sys_warning("fsync(%s) failed", file); } if (lock && !_file.unlock()) { swoole_sys_warning("flock(%s, LOCK_UN) failed", file); } retval = bytes; }); return retval; } std::string gethostbyname_impl_with_async(const std::string &hostname, int domain, double timeout) { AsyncEvent ev{}; auto req = new GethostbynameRequest(hostname, domain); ev.data = std::shared_ptr(req); ev.retval = 1; coroutine::async(async::handler_gethostbyname, ev, timeout); if (ev.retval == -1) { if (ev.error == SW_ERROR_AIO_TIMEOUT) { ev.error = SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT; } swoole_set_last_error(ev.error); return ""; } else { return req->addr; } } std::string System::gethostbyname(const std::string &hostname, int domain, double timeout) { if (dns_cache.data == nullptr && dns_cache.capacity != 0) { dns_cache.data = new LRUCache(dns_cache.capacity); } std::string cache_key; std::string result; if (dns_cache.data) { /** * The cache key must end with a prefix that uses a dot. * The domain name cannot contain the `.` symbol, and other characters are considered unsafe. */ cache_key.append(domain == AF_INET ? "IPv4." : "IPv6."); cache_key.append(hostname); auto cache = dns_cache.data->get(cache_key); if (cache) { dns_cache.hit_count++; return *(std::string *) cache.get(); } else { dns_cache.miss_count++; } } #ifdef SW_USE_CARES auto result_list = dns_lookup_impl_with_cares(hostname.c_str(), domain, timeout); if (!result_list.empty()) { if (SwooleG.dns_lookup_random) { result = result_list[swoole_rand() % result_list.size()]; } else { result = result_list[0]; } } #else result = gethostbyname_impl_with_async(hostname, domain, timeout); #endif if (dns_cache.data && !result.empty()) { dns_cache.data->set(cache_key, std::make_shared(result), dns_cache.expire); } return result; } std::vector System::getaddrinfo( const std::string &hostname, int family, int socktype, int protocol, const std::string &service, double timeout) { assert(!hostname.empty()); assert(family == AF_INET || family == AF_INET6); AsyncEvent ev{}; auto req = new GetaddrinfoRequest(hostname, family, socktype, protocol, service); ev.data = std::shared_ptr(req); coroutine::async(async::handler_getaddrinfo, ev, timeout); std::vector retval; if (ev.retval == -1 || req->error != 0) { if (ev.error == SW_ERROR_AIO_TIMEOUT) { ev.error = SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT; } swoole_set_last_error(ev.error); } else { req->parse_result(retval); } return retval; } struct SignalListener { Coroutine *co; int signo; }; /** * Only the main thread should listen for signals, * without modifying it to a thread-local variable. */ static SignalListener *listeners[SW_SIGNO_MAX]; int System::wait_signal(int signal, double timeout) { std::vector signals = {signal}; return wait_signal(signals, timeout); } /** * @error: swoole_get_last_error() */ int System::wait_signal(const std::vector &signals, double timeout) { SignalListener listener = { Coroutine::get_current_safe(), -1, }; if (SwooleG.signal_listener_num > 0) { swoole_set_last_error(EBUSY); return -1; } auto callback_fn = [](int signo) { auto listener = listeners[signo]; if (listener) { listeners[signo] = nullptr; listener->signo = signo; listener->co->resume(); } }; for (auto &signo : signals) { if (signo < 0 || signo >= SW_SIGNO_MAX || signo == SIGCHLD) { swoole_set_last_error(EINVAL); return -1; } /* resgiter signal */ listeners[signo] = &listener; #ifdef SW_USE_THREAD_CONTEXT swoole_event_defer([signo, &callback_fn](void *) { swoole_signal_set(signo, callback_fn); }, nullptr); #else swoole_signal_set(signo, callback_fn); #endif } // exit condition if (!sw_reactor()->isset_exit_condition(Reactor::EXIT_CONDITION_CO_SIGNAL_LISTENER)) { sw_reactor()->set_exit_condition( Reactor::EXIT_CONDITION_CO_SIGNAL_LISTENER, [](Reactor *reactor, size_t &event_num) -> bool { return SwooleG.signal_async_listener_num == 0; }); } SwooleG.signal_async_listener_num++; bool retval = listener.co->yield_ex(timeout); for (auto &signo : signals) { #ifdef SW_USE_THREAD_CONTEXT swoole_event_defer([signo](void *) { swoole_signal_set(signo, nullptr); }, nullptr); #else swoole_signal_set(signo, nullptr); #endif listeners[signo] = nullptr; } SwooleG.signal_async_listener_num--; return retval ? listener.signo : -1; } struct CoroPollTask { std::unordered_map *fds = nullptr; Coroutine *co = nullptr; TimerNode *timer = nullptr; bool success = false; bool wait = true; }; static inline void socket_poll_clean(const CoroPollTask *task) { for (auto &fd : *task->fds) { network::Socket *socket = fd.second.socket; if (!socket) { continue; } int retval = swoole_event_del(fd.second.socket); socket->move_fd(); socket->free(); fd.second.socket = nullptr; if (retval < 0) { continue; } } } static void socket_poll_timeout(Timer *timer, TimerNode *tnode) { auto *task = static_cast(tnode->data); task->timer = nullptr; task->success = false; task->wait = false; socket_poll_clean(task); task->co->resume(); } static void socket_poll_completed(void *data) { auto *task = static_cast(data); socket_poll_clean(task); task->co->resume(); } static inline void socket_poll_trigger_event(Reactor *reactor, CoroPollTask *task, int fd, EventType event) { auto i = task->fds->find(fd); if (event == SW_EVENT_ERROR && !(i->second.events & SW_EVENT_ERROR)) { if (i->second.events & SW_EVENT_READ) { i->second.revents |= SW_EVENT_READ; } if (i->second.events & SW_EVENT_WRITE) { i->second.revents |= SW_EVENT_WRITE; } } else { i->second.revents |= event; } if (task->wait) { task->wait = false; task->success = true; if (task->timer) { swoole_timer_del(task->timer); task->timer = nullptr; } reactor->defer(socket_poll_completed, task); } } static int socket_poll_read_callback(Reactor *reactor, Event *event) { socket_poll_trigger_event(reactor, (CoroPollTask *) event->socket->object, event->fd, SW_EVENT_READ); return SW_OK; } static int socket_poll_write_callback(Reactor *reactor, Event *event) { socket_poll_trigger_event(reactor, (CoroPollTask *) event->socket->object, event->fd, SW_EVENT_WRITE); return SW_OK; } static int socket_poll_error_callback(Reactor *reactor, Event *event) { socket_poll_trigger_event(reactor, (CoroPollTask *) event->socket->object, event->fd, SW_EVENT_ERROR); return SW_OK; } bool System::socket_poll(std::unordered_map &fds, double timeout) { if (timeout == 0) { auto *event_list = static_cast(sw_calloc(fds.size(), sizeof(struct pollfd))); if (!event_list) { swoole_warning("calloc() failed"); return false; } int n = 0; for (auto i = fds.begin(); i != fds.end(); ++i, n++) { event_list[n].fd = i->first; event_list[n].events = translate_events_to_poll(i->second.events); event_list[n].revents = 0; } int retval = ::poll(event_list, n, 0); if (retval > 0) { int _n = 0; for (auto i = fds.begin(); i != fds.end(); ++i, _n++) { i->second.revents = translate_events_from_poll(event_list[_n].revents); } } sw_free(event_list); return retval > 0; } size_t tasked_num = 0; CoroPollTask task; task.fds = &fds; task.co = Coroutine::get_current_safe(); for (auto &fd : fds) { fd.second.socket = make_socket(fd.first, SW_FD_CO_POLL); if (swoole_event_add(fd.second.socket, fd.second.events) < 0) { // socket_poll() is not the owner of the socket, // so the socket should not be closed upon failure or successful return; // it is necessary to release control over the fd. fd.second.socket->move_fd(); fd.second.socket->free(); continue; } fd.second.socket->object = &task; tasked_num++; } if (sw_unlikely(tasked_num == 0)) { return false; } if (timeout > 0) { task.timer = swoole_timer_add(timeout, false, socket_poll_timeout, &task); } task.co->yield(); return task.success; } struct EventWaiter { network::Socket *socket; Coroutine *co; int revents; int error_; EventWaiter(int fd, int events, double timeout) { error_ = revents = 0; socket = swoole::make_socket(fd, SW_FD_CO_EVENT); socket->object = this; co = Coroutine::get_current_safe(); if (swoole_event_add(socket, events) < 0) { error_ = swoole_get_last_error(); goto _done; } if (!co->yield_ex(timeout)) { error_ = swoole_get_last_error(); } swoole_event_del(socket); _done: socket->fd = -1; /* skip close */ socket->free(); } }; static inline void event_waiter_callback(Reactor *reactor, EventWaiter *waiter, EventType event) { if (waiter->revents == 0) { reactor->defer([waiter](void *data) { waiter->co->resume(); }); } waiter->revents |= event; } static int event_waiter_read_callback(Reactor *reactor, Event *event) { event_waiter_callback(reactor, (EventWaiter *) event->socket->object, SW_EVENT_READ); return SW_OK; } static int event_waiter_write_callback(Reactor *reactor, Event *event) { event_waiter_callback(reactor, (EventWaiter *) event->socket->object, SW_EVENT_WRITE); return SW_OK; } static int event_waiter_error_callback(Reactor *reactor, Event *event) { event_waiter_callback(reactor, (EventWaiter *) event->socket->object, SW_EVENT_ERROR); return SW_OK; } /** * @errror: errno & swoole_get_last_error() */ int System::wait_event(int fd, int events, double timeout) { events &= SW_EVENT_READ | SW_EVENT_WRITE; if (events == 0) { swoole_set_last_error(EINVAL); return -1; } if (timeout == 0) { pollfd pfd; pfd.fd = fd; pfd.events = translate_events_to_poll(events); pfd.revents = 0; int retval = ::poll(&pfd, 1, 0); if (retval == 1) { if (pfd.revents & POLLNVAL) { swoole_set_last_error(EBADF); return -1; } return translate_events_from_poll(pfd.revents); } swoole_set_last_error(retval < 0 ? errno : ETIMEDOUT); return -1; } EventWaiter waiter(fd, events, timeout); if (waiter.error_) { errno = waiter.error_; return SW_ERR; } int revents = waiter.revents; if (revents & SW_EVENT_ERROR) { revents ^= SW_EVENT_ERROR; if (events & SW_EVENT_READ) { revents |= SW_EVENT_READ; } if (events & SW_EVENT_WRITE) { revents |= SW_EVENT_WRITE; } } return revents; } bool System::exec(const char *command, bool get_error_stream, std::shared_ptr buffer, int *status) { Coroutine::get_current_safe(); pid_t pid; int fd = swoole_shell_exec(command, &pid, get_error_stream); if (fd < 0) { swoole_sys_warning("Unable to execute '%s'", command); return false; } SocketImpl socket(fd, SW_SOCK_UNIX_STREAM); while (true) { ssize_t retval = socket.read(buffer->str + buffer->length, buffer->size - buffer->length); if (retval > 0) { buffer->length += retval; if (buffer->length == buffer->size) { buffer->extend(); } } else { break; } } socket.close(); return waitpid_safe(pid, status, 0) == pid; } void System::init_reactor(Reactor *reactor) { reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_READ, socket_poll_read_callback); reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_WRITE, socket_poll_write_callback); reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_ERROR, socket_poll_error_callback); reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_READ, event_waiter_read_callback); reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_WRITE, event_waiter_write_callback); reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_ERROR, event_waiter_error_callback); reactor->set_handler(SW_FD_AIO, SW_EVENT_READ, AsyncThreads::callback); #ifdef SW_USE_IOURING reactor->set_handler(SW_FD_IOURING, SW_EVENT_READ, Iouring::callback); #endif } static void async_task_completed(AsyncEvent *event) { if (event->canceled) { return; } auto *co = static_cast(event->object); co->resume(); } /** * @error: swoole_get_last_error() */ bool async(async::Handler handler, AsyncEvent &event, double timeout) { Coroutine *co = Coroutine::get_current_safe(); event.object = co; event.handler = handler; event.callback = async_task_completed; AsyncEvent *_ev = async::dispatch(&event); if (_ev == nullptr) { return false; } if (!co->yield_ex(timeout)) { event.canceled = _ev->canceled = true; event.retval = -1; event.error = errno = swoole_get_last_error(); return false; } else { event.canceled = _ev->canceled; event.error = errno = _ev->error; event.retval = _ev->retval; return true; } } struct AsyncLambdaTask { Coroutine *co; std::function fn; }; static void async_lambda_handler(AsyncEvent *event) { auto *task = static_cast(event->object); task->fn(); event->error = errno; event->retval = 0; } static void async_lambda_callback(AsyncEvent *event) { auto *task = static_cast(event->object); task->co->resume(); } bool async(const std::function &fn) { AsyncEvent event{}; AsyncLambdaTask task{Coroutine::get_current_safe(), fn}; event.object = &task; event.handler = async_lambda_handler; event.callback = async_lambda_callback; AsyncEvent *_ev = async::dispatch(&event); if (_ev == nullptr) { return false; } task.co->yield(); errno = _ev->error; return true; } bool wait_for(const std::function &fn) { double second = SW_FILE_LOCK_DEFAULT_SECOND; while (true) { if (fn()) { break; } if (System::sleep(second) != SW_OK) { return false; } // Limit the maximum waiting time to 0.1 second; otherwise, this exponential time waiting // will make it increasingly difficult to acquire the lock. second = (second >= SW_FILE_LOCK_MAX_SECOND) ? SW_FILE_LOCK_DEFAULT_SECOND : SW_MIN(second * 2, SW_FILE_LOCK_MAX_SECOND); } return true; } } // namespace coroutine } // namespace swoole ================================================ FILE: src/coroutine/thread_context.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_signal.h" #include "swoole_async.h" #include "swoole_coroutine_context.h" #ifdef SW_USE_THREAD_CONTEXT namespace swoole { namespace coroutine { static std::mutex g_lock; static Reactor *g_reactor = nullptr; static Timer *g_timer = nullptr; static String *g_buffer = nullptr; static AsyncThreads *g_async_threads = nullptr; static std::mutex *current_lock = nullptr; void thread_context_init() { if (!swoole_timer_is_available()) { swoole_timer_add( 1L, false, [](Timer *timer, TimerNode *tnode) { // do nothing }, nullptr); } if (SwooleTG.async_threads == nullptr) { SwooleTG.async_threads = new AsyncThreads(); } g_reactor = SwooleTG.reactor; g_buffer = SwooleTG.buffer_stack; g_timer = SwooleTG.timer; g_async_threads = SwooleTG.async_threads; current_lock = &g_lock; g_lock.lock(); } void thread_context_clean() { g_reactor = nullptr; g_buffer = nullptr; g_timer = nullptr; g_async_threads = nullptr; current_lock = nullptr; g_lock.unlock(); } Context::Context(size_t stack_size, CoroutineFunc fn, void *private_data) : fn_(std::move(fn)), private_data_(private_data) { end_ = false; lock_.lock(); swap_lock_ = nullptr; thread_ = std::thread(Context::context_func, this); } Context::~Context() { thread_.join(); } bool Context::swap_in() { swap_lock_ = current_lock; current_lock = &lock_; lock_.unlock(); swap_lock_->lock(); return true; } bool Context::swap_out() { current_lock = swap_lock_; swap_lock_->unlock(); lock_.lock(); return true; } void Context::context_func(coroutine_transfer_t arg) { swoole_signal_block_all(); Context *_this = (Context *) arg; SwooleTG.reactor = g_reactor; SwooleTG.timer = g_timer; SwooleTG.buffer_stack = g_buffer; SwooleTG.async_threads = g_async_threads; _this->lock_.lock(); _this->fn_(_this->private_data_); _this->end_ = true; current_lock = _this->swap_lock_; _this->swap_lock_->unlock(); } } // namespace coroutine } // namespace swoole #endif ================================================ FILE: src/coroutine/uring_socket.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | @link https://www.swoole.com/ | | @contact team@swoole.com | | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | | @Author Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_uring_socket.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" #include "swoole_util.h" #include "swoole_iouring.h" typedef swoole::network::Socket NetSocket; #ifdef SW_USE_IOURING namespace swoole { namespace coroutine { bool UringSocket::connect(const sockaddr *addr, socklen_t addrlen) { if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } write_co = read_co = Coroutine::get_current_safe(); int retval = Iouring::connect(socket->get_fd(), addr, addrlen, socket->connect_timeout); write_co = read_co = nullptr; if (retval < 0) { return false; } connected = true; socket->get_name(); set_err(0); return true; } UringSocket *UringSocket::accept(double timeout) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return nullptr; } if (ssl_is_enable() && sw_unlikely(ssl_context->context == nullptr) && !ssl_context_create()) { return nullptr; } read_co = Coroutine::get_current_safe(); network::Socket *conn = uring_accept(timeout == 0 ? socket->read_timeout : timeout); read_co = nullptr; if (conn == nullptr) { set_err(errno); return nullptr; } auto *client_sock = new UringSocket(conn, this); if (sw_unlikely(client_sock->get_fd() < 0)) { swoole_sys_warning("new Socket() failed"); set_err(errno); delete client_sock; return nullptr; } return client_sock; } NetSocket *UringSocket::uring_accept(double timeout) { auto *client_socket = new NetSocket(); int fd = Iouring::accept(socket->get_fd(), reinterpret_cast(&client_socket->info.addr), &client_socket->info.len, SOCK_CLOEXEC | SOCK_NONBLOCK, socket->read_timeout); if (fd < 0) { delete client_socket; return nullptr; } client_socket->fd = fd; client_socket->removed = 1; client_socket->info.type = socket->socket_type; client_socket->nonblock = 1; client_socket->cloexec = 1; return client_socket; } ssize_t UringSocket::uring_send(const void *_buf, size_t _n) { return Iouring::send(socket->get_fd(), _buf, _n, 0, socket->write_timeout); } ssize_t UringSocket::uring_recv(void *_buf, size_t _n) { return Iouring::recv(socket->get_fd(), _buf, _n, 0, socket->read_timeout); } ssize_t UringSocket::uring_readv(const struct iovec *iovec, int count) { return Iouring::readv(socket->get_fd(), iovec, count, socket->read_timeout); } ssize_t UringSocket::uring_writev(const struct iovec *iovec, int count) { return Iouring::writev(socket->get_fd(), iovec, count, socket->write_timeout); } ssize_t UringSocket::uring_sendfile(const File &file, off_t *offset, size_t size) { return Iouring::sendfile(socket->get_fd(), file.get_fd(), offset, size, socket->write_timeout); } ssize_t UringSocket::read(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } read_co = Coroutine::get_current_safe(); ssize_t retval = Iouring::read(socket->get_fd(), _buf, _n, socket->read_timeout); read_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::write(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } write_co = Coroutine::get_current_safe(); ssize_t retval = Iouring::write(socket->get_fd(), _buf, _n, socket->write_timeout); write_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::recvmsg(msghdr *msg, int flags) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } read_co = Coroutine::get_current_safe(); ssize_t retval = Iouring::recvmsg(socket->get_fd(), msg, flags, socket->read_timeout); read_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::sendmsg(const msghdr *msg, int flags) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } write_co = Coroutine::get_current_safe(); ssize_t retval = Iouring::sendmsg(socket->get_fd(), msg, flags, socket->write_timeout); write_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } read_co = Coroutine::get_current_safe(); ssize_t retval = Iouring::recvfrom(socket->get_fd(), _buf, _n, _addr, _socklen, socket->read_timeout); read_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::sendto(const std::string &host, int port, const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval = 0; network::Address addr; if (!socket->is_dgram()) { set_err(EPROTONOSUPPORT); return -1; } auto ip_addr = host; SW_LOOP_N(2) { if (!addr.assign(type, ip_addr, port, false)) { if (swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { ip_addr = System::gethostbyname(host, sock_domain, socket->dns_timeout); if (!ip_addr.empty()) { continue; } } set_err(); return -1; } break; } write_co = Coroutine::get_current_safe(); retval = Iouring::sendto(socket->get_fd(), _buf, _n, 0, &addr.addr.ss, addr.len, socket->write_timeout); write_co = nullptr; swoole_trace_log(SW_TRACE_SOCKET, "sendto %ld/%ld bytes, errno=%d", retval, _n, errno); check_return_value(retval); return retval; } ssize_t UringSocket::recv(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; read_co = Coroutine::get_current_safe(); if (is_ssl()) { retval = ssl_recv(_buf, _n); } else { retval = uring_recv(_buf, _n); } read_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::send(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; write_co = Coroutine::get_current_safe(); if (is_ssl()) { retval = ssl_send(_buf, _n); } else { retval = uring_send(_buf, _n); } write_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::recv_all(void *_buf, size_t _n) { ssize_t retval = 0; size_t total_bytes = 0; do { retval = recv((char *) _buf + total_bytes, _n - total_bytes); if (retval <= 0) { break; } total_bytes += retval; } while (total_bytes < _n); check_return_value(retval); return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } ssize_t UringSocket::send_all(const void *_buf, size_t _n) { ssize_t retval = 0; size_t total_bytes = 0; do { retval = send((char *) _buf + total_bytes, _n - total_bytes); if (retval <= 0) { break; } total_bytes += retval; } while (total_bytes < _n); check_return_value(retval); return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } bool UringSocket::poll(EventType _type, double timeout) { if (sw_unlikely(!is_available(_type))) { return false; } struct pollfd fds[1]; fds[0].events = translate_events_to_poll(_type); fds[0].fd = socket->get_fd(); fds[0].revents = 0; auto rc = Iouring::poll(fds, 1, timeout > 0 ? timeout * 1000 : timeout) == 1; if (rc != 1) { set_err(rc == 0 ? ETIMEDOUT : errno); return false; } return true; } bool UringSocket::sendfile(const char *filename, off_t offset, size_t length) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return false; } File file(filename, O_RDONLY); if (!file.ready()) { set_err(errno, std_string::format("open(%s) failed, %s", filename, strerror(errno))); return false; } if (length == 0) { FileStatus file_stat; if (!file.stat(&file_stat)) { set_err(errno, std_string::format("fstat(%s) failed, %s", filename, strerror(errno))); return false; } length = file_stat.st_size; } else { // total length of the file length = offset + length; } ssize_t retval; write_co = Coroutine::get_current_safe(); if (is_ssl()) { retval = ssl_sendfile(file, &offset, length); } else { retval = uring_sendfile(file, &offset, length); } write_co = nullptr; return retval == (ssize_t) length; } ssize_t UringSocket::readv(network::IOVector *io_vector) { ssize_t retval; if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } read_co = Coroutine::get_current_safe(); do { if (is_ssl()) { retval = ssl_readv(io_vector); } else { retval = uring_readv(io_vector->get_iterator(), io_vector->get_remain_count()); } io_vector->update_iterator(retval); } while (retval < 0 && errno == EINTR); read_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::writev(network::IOVector *io_vector) { ssize_t retval; if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } write_co = Coroutine::get_current_safe(); do { if (is_ssl()) { retval = ssl_writev(io_vector); } else { retval = uring_writev(io_vector->get_iterator(), io_vector->get_remain_count()); } io_vector->update_iterator(retval); } while (retval < 0 && errno == EINTR); write_co = nullptr; check_return_value(retval); return retval; } ssize_t UringSocket::readv_all(network::IOVector *io_vector) { ssize_t retval = 0; size_t total_bytes = 0; do { retval = readv(io_vector); if (retval <= 0) { break; } total_bytes += retval; } while (retval > 0 && io_vector->get_remain_count() > 0); check_return_value(retval); return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } ssize_t UringSocket::writev_all(network::IOVector *io_vector) { ssize_t retval = 0; size_t total_bytes = 0; do { retval = writev(io_vector); if (retval <= 0) { break; } total_bytes += retval; } while (retval > 0 && io_vector->get_remain_count() > 0); check_return_value(retval); return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } bool UringSocket::ssl_bio_write() { auto buf = get_write_buffer(); while (true) { size_t pending = BIO_ctrl_pending(wbio); if (pending == 0) { break; } int nread = BIO_read(wbio, buf->str, buf->size); if (nread <= 0) { set_err(SW_ERROR_SSL_BAD_PROTOCOL); return false; } ssize_t written = 0; while (written < nread) { ssize_t n = uring_send(buf->str + written, nread - written); if (n > 0) { written += n; } else if (n == 0) { errno = SW_ERROR_SSL_RESET; return false; } else { if (errno == EINTR) { continue; } set_err(errno); return false; } } } return true; } bool UringSocket::ssl_bio_read() { auto buf = get_read_buffer(); ssize_t rv = uring_recv(buf->str, buf->size); if (rv > 0) { int written = BIO_write(rbio, buf->str, rv); if (written != rv) { set_err(SW_ERROR_SSL_BAD_PROTOCOL); return false; } return true; } else if (rv == 0) { set_err(SW_ERROR_SSL_RESET); return false; } else { if (errno == EINTR) { return ssl_bio_read(); } set_err(errno); return false; } } bool UringSocket::ssl_bio_prepare() { if (BIO_ctrl_pending(wbio) > 0) { if (!ssl_bio_write()) { check_return_value(-1); return false; } } return true; } bool UringSocket::ssl_bio_perform(int rc, const char *fn) { if (!ssl_bio_prepare()) { return false; } int error = SSL_get_error(socket->ssl, rc); if (error == SSL_ERROR_WANT_WRITE) { if (ssl_bio_write()) { _error: check_return_value(-1); return false; } return true; } else if (error == SSL_ERROR_WANT_READ) { if (!ssl_bio_read()) { goto _error; } return true; } else if (error == SSL_ERROR_ZERO_RETURN) { swoole_debug("%s(fd=%d) return zero value", fn, socket->get_fd()); error = SW_ERROR_SSL_RESET; goto _error; } else if (error == SSL_ERROR_SYSCALL) { goto _error; } else { ulong_t err_code = ERR_get_error(); if (err_code) { char error_buf[512]; ERR_error_string_n(err_code, error_buf, sizeof(error_buf)); swoole_notice("%s(fd=%d) to server[%s:%d] failed. Error: %s[%d|%d]", fn, socket->get_fd(), socket->info.get_addr(), socket->info.get_port(), error_buf, error, ERR_GET_REASON(err_code)); set_err(SW_ERROR_SSL_BAD_PROTOCOL, error_buf); } else { set_err(SW_ERROR_SSL_BAD_PROTOCOL); } return false; } } bool UringSocket::ssl_handshake() { if (ssl_handshaked) { set_err(SW_ERROR_WRONG_OPERATION); return false; } if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } /** * If the ssl_context is empty, it indicates that this socket was not a connection * returned by a server socket accept, and a new ssl_context needs to be created. */ if (ssl_context->context == nullptr && !ssl_context_create()) { return false; } if (!ssl_create(get_ssl_context())) { return false; } rbio = BIO_new(BIO_s_mem()); wbio = BIO_new(BIO_s_mem()); SSL_set_bio(socket->ssl, rbio, wbio); const char *fn; if (ssl_is_server) { fn = "ssl_accept"; SSL_set_accept_state(socket->ssl); } else { fn = "ssl_connect"; SSL_set_connect_state(socket->ssl); } while (true) { auto rs = SSL_do_handshake(socket->ssl); if (rs == 1) { break; } if (ssl_bio_perform(rs, fn)) { continue; } else { return false; } } if (ssl_context->verify_peer) { if (!ssl_verify(ssl_context->allow_self_signed)) { return false; } } ssl_handshaked = true; return true; } ssize_t UringSocket::ssl_readv(network::IOVector *io_vector) { ssize_t retval, total_bytes = 0; do { retval = ssl_recv(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); total_bytes += retval > 0 ? retval : 0; io_vector->update_iterator(retval); } while (retval > 0 && io_vector->get_remain_count() > 0); return total_bytes > 0 ? total_bytes : retval; } ssize_t UringSocket::ssl_writev(network::IOVector *io_vector) { ssize_t retval, total_bytes = 0; do { retval = ssl_send(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); total_bytes += retval > 0 ? retval : 0; io_vector->update_iterator(retval); } while (retval > 0 && io_vector->get_remain_count() > 0); return total_bytes > 0 ? total_bytes : retval; } ssize_t UringSocket::ssl_recv(void *_buf, size_t _n) { while (true) { int n = SSL_read(socket->ssl, _buf, _n); if (!ssl_bio_prepare()) { return -1; } if (n > 0) { return n; } if (!ssl_bio_perform(n, "ssl_recv")) { return -1; } } } ssize_t UringSocket::ssl_send(const void *_buf, size_t _n) { while (true) { int n = SSL_write(socket->ssl, _buf, _n); if (!ssl_bio_prepare()) { return -1; } if (n > 0) { return n; } if (!ssl_bio_perform(n, "ssl_send")) { return -1; } } } ssize_t UringSocket::ssl_sendfile(const File &file, off_t *offset, size_t size) { char buf[SW_BUFFER_SIZE_BIG]; size_t total = 0; while (total < size) { ssize_t readn = size > sizeof(buf) ? sizeof(buf) : size; ssize_t n = file.pread(buf, readn, *offset); if (n <= 0) { swoole_sys_warning("pread() failed"); break; } ssize_t ret = ssl_send(buf, n); if (ret > 0) { *offset += ret; total += ret; swoole_trace_log(SW_TRACE_REACTOR, "fd=%d, readn=%ld, n=%ld, ret=%ld", socket->get_fd(), readn, n, ret); } else if (ret == 0) { return total; } else { switch (socket->catch_write_error(errno)) { case SW_ERROR: swoole_sys_warning("write() failed"); return total; case SW_CLOSE: return total; case SW_WAIT: default: break; } } } return total; } }; // namespace coroutine }; // namespace swoole #endif ================================================ FILE: src/lock/barrier.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_lock.h" namespace swoole { #define BARRIER_USEC 10000 void Barrier::init(bool shared, int count) { #ifdef SW_USE_PTHREAD_BARRIER if (shared) { pthread_barrierattr_setpshared(&barrier_attr_, PTHREAD_PROCESS_SHARED); pthread_barrier_init(&barrier_, &barrier_attr_, count); } else { pthread_barrier_init(&barrier_, nullptr, count); } shared_ = shared; #else barrier_ = 0; count_ = count; #endif } void Barrier::wait() { #ifdef SW_USE_PTHREAD_BARRIER pthread_barrier_wait(&barrier_); #else sw_atomic_add_fetch(&barrier_, 1); SW_LOOP { if (barrier_ == count_) { break; } usleep(BARRIER_USEC); sw_atomic_memory_barrier(); } #endif } void Barrier::destroy() { #ifdef SW_USE_PTHREAD_BARRIER pthread_barrier_destroy(&barrier_); if (shared_) { pthread_barrierattr_destroy(&barrier_attr_); } #endif } }; // namespace swoole ================================================ FILE: src/lock/coroutine_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: NathanFreeman | +----------------------------------------------------------------------+ */ #include "swoole_lock.h" #ifdef HAVE_IOURING_FUTEX #include "swoole_iouring.h" #else #include "swoole_coroutine_system.h" using swoole::coroutine::System; #endif namespace swoole { CoroutineLock::CoroutineLock(bool shared) : Lock(COROUTINE_LOCK, shared) { if (shared) { value = (sw_atomic_t *) sw_mem_pool()->alloc(sizeof(sw_atomic_t)); } else { value = new sw_atomic_t; } *value = 0; } CoroutineLock::~CoroutineLock() { if (shared_) { sw_mem_pool()->free((void *) value); } else { delete value; } value = nullptr; } int CoroutineLock::lock(int operation, int _) { if (operation & LOCK_NB) { return lock_impl(false); } else { return lock_impl(true); } } int CoroutineLock::unlock() { Coroutine *current_coroutine = Coroutine::get_current(); if (current_coroutine == nullptr) { swoole_warning("The coroutine lock can only be used in a coroutine environment"); return SW_ERROR_CO_OUT_OF_COROUTINE; } if (*value == 0) { return 0; } *value = 0; cid = 0; coroutine = nullptr; #ifdef HAVE_IOURING_FUTEX return Iouring::futex_wakeup((uint32_t *) value) >= 0 ? 0 : errno; #else return 0; #endif } int CoroutineLock::lock_impl(bool blocking) { Coroutine *current_coroutine = Coroutine::get_current(); if (current_coroutine == nullptr) { swoole_warning("The coroutine lock can only be used in a coroutine environment"); return SW_ERROR_CO_OUT_OF_COROUTINE; } if (current_coroutine == static_cast(coroutine) && current_coroutine->get_cid() == cid) { return 0; } int result = 0; #ifndef HAVE_IOURING_FUTEX double second = SW_FILE_LOCK_DEFAULT_SECOND; #endif while (true) { if (sw_atomic_cmp_set(value, 0, 1)) { break; } if (!blocking) { return EBUSY; } #ifdef HAVE_IOURING_FUTEX result = Iouring::futex_wait((uint32_t *) value); if (result != 0) { return errno; } #else if (System::sleep(second) != SW_OK) { return SW_ERROR_CO_CANCELED; } // Limit the maximum waiting time to 0.1 second; otherwise, this exponential time waiting // will make it increasingly difficult to acquire the lock. second = (second >= SW_FILE_LOCK_MAX_SECOND) ? SW_FILE_LOCK_DEFAULT_SECOND : SW_MIN(second * 2, SW_FILE_LOCK_MAX_SECOND); #endif } cid = current_coroutine->get_cid(); coroutine = (void *) current_coroutine; return result; } } // namespace swoole ================================================ FILE: src/lock/mutex.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_lock.h" namespace swoole { struct MutexImpl { pthread_mutex_t lock_; pthread_mutexattr_t attr_; }; Mutex::Mutex(bool shared) : Lock(MUTEX, shared) { if (shared) { impl = (MutexImpl *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } } else { impl = new MutexImpl(); } pthread_mutexattr_init(&impl->attr_); if (shared) { pthread_mutexattr_setpshared(&impl->attr_, PTHREAD_PROCESS_SHARED); } if (pthread_mutex_init(&impl->lock_, &impl->attr_) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_mutex_init() failed"); } } int Mutex::lock(int operation, int timeout_msec) { if (operation & LOCK_NB) { return pthread_mutex_trylock(&impl->lock_); } if (timeout_msec > 0) { #ifdef HAVE_MUTEX_TIMEDLOCK timespec timeo; realtime_get(&timeo); realtime_add(&timeo, timeout_msec); return pthread_mutex_timedlock(&impl->lock_, &timeo); #else return sw_wait_for([this]() { return pthread_mutex_trylock(&impl->lock_) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; #endif } return pthread_mutex_lock(&impl->lock_); } int Mutex::unlock() { return pthread_mutex_unlock(&impl->lock_); } Mutex::~Mutex() { pthread_mutexattr_destroy(&impl->attr_); pthread_mutex_destroy(&impl->lock_); if (shared_) { sw_mem_pool()->free(impl); } else { delete impl; } } } // namespace swoole ================================================ FILE: src/lock/rw_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_lock.h" #ifdef HAVE_RWLOCK namespace swoole { struct RWLockImpl { pthread_rwlock_t lock_; pthread_rwlockattr_t attr_; }; RWLock::RWLock(bool shared) : Lock(RW_LOCK, shared) { if (shared) { impl = (RWLockImpl *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } } else { impl = new RWLockImpl(); } pthread_rwlockattr_init(&impl->attr_); if (shared) { pthread_rwlockattr_setpshared(&impl->attr_, PTHREAD_PROCESS_SHARED); } if (pthread_rwlock_init(&impl->lock_, &impl->attr_) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_rwlock_init() failed"); } } static int rwlock_timed_lock_rd(pthread_rwlock_t *rwlock, int timeout_msec) { #ifdef HAVE_RWLOCK_TIMEDRDLOCK timespec timeo; realtime_get(&timeo); realtime_add(&timeo, timeout_msec); return pthread_rwlock_timedrdlock(rwlock, &timeo); #else return sw_wait_for([rwlock]() { return pthread_rwlock_tryrdlock(rwlock) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; #endif } static int rwlock_timed_lock_wr(pthread_rwlock_t *rwlock, int timeout_msec) { #ifdef HAVE_RWLOCK_TIMEDRDLOCK timespec timeo; realtime_get(&timeo); realtime_add(&timeo, timeout_msec); return pthread_rwlock_timedwrlock(rwlock, &timeo); #else return sw_wait_for([rwlock]() { return pthread_rwlock_trywrlock(rwlock) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; #endif } int RWLock::lock(int operation, int timeout_msec) { if (operation & LOCK_NB) { if (operation & LOCK_SH) { return pthread_rwlock_tryrdlock(&impl->lock_); } else { return pthread_rwlock_trywrlock(&impl->lock_); } } else { if (timeout_msec > 0) { if (operation & LOCK_SH) { return rwlock_timed_lock_rd(&impl->lock_, timeout_msec); } else { return rwlock_timed_lock_wr(&impl->lock_, timeout_msec); } } else { if (operation & LOCK_SH) { return pthread_rwlock_rdlock(&impl->lock_); } else { return pthread_rwlock_wrlock(&impl->lock_); } } } } int RWLock::unlock() { return pthread_rwlock_unlock(&impl->lock_); } RWLock::~RWLock() { pthread_rwlockattr_destroy(&impl->attr_); pthread_rwlock_destroy(&impl->lock_); if (shared_) { sw_mem_pool()->free(impl); } else { delete impl; } } } // namespace swoole #endif ================================================ FILE: src/lock/spin_lock.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_lock.h" #ifdef HAVE_SPINLOCK namespace swoole { SpinLock::SpinLock(bool shared) : Lock(SPIN_LOCK, shared) { if (shared) { impl = (pthread_spinlock_t *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } } else { impl = new pthread_spinlock_t(); } if (pthread_spin_init(impl, shared) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_spin_init() failed"); } } int SpinLock::lock(int operation, int timeout_msec) { if (operation & LOCK_NB) { return pthread_spin_trylock(impl); } if (timeout_msec > 0) { return sw_wait_for([this]() { return pthread_spin_trylock(impl) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; } return pthread_spin_lock(impl); } int SpinLock::unlock() { return pthread_spin_unlock(impl); } SpinLock::~SpinLock() { pthread_spin_destroy(impl); if (shared_) { sw_mem_pool()->free((void *) impl); } else { delete impl; } } } // namespace swoole #endif ================================================ FILE: src/memory/fixed_pool.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" namespace swoole { struct FixedPoolSlice { uint8_t lock; FixedPoolSlice *next; FixedPoolSlice *prev; char data[0]; }; struct FixedPoolImpl { void *memory; size_t size; FixedPoolSlice *head; FixedPoolSlice *tail; // total memory size uint32_t slice_num; // memory usage uint32_t slice_use; // Fixed slice size, not include the memory used by FixedPoolSlice uint32_t slice_size; bool shared; bool allocated; void init(); }; /** * create new FixedPool, random alloc/free fixed size memory */ FixedPool::FixedPool(uint32_t slice_num, uint32_t slice_size, bool shared) { if (slice_num < 2) { throw Exception(SW_ERROR_INVALID_PARAMS); } slice_size = SW_MEM_ALIGNED_SIZE(slice_size); size_t size = slice_num * (sizeof(FixedPoolSlice) + slice_size); size_t alloc_size = size + sizeof(*impl); void *memory = shared ? ::sw_shm_malloc(alloc_size) : ::sw_malloc(alloc_size); if (!memory) { throw std::bad_alloc(); } impl = static_cast(memory); memory = static_cast(memory) + sizeof(*impl); sw_memset_zero(impl, sizeof(*impl)); impl->shared = shared; impl->slice_num = slice_num; impl->slice_size = slice_size; impl->size = size; impl->memory = memory; impl->allocated = true; impl->init(); } /** * create new FixedPool, Using the given memory */ FixedPool::FixedPool(uint32_t slice_size, void *memory, size_t size, bool shared) { impl = (FixedPoolImpl *) memory; memory = (char *) memory + sizeof(*impl); sw_memset_zero(impl, sizeof(*impl)); impl->shared = shared; impl->slice_size = slice_size; impl->size = size - sizeof(*impl); uint32_t slice_num = impl->size / (slice_size + sizeof(FixedPoolSlice)); if (slice_num < 2) { throw Exception(SW_ERROR_INVALID_PARAMS); } impl->slice_num = slice_num; impl->memory = memory; impl->allocated = false; impl->init(); } size_t FixedPool::sizeof_struct_slice() { return sizeof(FixedPoolSlice); } size_t FixedPool::sizeof_struct_impl() { return sizeof(FixedPoolImpl); } /** * linked list */ void FixedPoolImpl::init() { void *cur = memory; void *max = (char *) memory + size; do { auto *slice = static_cast(cur); sw_memset_zero(slice, sizeof(FixedPoolSlice)); if (head != nullptr) { head->prev = slice; slice->next = head; } else { tail = slice; } head = slice; cur = (char *) cur + (sizeof(FixedPoolSlice) + slice_size); if (cur < max) { slice->prev = static_cast(cur); } else { slice->prev = nullptr; break; } } while (true); } uint32_t FixedPool::get_number_of_spare_slice() const { return impl->slice_num - impl->slice_use; } uint32_t FixedPool::get_number_of_total_slice() const { return impl->slice_num; } uint32_t FixedPool::get_slice_size() const { return impl->slice_size; } void *FixedPool::alloc(uint32_t size) { FixedPoolSlice *slice = impl->head; if (slice->lock) { swoole_set_last_error(SW_ERROR_MALLOC_FAIL); assert(get_number_of_spare_slice() == 0); return nullptr; } slice->lock = 1; impl->slice_use++; // move next slice to head (idle list) impl->head = slice->next; impl->head->prev = nullptr; // move this slice to tail (busy list) impl->tail->next = slice; slice->next = nullptr; slice->prev = impl->tail; impl->tail = slice; return slice->data; } void FixedPool::free(void *ptr) { auto *slice = reinterpret_cast(static_cast(ptr) - sizeof(FixedPoolSlice)); assert(ptr > impl->memory && static_cast(ptr) < static_cast(impl->memory) + impl->size); assert(slice->lock == 1); impl->slice_use--; slice->lock = 0; if (slice == impl->head) { return; } if (slice == impl->tail) { slice->prev->next = nullptr; impl->tail = slice->prev; } else { slice->prev->next = slice->next; slice->next->prev = slice->prev; } // move slice to head (idle) slice->prev = nullptr; slice->next = impl->head; impl->head->prev = slice; impl->head = slice; } FixedPool::~FixedPool() { if (!impl->allocated) { return; } if (impl->shared) { ::sw_shm_free(impl); } else { ::sw_free(impl); } } void FixedPool::debug(int max_lines) const { int line = 0; FixedPoolSlice *slice = impl->head; printf("===============================%s=================================\n", __FUNCTION__); while (slice != nullptr) { if (slice->next == slice) { printf("-------------------@@@@@@@@@@@@@@@@@@@@@@----------------\n"); } printf("#%d\t", line); printf("slice[%p]\t", slice); printf("prev=%p\t", slice->prev); printf("next=%p\t", slice->next); printf("tag=%d\t", slice->lock); printf("data=%p\n", slice->data); slice = slice->next; if (line++ > max_lines) { break; } } } } // namespace swoole ================================================ FILE: src/memory/global_memory.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" #include #include #define SW_MIN_PAGE_SIZE 4096 namespace swoole { struct GlobalMemoryImpl { bool shared; uint32_t pagesize; std::mutex lock; std::vector pages; uint32_t alloc_offset = 0; pid_t create_pid; GlobalMemoryImpl(uint32_t _pagesize, bool _shared); ~GlobalMemoryImpl(); char *new_page(); }; struct MemoryBlock { uint32_t size; uint32_t reserved; char memory[0]; }; /** * After the memory is allocated, * it will not be released until it is recycled by OS when the process exits */ GlobalMemory::GlobalMemory(uint32_t pagesize, bool shared) { assert(pagesize >= SW_MIN_PAGE_SIZE); impl = new GlobalMemoryImpl(pagesize, shared); } GlobalMemoryImpl::GlobalMemoryImpl(uint32_t _pagesize, bool _shared) { shared = _shared; pagesize = SW_MEM_ALIGNED_SIZE_EX(_pagesize, swoole_pagesize()); create_pid = getpid(); if (new_page() == nullptr) { throw std::bad_alloc(); } } GlobalMemoryImpl::~GlobalMemoryImpl() { for (auto page : pages) { shared ? ::sw_shm_free(page) : ::sw_free(page); } } char *GlobalMemoryImpl::new_page() { auto page = static_cast(shared ? sw_shm_malloc(pagesize) : sw_malloc(pagesize)); if (page == nullptr) { return nullptr; } pages.push_back(page); alloc_offset = 0; return page; } /** * The returned memory must be initialized to 0 */ void *GlobalMemory::alloc(uint32_t size) { MemoryBlock *block; size = SW_MEM_ALIGNED_SIZE(size); uint32_t alloc_size = sizeof(*block) + size; std::unique_lock lock(impl->lock); if (alloc_size > impl->pagesize) { swoole_warning("failed to alloc %d bytes, exceed the maximum size[%d]", size, impl->pagesize); return nullptr; } if (impl->shared and impl->create_pid != getpid()) { GlobalMemoryImpl *old_impl = impl; impl = new GlobalMemoryImpl(old_impl->pagesize, old_impl->shared); } swoole_trace("alloc_size=%u, size=%u", alloc_size, size); if (impl->alloc_offset + alloc_size > impl->pagesize) { char *page = impl->new_page(); if (page == nullptr) { swoole_warning("alloc memory error"); return nullptr; } } block = reinterpret_cast(impl->pages.back() + impl->alloc_offset); impl->alloc_offset += alloc_size; block->size = size; sw_memset_zero(block->memory, size); return block->memory; } void GlobalMemory::free(void *ptr) {} size_t GlobalMemory::capacity() const { return impl->pagesize - impl->alloc_offset; } size_t GlobalMemory::get_memory_size() const { return impl->pagesize * impl->pages.size(); } GlobalMemory::~GlobalMemory() { delete impl; } } // namespace swoole ================================================ FILE: src/memory/ring_buffer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" namespace swoole { struct RingBufferItem; struct RingBufferImpl { void *memory; bool shared; uint8_t status; uint32_t size; uint32_t alloc_offset; uint32_t collect_offset; uint32_t alloc_count; sw_atomic_t free_count; void collect(); RingBufferItem *get_item(uint32_t offset) const; }; struct RingBufferItem { uint16_t lock; uint16_t index; uint32_t length; char data[0]; }; #ifdef SW_RINGBUFFER_DEBUG static void swRingBuffer_print(swRingBuffer *object, char *prefix); static void swRingBuffer_print(swRingBuffer *object, char *prefix) { printf("%s: size=%d, status=%d, alloc_count=%d, free_count=%d, offset=%d, next_offset=%d\n", prefix, impl->size, impl->status, impl->alloc_count, impl->free_count, impl->alloc_offset, impl->collect_offset); } #endif RingBuffer::RingBuffer(uint32_t size, bool shared) { size = SW_MEM_ALIGNED_SIZE(size); void *mem = (shared == 1) ? sw_shm_malloc(size) : sw_malloc(size); if (mem == nullptr) { throw std::bad_alloc(); } impl = static_cast(mem); mem = static_cast(mem) + sizeof(*impl); sw_memset_zero(impl, sizeof(*impl)); impl->size = size - sizeof(*impl); impl->shared = shared; impl->memory = mem; swoole_debug("memory: ptr=%p", mem); } RingBufferItem *RingBufferImpl::get_item(uint32_t offset) const { return reinterpret_cast(static_cast(memory) + offset); } void RingBufferImpl::collect() { for (uint32_t i = 0; i < free_count; i++) { const auto *item = get_item(collect_offset); if (item->lock == 0) { const uint32_t n_size = item->length + sizeof(RingBufferItem); collect_offset += n_size; if (collect_offset + sizeof(RingBufferItem) > size || collect_offset >= size) { collect_offset = 0; status = 0; } sw_atomic_fetch_sub(&free_count, 1); } else { break; } } } void *RingBuffer::alloc(uint32_t size) { assert(size > 0); RingBufferItem *item; uint32_t capacity; size = SW_MEM_ALIGNED_SIZE(size); uint32_t alloc_size = size + sizeof(RingBufferItem); if (impl->free_count > 0) { impl->collect(); } if (impl->status == 0) { if (impl->alloc_offset + alloc_size >= (impl->size - sizeof(RingBufferItem))) { uint32_t skip_n = impl->size - impl->alloc_offset; if (skip_n >= sizeof(RingBufferItem)) { item = impl->get_item(impl->alloc_offset); item->lock = 0; item->length = skip_n - sizeof(RingBufferItem); sw_atomic_t *free_count = &impl->free_count; sw_atomic_fetch_add(free_count, 1); } impl->alloc_offset = 0; impl->status = 1; capacity = impl->collect_offset - impl->alloc_offset; } else { capacity = impl->size - impl->alloc_offset; } } else { capacity = impl->collect_offset - impl->alloc_offset; } if (capacity < alloc_size) { return nullptr; } item = impl->get_item(impl->alloc_offset); item->lock = 1; item->length = size; item->index = impl->alloc_count; impl->alloc_offset += alloc_size; impl->alloc_count++; swoole_debug("alloc: ptr=%p", (void *) (item->data - (char *) impl->memory)); return item->data; } void RingBuffer::free(void *ptr) { auto *item = reinterpret_cast(static_cast(ptr) - sizeof(RingBufferItem)); assert(ptr >= impl->memory); assert(static_cast(ptr) <= static_cast(impl->memory) + impl->size); assert(item->lock == 1); if (item->lock != 1) { swoole_debug("invalid free: index=%d, ptr=%p", item->index, (void *) (item->data - (char *) impl->memory)); } else { item->lock = 0; } swoole_debug("free: ptr=%p", (void *) (item->data - (char *) impl->memory)); sw_atomic_t *free_count = &impl->free_count; sw_atomic_fetch_add(free_count, 1); } RingBuffer::~RingBuffer() { if (impl->shared) { sw_shm_free(impl); } else { sw_free(impl); } } } // namespace swoole ================================================ FILE: src/memory/shared_memory.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" #include #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) #define MAP_ANONYMOUS MAP_ANON #endif namespace swoole { struct SharedMemory { size_t size_; static void *alloc(size_t size); static void free(void *ptr); static SharedMemory *fetch_object(void *ptr) { return reinterpret_cast(static_cast(ptr) - sizeof(SharedMemory)); } }; void *SharedMemory::alloc(size_t size) { void *mem; int tmpfd = -1; int flags = MAP_SHARED; SharedMemory object; size = SW_MEM_ALIGNED_SIZE(size); size += sizeof(SharedMemory); #ifdef MAP_ANONYMOUS flags |= MAP_ANONYMOUS; #else File zerofile("/dev/zero", O_RDWR); if (!zerofile.ready()) { return nullptr; } tmpfd = zerofile.get_fd(); #endif mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, flags, tmpfd, 0); #ifdef MAP_FAILED if (mem == MAP_FAILED) #else if (!mem) #endif { swoole_sys_warning("mmap(%lu) failed", size); return nullptr; } else { object.size_ = size; memcpy(mem, &object, sizeof(object)); return static_cast(mem) + sizeof(object); } } void SharedMemory::free(void *ptr) { SharedMemory *object = SharedMemory::fetch_object(ptr); size_t size = object->size_; if (munmap(object, size) < 0) { swoole_sys_warning("munmap(%p, %lu) failed", object, size); } } } // namespace swoole using swoole::SharedMemory; void *sw_shm_malloc(size_t size) { return SharedMemory::alloc(size); } void *sw_shm_calloc(size_t num, size_t _size) { return SharedMemory::alloc(num * _size); } int sw_shm_protect(void *ptr, int flags) { SharedMemory *object = SharedMemory::fetch_object(ptr); return mprotect(object, object->size_, flags); } void sw_shm_free(void *ptr) { SharedMemory::free(ptr); } void *sw_shm_realloc(void *ptr, size_t new_size) { SharedMemory *object = SharedMemory::fetch_object(ptr); void *new_ptr = sw_shm_malloc(new_size); if (new_ptr == nullptr) { return nullptr; } memcpy(new_ptr, ptr, object->size_); SharedMemory::free(ptr); return new_ptr; } ================================================ FILE: src/memory/table.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_table.h" #include "swoole_hash.h" #include "swoole_util.h" #include namespace swoole { Table *Table::make(uint32_t rows_size, float conflict_proportion) { if (rows_size >= SW_TABLE_MAX_ROW_SIZE) { rows_size = SW_TABLE_MAX_ROW_SIZE; } else { uint32_t i = 6; while ((1U << i) < rows_size) { i++; } rows_size = 1 << i; } if (conflict_proportion > 1.0) { conflict_proportion = 1.0; } else if (conflict_proportion < SW_TABLE_CONFLICT_PROPORTION) { conflict_proportion = SW_TABLE_CONFLICT_PROPORTION; } auto table = static_cast(sw_mem_pool()->alloc(sizeof(Table))); if (table == nullptr) { return nullptr; } table->mutex = new Mutex(true); table->iterator = nullptr; table->column_map = new std::unordered_map; table->column_list = new std::vector; table->size = rows_size; table->mask = rows_size - 1; table->conflict_proportion = conflict_proportion; #ifdef SW_TABLE_USE_PHP_HASH table->hash_func = swoole_hash_php; #else table->hash_func = swoole_hash_austin; #endif return table; } bool Table::add_column(const std::string &_name, enum TableColumn::Type _type, size_t _size) { if (_type < TableColumn::TYPE_INT || _type > TableColumn::TYPE_STRING) { swoole_warning("unknown column type"); return false; } auto col = new TableColumn(_name, _type, _size); col->index = item_size; item_size += col->size; column_map->emplace(_name, col); column_list->push_back(col); return true; } TableColumn *Table::get_column(const std::string &key) const { auto i = column_map->find(key); if (i == column_map->end()) { return nullptr; } else { return i->second; } } bool Table::exists(const char *key, uint16_t keylen) const { TableRow *_rowlock = nullptr; const TableRow *row = get(key, keylen, &_rowlock); _rowlock->unlock(); return row != nullptr; } TableIterator::TableIterator(size_t row_size) { current_ = (TableRow *) sw_malloc(row_size); if (!current_) { throw std::bad_alloc(); } mutex_ = new Mutex(true); row_memory_size_ = row_size; reset(); } void TableIterator::reset() { absolute_index = 0; collision_index = 0; sw_memset_zero(current_, row_memory_size_); } TableIterator::~TableIterator() { if (current_) { sw_free(current_); } delete mutex_; } size_t Table::calc_memory_size() const { /** * table size + conflict size */ size_t _row_num = size * (1 + conflict_proportion); /* * header + data */ size_t _row_memory_size = sizeof(TableRow) + item_size; /** * row data & header */ size_t _memory_size = _row_num * _row_memory_size; /** * memory pool for conflict rows */ _memory_size += FixedPool::sizeof_struct_impl() + ((_row_num - size) * FixedPool::sizeof_struct_slice()); /** * for iterator, Iterate through all the elements */ _memory_size += size * sizeof(TableRow *); swoole_trace("_memory_size=%lu, _row_num=%lu, _row_memory_size=%lu", _memory_size, _row_num, _row_memory_size); return _memory_size; } size_t Table::get_memory_size() const { return memory_size; } uint32_t Table::get_available_slice_num() const { lock(); uint32_t num = pool->get_number_of_spare_slice(); unlock(); return num; } uint32_t Table::get_total_slice_num() const { return pool->get_number_of_total_slice(); } bool Table::create() { if (created) { return false; } size_t _memory_size = calc_memory_size(); size_t _row_memory_size = sizeof(TableRow) + item_size; void *_memory = sw_shm_malloc(_memory_size); if (_memory == nullptr) { return false; } memory = _memory; rows = static_cast(_memory); _memory = static_cast(_memory) + size * sizeof(TableRow *); _memory_size -= size * sizeof(TableRow *); for (size_t i = 0; i < size; i++) { rows[i] = reinterpret_cast(static_cast(_memory) + (_row_memory_size * i)); memset(rows[i], 0, sizeof(TableRow)); } _memory = static_cast(_memory) + _row_memory_size * size; _memory_size -= _row_memory_size * size; pool = new FixedPool(_row_memory_size, _memory, _memory_size, true); iterator = new TableIterator(_row_memory_size); memory_size = _memory_size; created = true; return true; } void Table::destroy() { #ifdef SW_TABLE_DEBUG printf("swoole_table: size=%ld, conflict_count=%d, conflict_max_level=%d, insert_count=%d\n", size, conflict_count, conflict_max_level, insert_count); #endif auto i = column_map->begin(); while (i != column_map->end()) { delete i->second; column_map->erase(i++); } delete column_map; delete column_list; delete iterator; delete pool; if (memory) { sw_shm_free(memory); } memory = nullptr; delete mutex; sw_mem_pool()->free(this); } void TableRow::lock() { sw_atomic_t *lock = &lock_; uint32_t i, n; long t = 0; while (true) { if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { _success: lock_pid = getpid(); return; } if (SW_CPU_NUM > 1) { for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1) { for (i = 0; i < n; i++) { sw_atomic_cpu_pause(); } if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { goto _success; } } } /** * The process occupied by the resource no longer exists, * indicating that OOM occurred during the locking process, * forced to unlock */ if (kill(lock_pid, 0) < 0 && errno == ESRCH) { *lock = 1; swoole_warning("lock process[%d] not exists, force unlock", lock_pid); goto _success; } /** * Mark time */ if (t == 0) { t = swoole::time(true); } /** * The deadlock time exceeds 2 seconds (SW_TABLE_FORCE_UNLOCK_TIME), * indicating that the lock process has OOM, * and the PID has been reused, forcing the unlock */ else if ((swoole::time(true) - t) > SW_TABLE_FORCE_UNLOCK_TIME) { *lock = 1; swoole_warning("timeout, force unlock"); goto _success; } std::this_thread::yield(); } } void Table::forward() const { iterator->lock(); for (; iterator->absolute_index < size; iterator->absolute_index++) { TableRow *row = get_by_index(iterator->absolute_index); if (row == nullptr) { continue; } row->lock(); if (row->next == nullptr) { iterator->absolute_index++; memcpy(iterator->current_, row, iterator->row_memory_size_); row->unlock(); iterator->unlock(); return; } else { uint32_t i = 0; TableRow *tmp_row = row; for (;; i++) { if (tmp_row == nullptr) { iterator->collision_index = 0; break; } if (i == iterator->collision_index) { iterator->collision_index++; memcpy(iterator->current_, tmp_row, iterator->row_memory_size_); row->unlock(); iterator->unlock(); return; } tmp_row = tmp_row->next; } } row->unlock(); } sw_memset_zero(iterator->current_, sizeof(TableRow)); iterator->unlock(); } TableRow *Table::get(const char *key, uint16_t keylen, TableRow **rowlock) const { check_key_length(&keylen); TableRow *row = hash(key, keylen); *rowlock = row; row->lock(); for (;;) { if (sw_mem_equal(row->key, row->key_len, key, keylen)) { if (!row->active) { row = nullptr; } break; } else if (row->next == nullptr) { row = nullptr; break; } else { row = row->next; } } return row; } TableRow *Table::set(const char *key, uint16_t keylen, TableRow **rowlock, int *out_flags) { check_key_length(&keylen); TableRow *row = hash(key, keylen); *rowlock = row; row->lock(); int _out_flags = 0; if (row->active) { uint32_t _conflict_level = 1; while (!sw_mem_equal(row->key, row->key_len, key, keylen)) { if (row->next == nullptr) { conflict_count++; if (_conflict_level > conflict_max_level) { conflict_max_level = _conflict_level; } TableRow *new_row = alloc_row(); if (!new_row) { return nullptr; } init_row(new_row, key, keylen); _out_flags |= SW_TABLE_FLAG_NEW_ROW; row->next = new_row; row = new_row; break; } else { row = row->next; _out_flags |= SW_TABLE_FLAG_CONFLICT; _conflict_level++; } } } else { init_row(row, key, keylen); _out_flags |= SW_TABLE_FLAG_NEW_ROW; } if (out_flags) { *out_flags = _out_flags; } if (_out_flags & SW_TABLE_FLAG_NEW_ROW) { sw_atomic_fetch_add(&(insert_count), 1); } else { sw_atomic_fetch_add(&(update_count), 1); } return row; } bool Table::del(const char *key, uint16_t keylen) { check_key_length(&keylen); TableRow *row = hash(key, keylen); // no exists if (!row->active) { return false; } TableRow *tmp, *prev = nullptr; row->lock(); if (row->next == nullptr) { if (sw_mem_equal(row->key, row->key_len, key, keylen)) { row->clear(); } else { goto _not_exists; } } else { tmp = row; while (tmp) { if (sw_mem_equal(tmp->key, tmp->key_len, key, keylen)) { break; } prev = tmp; tmp = tmp->next; } if (tmp == nullptr) { _not_exists: row->unlock(); return false; } // when the deleting element is root, should move the first element's data to root, // and remove the element from the collision list. if (tmp == row) { tmp = tmp->next; row->next = tmp->next; memcpy(row->key, tmp->key, tmp->key_len + 1); row->key_len = tmp->key_len; memcpy(row->data, tmp->data, item_size); } else { prev->next = tmp->next; } free_row(tmp); } sw_atomic_fetch_add(&(delete_count), 1); sw_atomic_fetch_sub(&(row_num), 1); row->unlock(); return true; } TableColumn::TableColumn(const std::string &_name, Type _type, size_t _size) { index = 0; name = _name; type = _type; switch (_type) { case TYPE_INT: size = sizeof(long); break; case TYPE_FLOAT: size = sizeof(double); break; case TYPE_STRING: size = _size + sizeof(TableStringLength); break; default: abort(); break; } } void TableColumn::clear(TableRow *row) const { if (type == TYPE_STRING) { row->set_value(this, nullptr, 0); } else if (type == TYPE_FLOAT) { double _value = 0; row->set_value(this, &_value, 0); } else { long _value = 0; row->set_value(this, &_value, 0); } } void TableRow::set_value(const TableColumn *col, const void *value, size_t vlen) { switch (col->type) { case TableColumn::TYPE_INT: memcpy(data + col->index, value, sizeof(long)); break; case TableColumn::TYPE_FLOAT: memcpy(data + col->index, value, sizeof(double)); break; default: if (vlen > (col->size - sizeof(TableStringLength))) { swoole_warning("[key=%s,field=%s]string value is too long", key, col->name.c_str()); vlen = col->size - sizeof(TableStringLength); } if (value == nullptr) { vlen = 0; } memcpy(data + col->index, &vlen, sizeof(TableStringLength)); if (vlen > 0) { memcpy(data + col->index + sizeof(TableStringLength), value, vlen); } break; } } void TableRow::get_value(const TableColumn *col, double *dval) const { memcpy(dval, data + col->index, sizeof(*dval)); } void TableRow::get_value(const TableColumn *col, long *lval) const { memcpy(lval, data + col->index, sizeof(*lval)); } void TableRow::get_value(const TableColumn *col, char **value, TableStringLength *len) { memcpy(len, data + col->index, sizeof(*len)); *value = data + col->index + sizeof(*len); } } // namespace swoole ================================================ FILE: src/network/address.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include static bool IN_IS_ADDR_LOOPBACK(const in_addr *a) { return a->s_addr == htonl(INADDR_LOOPBACK); } namespace swoole { namespace network { static thread_local char tmp_address[INET6_ADDRSTRLEN]; const char *Address::addr_str(int family, const void *addr) { if (inet_ntop(family, addr, tmp_address, sizeof(tmp_address))) { return tmp_address; } return nullptr; } bool Address::verify_ip(int family, const std::string &str) { return inet_pton(family, str.c_str(), tmp_address) == 1; } bool Address::verify_port(const int port, const bool for_connect) { if (port < 0 || port > 65535) { return false; } if (for_connect && port == 0) { return false; } return true; } const char *Address::get_addr() const { if (Socket::is_inet4(type)) { return addr_str(AF_INET, &addr.inet_v4.sin_addr); } if (Socket::is_inet6(type)) { return addr_str(AF_INET6, &addr.inet_v6.sin6_addr); } if (Socket::is_local(type)) { return addr.un.sun_path; } return "unknown"; } bool Address::empty() const { return type == 0; } int Address::get_port() const { if (Socket::is_inet4(type)) { return ntohs(addr.inet_v4.sin_port); } if (Socket::is_inet6(type)) { return ntohs(addr.inet_v6.sin6_port); } return 0; } void Address::set_port(int _port) { if (Socket::is_inet4(type)) { addr.inet_v4.sin_port = htons(_port); } else if (Socket::is_inet6(type)) { addr.inet_v6.sin6_port = htons(_port); } } bool Address::assign(SocketType _type, const std::string &_host, int _port, bool _resolve_name) { type = _type; const char *host = _host.c_str(); if (!verify_port(_port)) { swoole_set_last_error(SW_ERROR_BAD_PORT); return false; } if (Socket::is_inet4(_type)) { addr.inet_v4.sin_family = AF_INET; addr.inet_v4.sin_port = htons(_port); len = sizeof(addr.inet_v4); if (inet_pton(AF_INET, host, &addr.inet_v4.sin_addr.s_addr) != 1) { if (!_resolve_name) { swoole_set_last_error(SW_ERROR_BAD_HOST_ADDR); return false; } if (gethostbyname(AF_INET, host, reinterpret_cast(&addr.inet_v4.sin_addr)) < 0) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return false; } } } else if (Socket::is_inet6(_type)) { addr.inet_v6.sin6_family = AF_INET6; addr.inet_v6.sin6_port = htons(_port); len = sizeof(addr.inet_v6); if (inet_pton(AF_INET6, host, addr.inet_v6.sin6_addr.s6_addr) != 1) { if (!_resolve_name) { swoole_set_last_error(SW_ERROR_BAD_HOST_ADDR); return false; } if (gethostbyname(AF_INET6, host, reinterpret_cast(&addr.inet_v6.sin6_addr)) < 0) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return false; } } } else if (Socket::is_local(_type)) { if (_host.length() >= sizeof(addr.un.sun_path) - 1) { swoole_set_last_error(SW_ERROR_NAME_TOO_LONG); return false; } addr.un.sun_family = AF_UNIX; swoole_strlcpy(addr.un.sun_path, host, sizeof(addr.un.sun_path)); addr.un.sun_path[sizeof(addr.un.sun_path) - 1] = 0; len = sizeof(addr.un.sun_path); } else { swoole_set_last_error(SW_ERROR_BAD_SOCKET_TYPE); return false; } return true; } const char *Address::type_str(SocketType type) { if (Socket::is_inet4(type)) { return "IPv4"; } if (Socket::is_inet6(type)) { return "IPv6"; } if (Socket::is_local(type)) { return "UnixSocket"; } return "Unknown"; } bool Address::assign(const std::string &url) { static const std::regex unix_pattern(R"(^(unix|udg)://(/[^?#]+))"); static const std::regex inet4_pattern(R"(^(tcp|udp)://([^:\[]+):(\d+)$)"); static const std::regex inet6_pattern(R"(^(tcp|udp)://\[([^\]]+)\]:(\d+)$)"); std::smatch match; if (std::regex_match(url, match, unix_pattern)) { std::string proto = match[1]; std::string path = match[2]; type = proto == "unix" ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM; return assign(type, path, 0); } if (std::regex_match(url, match, inet4_pattern)) { std::string proto = match[1]; std::string host = match[2]; int port = std::stoi(match[3]); type = proto == "tcp" ? SW_SOCK_TCP : SW_SOCK_UDP; return assign(type, host, port); } if (std::regex_match(url, match, inet6_pattern)) { std::string proto = match[1]; std::string host = match[2]; int port = std::stoi(match[3]); type = proto == "tcp" ? SW_SOCK_TCP6 : SW_SOCK_UDP6; return assign(type, host, port); } swoole_error_log(SW_LOG_NOTICE, SW_ERROR_BAD_HOST_ADDR, "Invalid address '%s'", url.c_str()); return false; } bool Address::is_loopback_addr() const { if (Socket::is_inet4(type)) { return IN_IS_ADDR_LOOPBACK(&addr.inet_v4.sin_addr); } if (Socket::is_inet6(type)) { return IN6_IS_ADDR_LOOPBACK(&addr.inet_v6.sin6_addr); } return false; } } // namespace network } // namespace swoole ================================================ FILE: src/network/client.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_timer.h" #include "swoole_protocol.h" #include "swoole_client.h" #include "swoole_proxy.h" #include "swoole_async.h" #include namespace swoole { namespace network { static int Client_inet_addr(Client *cli, const char *host, int port); static int Client_tcp_connect_sync(Client *cli, const char *host, int port, double _timeout, int nonblock); static int Client_tcp_connect_async(Client *cli, const char *host, int port, double timeout, int nonblock); static int Client_udp_connect(Client *cli, const char *host, int port, double _timeout, int udp_connect); static ssize_t Client_tcp_send_sync(Client *cli, const char *data, size_t length, int flags); static ssize_t Client_tcp_send_async(Client *cli, const char *data, size_t length, int flags); static ssize_t Client_udp_send(Client *cli, const char *data, size_t length, int flags); static int Client_tcp_sendfile_sync(Client *cli, const char *filename, off_t offset, size_t length); static int Client_tcp_sendfile_async(Client *cli, const char *filename, off_t offset, size_t length); static ssize_t Client_tcp_recv_sync(Client *cli, char *data, size_t len, int flags); static ssize_t Client_udp_recv(Client *cli, char *data, size_t len, int flags); static int Client_onDgramRead(Reactor *reactor, Event *event); static int Client_onStreamRead(Reactor *reactor, Event *event); static int Client_onWrite(Reactor *reactor, Event *event); static int Client_onError(Reactor *reactor, Event *event); static void Client_onTimeout(Timer *timer, TimerNode *tnode); static void Client_onResolveCompleted(AsyncEvent *event); static int Client_onPackage(const Protocol *proto, Socket *conn, const RecvData *rdata); static sw_inline void execute_onConnect(Client *cli) { if (cli->timer) { swoole_timer_del(cli->timer); cli->timer = nullptr; } cli->onConnect(cli); } void Client::init_reactor(Reactor *reactor) { reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_READ, Client_onStreamRead); reactor->set_handler(SW_FD_DGRAM_CLIENT, SW_EVENT_READ, Client_onDgramRead); reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_WRITE, Client_onWrite); reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_ERROR, Client_onError); } Client::Client(SocketType _type, bool _async) : async(_async) { fd_type = Socket::is_stream(_type) ? SW_FD_STREAM_CLIENT : SW_FD_DGRAM_CLIENT; socket = make_socket(_type, fd_type, (async ? SW_SOCK_NONBLOCK : 0) | SW_SOCK_CLOEXEC); if (socket == nullptr) { swoole_sys_warning("socket() failed"); return; } socket->object = this; input_buffer_size = SW_CLIENT_BUFFER_SIZE; socket->chunk_size = SW_SEND_BUFFER_SIZE; if (socket->is_stream()) { recv_ = Client_tcp_recv_sync; if (async) { connect_ = Client_tcp_connect_async; send_ = Client_tcp_send_async; sendfile_ = Client_tcp_sendfile_async; } else { connect_ = Client_tcp_connect_sync; send_ = Client_tcp_send_sync; sendfile_ = Client_tcp_sendfile_sync; } } else { connect_ = Client_udp_connect; recv_ = Client_udp_recv; send_ = Client_udp_send; } Socket::get_domain_and_type(_type, &sock_domain_, &sock_type_); remote_addr.type = _type; protocol.package_length_type = 'N'; protocol.package_length_size = 4; protocol.package_body_offset = 0; protocol.package_max_length = SW_INPUT_BUFFER_SIZE; protocol.onPackage = Client_onPackage; } int Client::bind(const std::string &addr, int port) const { if (socket->set_reuse_addr() < 0) { swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", socket->get_fd()); } if (socket->bind(addr, port) < 0) { swoole_set_last_error(errno); return SW_ERR; } return SW_OK; } void Client::set_timeout(double timeout, TimeoutType type) const { socket->set_timeout(timeout, type); } bool Client::has_timedout() const { return socket->has_timedout(); } void Client::set_socks5_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { socks5_proxy.reset(Socks5Proxy::create(get_socket_type(), host, port, user, pwd)); } void Client::set_http_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { http_proxy.reset(HttpProxy::create(host, port, user, pwd)); } int Client::sleep() { int ret; if (socket->events & SW_EVENT_WRITE) { ret = swoole_event_set(socket, SW_EVENT_WRITE); } else { ret = swoole_event_del(socket); } if (ret == SW_OK) { sleep_ = true; } return ret; } int Client::wakeup() { int ret; if (socket->events & SW_EVENT_WRITE) { ret = swoole_event_set(socket, SW_EVENT_READ | SW_EVENT_WRITE); } else { ret = swoole_event_add(socket, SW_EVENT_READ); } if (ret == SW_OK) { sleep_ = false; } return ret; } int Client::sendto(const std::string &host, int port, const char *data, size_t len) const { if (!socket->is_dgram()) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); swoole_warning("only supports SWOOLE_SOCK_(UDP/UDP6/UNIX_DGRAM)"); return SW_ERR; } Address tmp_addr; if (!tmp_addr.assign(socket->socket_type, host, port, !async)) { return SW_ERR; } if (socket->sendto(tmp_addr, data, len, 0) < 0) { swoole_set_last_error(errno); return SW_ERR; } return SW_OK; } int Client::get_peer_name(Address *addr) const { if (socket->is_dgram()) { *addr = remote_addr; return SW_OK; } else { return socket->get_peer_name(addr); } } int Client::shutdown(int _how) { if (!socket || closed) { return SW_ERR; } swoole_trace_log(SW_TRACE_CLIENT, "how=%d, fd=%d", _how, socket->fd); if (_how == SHUT_RD) { if (shutdown_read || shutdown_rw || ::shutdown(socket->fd, SHUT_RD)) { return SW_ERR; } else { shutdown_read = true; return SW_OK; } } else if (_how == SHUT_WR) { if (shutdown_write || shutdown_rw || ::shutdown(socket->fd, SHUT_WR) < 0) { return SW_ERR; } else { shutdown_write = true; return SW_OK; } } else if (_how == SHUT_RDWR) { if (shutdown_rw || ::shutdown(socket->fd, SHUT_RDWR) < 0) { return SW_ERR; } else { shutdown_read = true; return SW_OK; } } else { swoole_set_last_error(EINVAL); return SW_ERR; } } bool Client::socks5_handshake(const char *recv_data, size_t length) { auto send_fn = [this](const char *buf, size_t len) { return send(buf, len); }; return socks5_proxy->handshake(recv_data, length, send_fn); } #ifdef SW_SUPPORT_DTLS void Client::enable_dtls() { ssl_context->protocols = SW_SSL_DTLS; socket->dtls = 1; socket->chunk_size = SW_SSL_BUFFER_SIZE; send_ = Client_tcp_send_sync; recv_ = Client_tcp_recv_sync; } #endif int Client::enable_ssl_encrypt() { if (ssl_context) { return SW_ERR; } ssl_context = std::make_shared(); open_ssl = true; #ifdef SW_SUPPORT_DTLS if (socket->is_dgram()) { enable_dtls(); } #else { swoole_warning("DTLS support require openssl-1.1 or later"); return SW_ERR; } #endif return SW_OK; } int Client::ssl_handshake() const { if (socket->ssl_state == SW_SSL_STATE_READY) { return SW_ERR; } if (!ssl_context->ready()) { ssl_context->http_v2 = http2; if (!ssl_context->create()) { return SW_ERR; } } if (!socket->ssl) { socket->ssl_send_ = 1; if (socket->ssl_create(ssl_context.get(), SW_SSL_CLIENT) < 0) { swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return SW_ERR; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (!ssl_context->tls_host_name.empty()) { SSL_set_tlsext_host_name(socket->ssl, ssl_context->tls_host_name.c_str()); } #endif } if (socket->ssl_connect() < 0) { return SW_ERR; } if (socket->ssl_state == SW_SSL_STATE_READY && ssl_context->verify_peer) { if (ssl_verify(ssl_context->allow_self_signed) < 0) { return SW_ERR; } } return SW_OK; } int Client::ssl_verify(int allow_self_signed) const { if (!socket->ssl_verify(allow_self_signed)) { return SW_ERR; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (!ssl_context->tls_host_name.empty() && !socket->ssl_check_host(ssl_context->tls_host_name.c_str())) { return SW_ERR; } #endif return SW_OK; } static int Client_inet_addr(Client *cli, const char *host, int port) { if (!cli->host_preseted) { // enable socks5 proxy if (cli->socks5_proxy) { cli->socks5_proxy->target_host = host; cli->socks5_proxy->target_port = port; host = cli->socks5_proxy->host.c_str(); port = cli->socks5_proxy->port; } // enable http proxy if (cli->http_proxy) { cli->http_proxy->target_host = host; cli->http_proxy->target_port = port; host = cli->http_proxy->host.c_str(); port = cli->http_proxy->port; } cli->server_host = host; cli->server_port = port; cli->host_preseted = true; } if (!cli->server_addr.assign(cli->socket->socket_type, host, port, !cli->async)) { if (!cli->dns_completed && swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { cli->wait_dns = true; } else { return SW_ERR; } } return SW_OK; } Client::~Client() { if (!socket) { return; } assert(socket->fd != 0); // remove from reactor if (!closed) { close(); } // clear buffer if (buffer) { delete buffer; buffer = nullptr; } if (async) { // The object must be set to empty here, indicating that the client has been released in the onClose callback socket->object = nullptr; socket->free(); } else { delete socket; } } /** * There are two situations, 1 While maintaining the connection, the user actively calls the close function to close the * connection. 2 Received readable or writable, close event, and executed the close function at the underlying level */ int Client::close() { if (socket == nullptr || closed) { return SW_ERR; } closed = true; auto _socket = socket; if (open_ssl && ssl_context) { if (socket->ssl) { socket->ssl_close(); } } if (socket->socket_type == SW_SOCK_UNIX_DGRAM) { unlink(socket->info.addr.un.sun_path); } if (async) { // remove from reactor if (!socket->removed) { swoole_event_del(socket); } if (timer) { swoole_timer_del(timer); timer = nullptr; } // execute `onClose` callback if (active) { active = false; // In the `onClose` callback, the destructor may be executed and the C++ object will be released onClose(this); } } else { active = false; } if (_socket->object == nullptr) { return SW_OK; } // Set `socket->fd` to -1 to prevent duplicate closure of file descriptors const int fd = socket->fd; socket->fd = -1; swoole_trace_log(SW_TRACE_CLIENT, "fd=%d", fd); return ::close(fd); } static int Client_tcp_connect_sync(Client *cli, const char *host, int port, double timeout, int nonblock) { cli->set_timeout(timeout); if (timeout > 0) { cli->socket->set_kernel_timeout(timeout); } if (Client_inet_addr(cli, host, port) < 0) { return SW_ERR; } if (nonblock) { auto rc = cli->socket->connect_async(cli->server_addr); if (rc == SW_READY) { return SW_OK; } if (rc == SW_WAIT) { cli->async_connect = true; } return SW_ERR; } int ret = cli->socket->connect_sync(cli->server_addr); if (ret >= 0) { cli->active = true; auto recv_buf = sw_tg_buffer(); if (cli->socks5_proxy) { const auto ctx = cli->socks5_proxy.get(); const auto len = ctx->pack_negotiate_request(); if (cli->send(ctx->buf, len) < 0) { return SW_ERR; } ctx->state = SW_SOCKS5_STATE_HANDSHAKE; while (true) { const ssize_t n = cli->recv(recv_buf->str, recv_buf->size, 0); if (n > 0 && cli->socks5_handshake(recv_buf->str, n)) { if (cli->socks5_proxy->state == SW_SOCKS5_STATE_READY) { break; } continue; } return SW_ERR; } } else if (cli->http_proxy) { auto target_host = cli->get_http_proxy_host_name(); const size_t n_write = cli->http_proxy->pack(recv_buf, target_host); if (cli->send(recv_buf->str, n_write, 0) < 0) { return SW_ERR; } const ssize_t n_read = cli->recv(recv_buf->str, recv_buf->size, 0); if (n_read <= 0) { return SW_ERR; } recv_buf->length = n_read; if (!cli->http_proxy->handshake(recv_buf)) { return SW_ERR; } } if (cli->open_ssl && cli->ssl_handshake() < 0) { return SW_ERR; } } return ret; } static int Client_dns_lookup(Client *cli) { AsyncEvent ev{}; auto req = new GethostbynameRequest(cli->server_host, cli->sock_domain_); ev.data = std::shared_ptr(req); ev.object = cli; ev.handler = async::handler_gethostbyname; ev.callback = Client_onResolveCompleted; return async::dispatch(&ev) == nullptr ? SW_ERR : SW_OK; } static int Client_tcp_connect_async(Client *cli, const char *host, int port, double timeout, int nonblock) { int ret; if (!(cli->onConnect && cli->onError && cli->onClose && cli->onReceive)) { swoole_warning("onConnect/onError/onReceive/onClose callback have not set"); return SW_ERR; } cli->set_timeout(timeout); if (!cli->buffer) { cli->buffer = new String(cli->input_buffer_size); } if (cli->onBufferFull && cli->buffer_high_watermark == 0) { cli->buffer_high_watermark = cli->socket->buffer_size * 0.8; } if (Client_inet_addr(cli, host, port) < 0) { return SW_ERR; } if (cli->wait_dns) { return Client_dns_lookup(cli); } do { ret = cli->socket->connect(cli->server_addr); if (ret < 0 && errno == EINTR) { continue; } if ((ret < 0 && errno == EINPROGRESS) || ret == 0) { /** * A return value of 0 indicates that the connection has been successfully established, * and there is no need to add a timer to check for connection timeouts. */ if (ret < 0 && timeout > 0) { cli->timer = swoole_timer_add(timeout, false, Client_onTimeout, cli); if (!cli->timer) { return SW_ERR; } } /** * Regardless of whether the connection has been successfully established or is still in progress, * listen for writable events to handle the proxy and SSL handshake within those events. */ return swoole_event_add(cli->socket, SW_EVENT_WRITE); } cli->active = false; cli->socket->removed = 1; cli->close(); if (cli->onError) { cli->onerror_called = true; cli->onError(cli); } break; } while (true); return ret; } static ssize_t Client_tcp_send_async(Client *cli, const char *data, size_t length, int flags) { ssize_t n = length; if (swoole_event_write(cli->socket, data, length) < 0) { if (swoole_get_last_error() == SW_ERROR_OUTPUT_BUFFER_OVERFLOW) { n = -1; cli->high_watermark = true; } else { return SW_ERR; } } if (cli->onBufferFull && !cli->high_watermark && cli->socket->get_out_buffer_length() >= cli->buffer_high_watermark) { cli->high_watermark = true; cli->onBufferFull(cli); } return n; } static ssize_t Client_tcp_send_sync(Client *cli, const char *data, size_t length, int flags) { return cli->socket->send_sync(data, length, flags); } static int Client_tcp_sendfile_sync(Client *cli, const char *filename, off_t offset, size_t length) { if (cli->socket->sendfile_sync(filename, offset, length) < 0) { swoole_set_last_error(errno); return SW_ERR; } return SW_OK; } static int Client_tcp_sendfile_async(Client *cli, const char *filename, off_t offset, size_t length) { if (cli->socket->sendfile_async(filename, offset, length) < 0) { swoole_set_last_error(errno); return SW_ERR; } if (swoole_event_add_or_update(cli->socket, SW_EVENT_WRITE) == SW_ERR) { return SW_ERR; } return SW_OK; } static ssize_t Client_tcp_recv_sync(Client *cli, char *data, size_t len, int flags) { auto rv = cli->socket->recv_sync(data, len, flags); /** * To maintain forward compatibility, the recv system call returns EAGAIN after a timeout, * while the poll() function returns 0 on timeout without setting errno, * which should be set to either EAGAIN or ETIMEDOUT. */ if (rv == -1 && swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT) { errno = EAGAIN; } return rv; } static int Client_udp_connect(Client *cli, const char *host, int port, double timeout, int udp_connect) { cli->set_timeout(timeout); if (!cli->async && timeout > 0) { cli->socket->set_kernel_timeout(timeout); } cli->sock_flags_ = udp_connect; if (Client_inet_addr(cli, host, port) < 0) { return SW_ERR; } if (cli->async && !cli->onReceive) { swoole_warning("`onReceive` callback have not set"); return SW_ERR; } if (cli->wait_dns && cli->async) { /** * Domain name resolution is required, and UDP connect cannot return immediately. * If an `onError` callback is not set, the caller will not receive any notification in case of a resolution * failure. Therefore, it is essential to set the onError callback; otherwise, an error will be returned * immediately. */ if (!cli->onError) { swoole_warning("`onError` callback have not set"); return SW_ERR; } return Client_dns_lookup(cli); } cli->active = true; cli->socket->set_buffer_size(Socket::default_buffer_size); if (timeout > 0) { cli->socket->set_timeout(timeout); } if (cli->socket->socket_type == SW_SOCK_UNIX_DGRAM) { sockaddr_un *client_addr = &cli->socket->info.addr.un; sprintf(client_addr->sun_path, "/tmp/swoole-client.%d.%d.sock", getpid(), cli->socket->fd); client_addr->sun_family = AF_UNIX; unlink(client_addr->sun_path); if (bind(cli->socket->fd, reinterpret_cast(client_addr), sizeof(cli->socket->info.addr.un)) < 0) { swoole_sys_warning("bind(%s) failed", client_addr->sun_path); return SW_ERR; } } if (cli->open_ssl) #ifdef SW_SUPPORT_DTLS { udp_connect = 1; cli->enable_dtls(); } #else { swoole_warning("DTLS support require openssl-1.1 or later"); return SW_ERR; } #endif if (udp_connect != 1) { goto _connect_ok; } if (cli->socket->connect(cli->server_addr) == 0) { cli->socket->clean(); _connect_ok: if (cli->async && cli->onConnect) { if (swoole_event_add(cli->socket, SW_EVENT_READ) < 0) { return SW_ERR; } execute_onConnect(cli); } if (cli->open_ssl && cli->ssl_handshake() < 0) { return SW_ERR; } return SW_OK; } else { cli->active = false; cli->socket->removed = 1; cli->close(); if (cli->async && cli->onError) { cli->onerror_called = true; cli->onError(cli); } return SW_ERR; } } static ssize_t Client_udp_send(Client *cli, const char *data, size_t len, int flags) { ssize_t n = cli->socket->sendto(cli->server_addr, data, len, 0); if (n < 0 || n < (ssize_t) len) { return SW_ERR; } else { return n; } } static ssize_t Client_udp_recv(Client *cli, char *data, size_t length, int flags) { if (cli->async) { return cli->socket->recvfrom_sync(data, length, flags, &cli->remote_addr); } else { return cli->socket->recvfrom(data, length, flags, &cli->remote_addr); } } static int Client_onPackage(const Protocol *proto, Socket *conn, const RecvData *rdata) { auto *cli = static_cast(conn->object); cli->onReceive(cli, rdata->data, rdata->info.len); return conn->close_wait ? SW_ERR : SW_OK; } static int Client_onStreamRead(Reactor *reactor, Event *event) { ssize_t n; auto *cli = (Client *) event->socket->object; char *buf = cli->buffer->str + cli->buffer->length; ssize_t buf_size = cli->buffer->size - cli->buffer->length; bool do_ssl_handshake = cli->open_ssl; if (cli->http_proxy && cli->http_proxy->state != SW_HTTP_PROXY_STATE_READY) { n = event->socket->recv(buf, buf_size, 0); if (n <= 0) { swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); _connect_fail: cli->active = false; cli->close(); if (cli->onError) { cli->onError(cli); } return SW_OK; } cli->buffer->length += n; if (!cli->http_proxy->handshake(cli->buffer)) { swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); goto _connect_fail; } cli->http_proxy->state = SW_HTTP_PROXY_STATE_READY; cli->buffer->clear(); if (!do_ssl_handshake) { execute_onConnect(cli); return SW_OK; } } if (cli->socks5_proxy && cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { n = event->socket->recv(buf, buf_size, 0); if (n <= 0) { swoole_set_last_error(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); goto _connect_fail; } cli->buffer->length += n; if (!cli->socks5_handshake(buf, buf_size)) { swoole_set_last_error(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); goto _connect_fail; } if (cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { return SW_OK; } cli->buffer->clear(); if (!do_ssl_handshake) { execute_onConnect(cli); return SW_OK; } } if (cli->open_ssl && cli->socket->ssl_state != SW_SSL_STATE_READY) { if (cli->ssl_handshake() < 0) { swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } if (cli->socket->ssl_state != SW_SSL_STATE_READY) { return SW_OK; } else { execute_onConnect(cli); return SW_OK; } } if (cli->open_eof_check || cli->open_length_check) { Socket *conn = cli->socket; Protocol *protocol = &cli->protocol; if (cli->open_eof_check) { n = protocol->recv_with_eof_protocol(conn, cli->buffer); } else { n = protocol->recv_with_length_protocol(conn, cli->buffer); } if (n < 0) { if (!cli->closed) { cli->close(); } return SW_OK; } else { if (conn->removed == 0 && cli->remove_delay) { cli->sleep(); cli->remove_delay = false; } return SW_OK; } } n = event->socket->recv(buf, buf_size, 0); if (n < 0) { switch (event->socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("Read from socket[%d] failed", event->fd); return SW_OK; case SW_CLOSE: goto __close; case SW_WAIT: return SW_OK; default: return SW_OK; } } else if (n == 0) { __close: return cli->close(); } else { cli->onReceive(cli, buf, n); return SW_OK; } return SW_OK; } static int Client_onDgramRead(Reactor *reactor, Event *event) { auto *cli = (Client *) event->socket->object; char buffer[SW_BUFFER_SIZE_UDP]; int n = Client_udp_recv(cli, buffer, sizeof(buffer), 0); if (n < 0) { return SW_ERR; } else { cli->onReceive(cli, buffer, n); } return SW_OK; } static int Client_onError(Reactor *reactor, Event *event) { auto *cli = (Client *) event->socket->object; if (cli->active) { return cli->close(); } else { Client_onWrite(reactor, event); } return SW_OK; } static void Client_onTimeout(Timer *timer, TimerNode *tnode) { auto *cli = (Client *) tnode->data; swoole_set_last_error(ETIMEDOUT); cli->timer = nullptr; if (cli->open_ssl && cli->socket->ssl_state != SW_SSL_STATE_READY) { cli->active = false; } if (cli->socks5_proxy && cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { cli->active = false; } else if (cli->http_proxy && cli->http_proxy->state != SW_HTTP_PROXY_STATE_READY) { cli->active = false; } cli->close(); if (cli->onError) { cli->onError(cli); } } static void Client_onResolveCompleted(AsyncEvent *event) { auto *req = dynamic_cast(event->data.get()); auto *cli = static_cast(event->object); cli->wait_dns = false; cli->dns_completed = true; if (event->error == 0) { /** * In the callback function, the application layer cannot obtain the return value of the connect function, * so it must call `onError` to notify the caller. */ double timeout = cli->socket->get_timeout(SW_TIMEOUT_CONNECT); if (cli->connect(req->addr.c_str(), cli->server_port, timeout, cli->sock_flags_) == SW_ERR && !cli->onerror_called) { goto _error; } } else { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); _error: cli->socket->removed = 1; cli->close(); if (cli->onError) { cli->onerror_called = true; cli->onError(cli); } } } static int Client_onWrite(Reactor *reactor, Event *event) { auto *cli = (Client *) event->socket->object; Socket *_socket = cli->socket; int ret; int err; if (cli->active) { if (cli->open_ssl && _socket->ssl_state == SW_SSL_STATE_WAIT_STREAM) { if (cli->ssl_handshake() < 0) { swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } else if (_socket->ssl_state == SW_SSL_STATE_READY) { goto _connect_success; } else { if (_socket->ssl_want_read) { swoole_event_set(event->socket, SW_EVENT_READ); } return SW_OK; } } if (Reactor::_writable_callback(reactor, event) < 0) { return SW_ERR; } if (cli->onBufferEmpty && cli->high_watermark && _socket->get_out_buffer_length() <= cli->buffer_low_watermark) { cli->high_watermark = false; cli->onBufferEmpty(cli); } return SW_OK; } ret = _socket->get_option(SOL_SOCKET, SO_ERROR, &err); swoole_set_last_error(err); if (ret < 0) { swoole_sys_warning("getsockopt(%d) failed", event->fd); return SW_ERR; } // success if (swoole_get_last_error() == 0) { // listen read event swoole_event_set(event->socket, SW_EVENT_READ); // connected cli->active = true; // socks5 proxy if (cli->socks5_proxy && cli->socks5_proxy->state == SW_SOCKS5_STATE_WAIT) { const auto len = cli->socks5_proxy->pack_negotiate_request(); cli->socks5_proxy->state = SW_SOCKS5_STATE_HANDSHAKE; return cli->send(cli->socks5_proxy->buf, len, 0); } // http proxy if (cli->http_proxy && cli->http_proxy->state == SW_HTTP_PROXY_STATE_WAIT) { auto proxy_buf = sw_tg_buffer(); auto host_name = cli->get_http_proxy_host_name(); size_t n = cli->http_proxy->pack(proxy_buf, host_name); swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy request: <str); return cli->send(proxy_buf->str, n, 0); } if (cli->open_ssl) { if (cli->ssl_handshake() < 0) { swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } else { _socket->ssl_state = SW_SSL_STATE_WAIT_STREAM; } return SW_OK; } _connect_success: execute_onConnect(cli); } else { _connect_fail: cli->active = false; cli->close(); cli->onError(cli); } return SW_OK; } } // namespace network } // namespace swoole ================================================ FILE: src/network/dns.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" #include "swoole_coroutine_api.h" #include "swoole_util.h" #include #include #include #include #include #include #define SW_PATH_HOSTS "/etc/hosts" #ifdef SW_USE_CARES #include #endif using swoole::NameResolver; using swoole::coroutine::System; using swoole::network::Address; SW_API bool swoole_load_resolv_conf() { FILE *fp; char line[100]; char buf[16] = {}; if ((fp = fopen(SwooleG.dns_resolvconf_path.c_str(), "rt")) == nullptr) { swoole_sys_warning("fopen(%s) failed", SwooleG.dns_resolvconf_path.c_str()); return false; } while (fgets(line, 100, fp)) { if (strncmp(line, "nameserver", 10) == 0) { strcpy(buf, strtok(line, " ")); strcpy(buf, strtok(nullptr, "\n")); break; } } fclose(fp); if (strlen(buf) == 0) { return false; } swoole_set_dns_server(buf); return true; } SW_API void swoole_set_dns_server(const std::string &server) { char *_port; int dns_server_port = SW_DNS_SERVER_PORT; char dns_server_host[32]; strcpy(dns_server_host, server.c_str()); if ((_port = strchr(const_cast(server.c_str()), ':'))) { dns_server_port = sw_atoi(_port + 1); if (!Address::verify_port(dns_server_port, true)) { dns_server_port = SW_DNS_SERVER_PORT; } dns_server_host[_port - server.c_str()] = '\0'; } SwooleG.dns_server.host = dns_server_host; SwooleG.dns_server.port = dns_server_port; } SW_API swoole::DnsServer swoole_get_dns_server() { if (SwooleG.dns_server.host.empty()) { swoole_load_resolv_conf(); } return SwooleG.dns_server; } SW_API void swoole_set_hosts_path(const std::string &hosts_file) { SwooleG.dns_hosts_path = hosts_file; } SW_API void swoole_name_resolver_add(const NameResolver &resolver, bool append) { if (append) { SwooleG.name_resolvers.push_back(resolver); } else { SwooleG.name_resolvers.push_front(resolver); } } SW_API void swoole_name_resolver_each( const std::function::iterator &iter)> &fn) { for (auto iter = SwooleG.name_resolvers.begin(); iter != SwooleG.name_resolvers.end(); ++iter) { const swTraverseOperation op = fn(iter); if (op == SW_TRAVERSE_REMOVE) { SwooleG.name_resolvers.erase(iter++); } else if (op == SW_TRAVERSE_STOP) { break; } } } SW_API std::string swoole_name_resolver_lookup(const std::string &host_name, NameResolver::Context *ctx) { if (SwooleG.name_resolvers.empty()) { goto _dns_lookup; } for (auto &name_resolver : SwooleG.name_resolvers) { std::string result = name_resolver.resolve(host_name, ctx, name_resolver.private_data); if (!result.empty() || ctx->final_) { return result; } } _dns_lookup: /* * Use DNS to resolve host name by default */ if (swoole_coroutine_is_in()) { return System::gethostbyname(host_name, ctx->type, ctx->timeout); } else { return swoole::network::gethostbyname(ctx->type, host_name); } } namespace swoole { namespace coroutine { enum RecordType { SW_DNS_A_RECORD = 0x01, // Lookup IPv4 address SW_DNS_AAAA_RECORD = 0x1c, // Lookup IPv6 address SW_DNS_MX_RECORD = 0x0f // Lookup mail server for domain }; enum DNSError { SW_DNS_NOT_EXIST, // Error: address does not exist SW_DNS_TIMEOUT, // Lookup time expired SW_DNS_ERROR // No memory or other error }; /* Struct for the DNS Header */ struct RecordHeader { uint16_t id; uchar rd : 1; uchar tc : 1; uchar aa : 1; uchar opcode : 4; uchar qr : 1; uchar rcode : 4; uchar z : 3; uchar ra : 1; uint16_t qdcount; uint16_t ancount; uint16_t nscount; uint16_t arcount; }; /* Struct for the flags for the DNS Question */ struct Q_FLAGS { uint16_t qtype; uint16_t qclass; }; /* Struct for the flags for the DNS RRs */ struct RR_FLAGS { uint16_t type; uint16_t rdclass; uint32_t ttl; uint16_t rdlength; }; static uint16_t dns_request_id = 1; static int domain_encode(const char *src, int n, char *dest); static void domain_decode(char *str); static std::string parse_ip_address(void *vaddr, int type); std::string get_ip_by_hosts(const std::string &search_domain) { std::ifstream file(SwooleG.dns_hosts_path.empty() ? SW_PATH_HOSTS : SwooleG.dns_hosts_path); if (!file.is_open()) { return ""; } std::string line; std::string domain; std::string txtaddr; std::vector domains; std::unordered_map result{}; while (getline(file, line)) { std::string::size_type ops = line.find_first_of('#'); if (ops != std::string::npos) { line[ops] = '\0'; } if (line[0] == '\n' || line[0] == '\0' || line[0] == '\r') { continue; } std::istringstream stream(line); while (stream >> domain) { domains.push_back(domain); } if (domains.empty() || domains.size() == 1) { domains.clear(); continue; } txtaddr = domains[0]; for (size_t i = 1; i < domains.size(); i++) { result.insert(std::make_pair(domains[i], txtaddr)); } auto iter = result.find(search_domain); if (iter != result.end()) { return iter->second; } else { result.clear(); domains.clear(); continue; } } return ""; } static std::string parse_ip_address(void *vaddr, int type) { auto addr = static_cast(vaddr); std::string ip_addr; if (type == AF_INET) { char buff[4 * 4 + 3 + 1]; sw_snprintf(buff, sizeof(buff), "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]); return ip_addr.assign(buff); } else if (type == AF_INET6) { for (int i = 0; i < 16; i += 2) { if (i > 0) { ip_addr.append(":"); } char buf[4 + 1]; size_t n = sw_snprintf(buf, sizeof(buf), "%02x%02x", addr[i], addr[i + 1]); ip_addr.append(buf, n); } } else { assert(0); } return ip_addr; } std::vector dns_lookup_impl_with_socket(const char *domain, int family, double timeout) { Q_FLAGS *qflags = nullptr; char packet[SW_BUFFER_SIZE_STD]; RecordHeader *header = nullptr; int steps = 0; std::vector result; if (SwooleG.dns_server.host.empty() && !swoole_load_resolv_conf()) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_NO_SERVER); return result; } header = reinterpret_cast(packet); int _request_id = dns_request_id++; header->id = htons(_request_id); header->qr = 0; header->opcode = 0; header->aa = 0; header->tc = 0; header->rd = 1; header->ra = 0; header->z = 0; header->rcode = 0; header->qdcount = htons(1); header->ancount = 0x0000; header->nscount = 0x0000; header->arcount = 0x0000; steps = sizeof(RecordHeader); char *_domain_name = &packet[steps]; const int len = strlen(domain); if (domain_encode(domain, len, _domain_name) < 0) { swoole_warning("invalid domain[%s]", domain); return result; } steps += (strlen((const char *) _domain_name) + 1); qflags = reinterpret_cast(&packet[steps]); qflags->qtype = htons(family == AF_INET6 ? SW_DNS_AAAA_RECORD : SW_DNS_A_RECORD); qflags->qclass = htons(0x0001); steps += sizeof(Q_FLAGS); Socket _sock(SW_SOCK_UDP); if (timeout > 0) { _sock.set_timeout(timeout); } if (!_sock.sendto(SwooleG.dns_server.host, SwooleG.dns_server.port, (char *) packet, steps)) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return result; } /** * response */ header = nullptr; qflags = nullptr; RR_FLAGS *rrflags = nullptr; uchar rdata[10][254]; uint32_t type[10]; sw_memset_zero(rdata, sizeof(rdata)); steps = 0; char name[10][254]; int i; auto ret = _sock.recv(packet, sizeof(packet) - 1); if (ret <= 0) { swoole_set_last_error(_sock.errCode == ECANCELED ? SW_ERROR_CO_CANCELED : SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return result; } packet[ret] = 0; header = reinterpret_cast(packet); steps = sizeof(RecordHeader); _domain_name = &packet[steps]; domain_decode(_domain_name); steps = steps + (strlen(_domain_name) + 2); qflags = reinterpret_cast(&packet[steps]); (void) qflags; steps = steps + sizeof(Q_FLAGS); int ancount = ntohs(header->ancount); if (ancount > 10) { ancount = 10; } /* Parsing the RRs from the reply packet */ for (i = 0; i < ancount; ++i) { type[i] = 0; /* Parsing the NAME portion of the RR */ char *temp = &packet[steps]; int j = 0; while (*temp != 0) { if ((uchar) (*temp) == 0xc0) { ++temp; temp = &packet[(uint8_t) *temp]; } else { name[i][j] = *temp; ++j; ++temp; } } name[i][j] = '\0'; domain_decode(name[i]); steps = steps + 2; /* Parsing the RR flags of the RR */ rrflags = (RR_FLAGS *) &packet[steps]; steps = steps + sizeof(RR_FLAGS) - 2; /* Parsing the IPv4 address in the RR */ type[i] = ntohs(rrflags->type); for (j = 0; j < ntohs(rrflags->rdlength); ++j) { rdata[i][j] = (uchar) packet[steps + j]; } /* Parsing the canonical name in the RR */ if (ntohs(rrflags->type) == 5) { temp = &packet[steps]; j = 0; while (*temp != 0) { if ((uchar) (*temp) == 0xc0) { ++temp; temp = &packet[(uint8_t) *temp]; } else { rdata[i][j] = *temp; ++j; ++temp; } } rdata[i][j] = '\0'; domain_decode((char *) rdata[i]); type[i] = ntohs(rrflags->type); } steps = steps + ntohs(rrflags->rdlength); } int request_id = ntohs(header->id); // bad response if (request_id != _request_id) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return result; } for (i = 0; i < ancount; i++) { if (type[i] != SW_DNS_A_RECORD && type[i] != SW_DNS_AAAA_RECORD) { continue; } result.push_back(parse_ip_address(rdata[i], type[i] == SW_DNS_A_RECORD ? AF_INET : AF_INET6)); } if (result.empty()) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); } return result; } /** * The function converts the dot-based hostname into the DNS format * (i.e. www.apple.com into 3www5apple3com0) */ static int domain_encode(const char *src, int n, char *dest) { if (src[n] == '.') { return SW_ERR; } int pos = 0; int len = 0; memcpy(dest + 1, src, n + 1); dest[n + 1] = '.'; dest[n + 2] = 0; src = dest + 1; n++; for (int i = 0; i < n; i++) { if (src[i] == '.') { len = i - pos; dest[pos] = len; pos += len + 1; } } dest[pos] = 0; return SW_OK; } /** * This function converts a DNS-based hostname into dot-based format * (i.e. 3www5apple3com0 into www.apple.com) */ static void domain_decode(char *str) { size_t i; for (i = 0; i < strlen(str); i++) { uint32_t len = str[i]; for (size_t j = 0; j < len; j++) { str[i] = str[i + 1]; i++; } str[i] = '.'; } str[i - 1] = '\0'; } #ifdef SW_USE_CARES struct ResolvContext { ares_channel channel; ares_options ares_opts; int ares_flags; int error; bool completed; Coroutine *co; std::shared_ptr defer_task_cancelled; std::unordered_map sockets; std::vector result; }; std::vector dns_lookup_impl_with_cares(const char *domain, int family, double timeout) { if (!swoole_event_isset_handler(SW_FD_CARES, SW_EVENT_READ)) { ares_library_init(ARES_LIB_INIT_ALL); swoole_event_set_handler(SW_FD_CARES, SW_EVENT_READ, [](Reactor *reactor, Event *event) -> int { auto ctx = static_cast(event->socket->object); swoole_trace_log(SW_TRACE_CARES, "[event callback] readable event, fd=%d", event->socket->fd); ares_process_fd(ctx->channel, event->fd, ARES_SOCKET_BAD); return SW_OK; }); swoole_event_set_handler(SW_FD_CARES, SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { auto ctx = static_cast(event->socket->object); swoole_trace_log(SW_TRACE_CARES, "[event callback] writable event, fd=%d", event->socket->fd); ares_process_fd(ctx->channel, ARES_SOCKET_BAD, event->fd); return SW_OK; }); sw_reactor()->add_destroy_callback([](void *_data) { ares_library_cleanup(); }, nullptr); } ResolvContext ctx{}; Coroutine *co = Coroutine::get_current_safe(); ctx.co = co; ctx.completed = false; ctx.defer_task_cancelled = std::make_shared(false); char lookups[] = "fb"; int res; ctx.ares_opts.lookups = lookups; ctx.ares_opts.timeout = timeout * 1000; ctx.ares_opts.tries = SwooleG.dns_tries; ctx.ares_opts.sock_state_cb_data = &ctx; ctx.ares_opts.sock_state_cb = [](void *arg, int fd, int readable, int writable) { auto ctx = static_cast(arg); int events = 0; if (readable) { events |= SW_EVENT_READ; } if (writable) { events |= SW_EVENT_WRITE; } swoole_trace_log(SW_TRACE_CARES, "[sock_state_cb], fd=%d, readable=%d, writable=%d", fd, readable, writable); network::Socket *_socket = nullptr; if (ctx->sockets.find(fd) == ctx->sockets.end()) { if (events == 0) { swoole_warning("error events, fd=%d", fd); return; } _socket = make_socket(fd, SW_FD_CARES); _socket->object = ctx; ctx->sockets[fd] = _socket; } else { _socket = ctx->sockets[fd]; if (events == 0) { swoole_trace_log(SW_TRACE_CARES, "[del event], fd=%d", fd); swoole_event_del(_socket); _socket->fd = -1; _socket->free(); ctx->sockets.erase(fd); return; } } if (_socket->events) { swoole_event_set(_socket, events); swoole_trace_log(SW_TRACE_CARES, "[set event] fd=%d, events=%d", fd, events); } else { swoole_event_add(_socket, events); swoole_trace_log(SW_TRACE_CARES, "[add event] fd=%d, events=%d", fd, events); } }; ctx.ares_flags = ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB | ARES_OPT_LOOKUPS; if ((res = ares_init_options(&ctx.channel, &ctx.ares_opts, ctx.ares_flags)) != ARES_SUCCESS) { swoole_warning("ares_init_options() failed, Error: %s[%d]", ares_strerror(res), res); goto _return; } if (!SwooleG.dns_server.host.empty()) { #if (ARES_VERSION >= 0x010b00) struct ares_addr_port_node servers; servers.family = AF_INET; servers.next = nullptr; inet_pton(AF_INET, SwooleG.dns_server.host.c_str(), &servers.addr.addr4); servers.tcp_port = 0; servers.udp_port = SwooleG.dns_server.port; ares_set_servers_ports(ctx.channel, &servers); #elif (ARES_VERSION >= 0x010701) struct ares_addr_node servers; servers.family = AF_INET; servers.next = nullptr; inet_pton(AF_INET, SwooleG.dns_server_host.c_str(), &servers.addr.addr4); ares_set_servers(ctx.channel, &servers); if (SwooleG.dns_server_port != SW_DNS_SERVER_PORT) { swoole_warning("not support to set port of dns server"); } #else swoole_warning("not support to set dns server"); #endif } ares_gethostbyname( ctx.channel, domain, family, [](void *data, int status, int timeouts, struct hostent *hostent) { auto ctx = static_cast(data); swoole_trace_log(SW_TRACE_CARES, "[cares callback] status=%d, timeouts=%d", status, timeouts); if (timeouts > 0) { ctx->error = SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT; goto _resume; } if (status != ARES_SUCCESS) { ctx->error = status; goto _resume; } if (hostent->h_addr_list) { char **paddr = hostent->h_addr_list; while (*paddr != nullptr) { ctx->result.emplace_back(parse_ip_address(*paddr, hostent->h_addrtype)); paddr++; } } _resume: if (ctx->co && ctx->co->is_suspending()) { auto _cancelled = ctx->defer_task_cancelled; swoole_event_defer( [_cancelled](void *data) { if (*_cancelled) { return; } auto *co = static_cast(data); co->resume(); }, ctx->co); ctx->co = nullptr; } else { ctx->completed = true; } }, &ctx); if (ctx.error || ctx.completed) { goto _destroy; } co->yield_ex(timeout); if (co->is_canceled()) { ares_cancel(ctx.channel); } else if (co->is_timedout()) { ares_process_fd(ctx.channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); ctx.error = ARES_ETIMEOUT; } else { swoole_trace_log(SW_TRACE_CARES, "lookup success, result_count=%lu", ctx.result.size()); } _destroy: if (ctx.error) { switch (ctx.error) { case ARES_ECANCELLED: swoole_set_last_error(SW_ERROR_CO_CANCELED); break; case ARES_ETIMEOUT: swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT); break; default: swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); break; } } *ctx.defer_task_cancelled = true; ares_destroy(ctx.channel); _return: return ctx.result; } #endif std::vector dns_lookup(const char *domain, int family, double timeout) { family = family == AF_INET6 ? AF_INET6 : AF_INET; // only support IPv4 and IPv6 #ifdef SW_USE_CARES return dns_lookup_impl_with_cares(domain, family, timeout); #else return dns_lookup_impl_with_socket(domain, family, timeout); #endif } } // namespace coroutine /** * blocking-IO, Use in synchronous mode or AIO thread pool */ namespace network { #ifndef HAVE_GETHOSTBYNAME2_R #include static std::mutex g_gethostbyname2_lock; #endif #ifdef HAVE_GETHOSTBYNAME2_R int gethostbyname(int flags, const char *name, char *addr) { int _af = flags & (~SW_DNS_LOOKUP_RANDOM); int index = 0; int rc, err; int buf_len = 256; hostent hbuf{}; hostent *result; char *buf = static_cast(sw_malloc(buf_len)); if (!buf) { return SW_ERR; } memset(buf, 0, buf_len); while ((rc = ::gethostbyname2_r(name, _af, &hbuf, buf, buf_len, &result, &err)) == ERANGE) { buf_len *= 2; char *tmp = static_cast(sw_realloc(buf, buf_len)); if (nullptr == tmp) { sw_free(buf); return SW_ERR; } else { buf = tmp; } } if (0 != rc || nullptr == result) { sw_free(buf); return SW_ERR; } union { char v4[INET_ADDRSTRLEN]; char v6[INET6_ADDRSTRLEN]; } addr_list[SW_DNS_HOST_BUFFER_SIZE]{}; int i = 0; for (i = 0; i < SW_DNS_HOST_BUFFER_SIZE; i++) { if (hbuf.h_addr_list[i] == nullptr) { break; } if (_af == AF_INET) { memcpy(addr_list[i].v4, hbuf.h_addr_list[i], hbuf.h_length); } else { memcpy(addr_list[i].v6, hbuf.h_addr_list[i], hbuf.h_length); } } if (_af == AF_INET) { memcpy(addr, addr_list[index].v4, hbuf.h_length); } else { memcpy(addr, addr_list[index].v6, hbuf.h_length); } sw_free(buf); return SW_OK; } #else int gethostbyname(int flags, const char *name, char *addr) { int __af = flags & (~SW_DNS_LOOKUP_RANDOM); int index = 0; std::lock_guard _lock(g_gethostbyname2_lock); struct hostent *host_entry; if (!(host_entry = ::gethostbyname2(name, __af))) { return SW_ERR; } union { char v4[INET_ADDRSTRLEN]; char v6[INET6_ADDRSTRLEN]; } addr_list[SW_DNS_HOST_BUFFER_SIZE]; int i = 0; for (i = 0; i < SW_DNS_HOST_BUFFER_SIZE; i++) { if (host_entry->h_addr_list[i] == nullptr) { break; } if (__af == AF_INET) { memcpy(addr_list[i].v4, host_entry->h_addr_list[i], host_entry->h_length); } else { memcpy(addr_list[i].v6, host_entry->h_addr_list[i], host_entry->h_length); } } if (__af == AF_INET) { memcpy(addr, addr_list[index].v4, host_entry->h_length); } else { memcpy(addr, addr_list[index].v6, host_entry->h_length); } return SW_OK; } #endif std::string gethostbyname(int type, const std::string &name) { char addr[sizeof(in6_addr)]; if (gethostbyname(type, name.c_str(), addr) == SW_OK) { return Address::addr_str(type, addr); } swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return {}; } int getaddrinfo(GetaddrinfoRequest *req) { addrinfo *result = nullptr; addrinfo *ptr = nullptr; addrinfo hints{}; hints.ai_family = req->family; hints.ai_socktype = req->socktype; hints.ai_protocol = req->protocol; int ret = ::getaddrinfo(req->hostname.c_str(), req->service.c_str(), &hints, &result); if (ret != 0) { req->error = ret; return SW_ERR; } int i = 0; for (ptr = result; ptr != nullptr; ptr = ptr->ai_next, i++) { } req->count = SW_MIN(i, SW_DNS_HOST_BUFFER_SIZE); req->results.resize(req->count); for (ptr = result, i = 0; ptr != nullptr; ptr = ptr->ai_next, i++) { switch (ptr->ai_family) { case AF_INET: memcpy(&req->results[i], ptr->ai_addr, sizeof(struct sockaddr_in)); break; case AF_INET6: memcpy(&req->results[i], ptr->ai_addr, sizeof(struct sockaddr_in6)); break; default: swoole_warning("unknown socket family[%d]", ptr->ai_family); break; } if (i == SW_DNS_HOST_BUFFER_SIZE) { break; } } ::freeaddrinfo(result); req->error = 0; return SW_OK; } int gethostbyname(GethostbynameRequest *req) { const auto rv = gethostbyname(req->family, req->name); if (rv.empty()) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return SW_ERR; } req->addr = rv; return SW_OK; } } // namespace network void GetaddrinfoRequest::parse_result(std::vector &retval) const { for (auto &addr : results) { const char *addr_str; if (family == AF_INET6) { addr_str = Address::addr_str(family, &addr.sin6_addr); } else { addr_str = Address::addr_str(family, &((sockaddr_in *) &addr)->sin_addr); } if (addr_str) { retval.emplace_back(addr_str); } } } } // namespace swoole ================================================ FILE: src/network/socket.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_signal.h" #include "swoole_util.h" #include "swoole_string.h" #include "swoole_timer.h" #include #include namespace swoole { namespace network { double Socket::default_dns_timeout = SW_SOCKET_DEFAULT_DNS_TIMEOUT; double Socket::default_connect_timeout = SW_SOCKET_DEFAULT_CONNECT_TIMEOUT; double Socket::default_read_timeout = SW_SOCKET_DEFAULT_READ_TIMEOUT; double Socket::default_write_timeout = SW_SOCKET_DEFAULT_WRITE_TIMEOUT; uint32_t Socket::default_buffer_size = SW_SOCKET_BUFFER_SIZE; IOVector::IOVector(const iovec *_iov, int _iovcnt) { iov = new iovec[_iovcnt + _iovcnt]; iov_iterator = iov + _iovcnt; count = remain_count = _iovcnt; memcpy(iov, _iov, sizeof(*_iov) * _iovcnt); memcpy(iov_iterator, _iov, sizeof(*_iov) * _iovcnt); } IOVector::~IOVector() { delete[] iov; } void IOVector::update_iterator(ssize_t _n) { size_t total_bytes = 0; size_t _offset_bytes = 0; int _index = 0; if (_n <= 0 || remain_count == 0) { return; } SW_LOOP_N(remain_count) { total_bytes += iov_iterator[i].iov_len; if (static_cast(total_bytes) >= _n) { _offset_bytes = iov_iterator[i].iov_len - (total_bytes - _n); _index = i; if (_offset_bytes == iov_iterator[i].iov_len) { _index++; _offset_bytes = 0; } // update remain_count, index, offset_bytes remain_count -= _index; index += _index; offset_bytes = i > 0 ? 0 : offset_bytes; offset_bytes += _offset_bytes; if (remain_count == 0) { // iov should not be modified, prevent valgrind from checking for invalid read return; } iov_iterator += _index; iov_iterator->iov_base = static_cast(iov_iterator->iov_base) + _offset_bytes; iov_iterator->iov_len = iov_iterator->iov_len - _offset_bytes; return; } } // represents the length of _n greater than total_bytes abort(); } static bool check_sendfile_parameters(const File *file, off_t begin, size_t length, off_t *end) { auto filename = file->get_path().c_str(); if (!file->ready()) { swoole_sys_warning("open('%s') failed", filename); return false; } FileStatus file_stat; if (!file->stat(&file_stat)) { swoole_sys_warning("fstat('%s') failed", filename); return false; } if (file_stat.st_size == 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_EMPTY, "cannot send empty file '%s'", filename); return false; } if (length == 0) { *end = file_stat.st_size; } else { *end = begin + static_cast(length); } if (begin < 0 || *end > file_stat.st_size) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "length[%ld] or offset[%ld] is invalid", length, (long) begin); return false; } return true; } static size_t get_sendfile_chunk_size(off_t begin, off_t end) { size_t real_length = end - begin; return real_length > SW_SENDFILE_CHUNK_SIZE ? SW_SENDFILE_CHUNK_SIZE : real_length; } int Socket::what_event_want(int default_event) const { if (ssl && (ssl_want_write || ssl_want_read)) { return ssl_want_write ? SW_EVENT_WRITE : SW_EVENT_READ; } return default_event; } #define CHECK_RETURN_VALUE(rv, be_zero_return) \ if (rv < 0) { \ if (errno == EINTR || catch_error(errno) == SW_WAIT) { \ return SW_CONTINUE; \ } \ swoole_set_last_error(errno); \ return SW_ERROR; \ } else if (rv == 0) { \ return be_zero_return; \ } bool Socket::wait_for(const std::function &fn, int event, int timeout_msec) { double began_at = 0; if (timeout_msec > 0) { began_at = microtime(); } if (!nonblock) { set_nonblock(); } if (ssl) { ssl_clear_error(); } while (true) { switch (fn()) { case SW_ERROR: return false; case SW_READY: return true; case SW_CONTINUE: /** * The ENOBUFS error indicates that the operating system currently lacks sufficient available memory, * requiring waiting for the kernel to reclaim memory. Event listening for writes is also ineffective—the * only recourse is to sleep for 10 milliseconds while awaiting kernel memory recovery. */ if (has_kernel_nobufs()) { usleep(10 * 1000); continue; } break; default: break; } auto rv = wait_event(timeout_msec, what_event_want(event)); if (rv == SW_ERR && ((errno == EINTR && dont_restart) || errno != EINTR)) { return false; } if (timeout_msec > 0) { timeout_msec -= sec2msec(microtime() - began_at); swoole_trace_log(SW_TRACE_CLIENT, "timeout_ms=%d", timeout_msec); if (timeout_msec <= 0) { swoole_set_last_error(ETIMEDOUT); errno = ETIMEDOUT; return false; } } } return false; } int Socket::sendfile_sync(const char *filename, off_t offset, size_t length) { off_t end; File file(filename, O_RDONLY); if (!check_sendfile_parameters(&file, offset, length, &end)) { return SW_ERR; } auto corked = false; if (end - offset > SW_SOCKET_CORK_MIN_SIZE) { corked = cork(); } auto rv = wait_for( [this, &file, &offset, end]() { size_t sent_bytes = get_sendfile_chunk_size(offset, end); ssize_t n = sendfile(file, &offset, sent_bytes); CHECK_RETURN_VALUE(n, SW_ERROR); return offset < end ? SW_CONTINUE : SW_READY; }, SW_EVENT_WRITE, sec2msec(write_timeout)); if (corked) { uncork(); } return rv ? SW_OK : SW_ERR; } ssize_t Socket::writev_sync(const struct iovec *iov, size_t iovcnt) { ssize_t bytes = 0; auto rv = wait_for( [this, &bytes, iov, iovcnt]() { ssize_t n = writev(iov, iovcnt); CHECK_RETURN_VALUE(n, SW_READY); bytes = n; return SW_READY; }, SW_EVENT_WRITE, sec2msec(write_timeout)); return rv ? bytes : -1; } swReturnCode Socket::connect_async(const Address &sa) { set_nonblock(); while (true) { auto ret = connect(sa); if (ret < 0) { if (errno == EINTR) { continue; } swoole_set_last_error(errno); return errno == EINPROGRESS ? SW_WAIT : SW_ERROR; } break; } return SW_READY; } int Socket::connect_sync(const Address &sa) { auto rc = connect_async(sa); if (rc != SW_WAIT) { return rc == SW_READY ? SW_OK : SW_ERR; } double timeout = connect_timeout; if (wait_event(timeout > 0 ? sec2msec(timeout) : timeout, SW_EVENT_WRITE) < 0) { swoole_set_last_error(ETIMEDOUT); return SW_ERR; } int err; socklen_t len = sizeof(len); int ret = get_option(SOL_SOCKET, SO_ERROR, &err, &len); if (ret < 0) { swoole_set_last_error(errno); return SW_ERR; } if (err != 0) { swoole_set_last_error(err); return SW_ERR; } set_block(); return SW_OK; } /** * clear socket buffer. */ void Socket::clean() const { char buf[2048]; while (::recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) { }; } /** * Wait socket can read or write. */ int Socket::wait_event(int timeout_ms, int _events) const { pollfd event; event.fd = fd; event.events = translate_events_to_poll(_events); if (timeout_ms < 0) { timeout_ms = -1; } while (true) { int ret = poll(&event, 1, timeout_ms); if (ret == 0) { swoole_set_last_error(SW_ERROR_SOCKET_POLL_TIMEOUT); errno = EAGAIN; return SW_ERR; } if (ret < 0) { if (errno != EINTR) { swoole_set_last_error(errno); return SW_ERR; } if (dont_restart) { swoole_set_last_error(errno); return SW_ERR; } swoole_signal_dispatch(); continue; } return SW_OK; } return SW_OK; } ssize_t Socket::send_sync(const void *_data, size_t _len, int flags) { ssize_t bytes = 0; auto rv = wait_for( [this, _data, _len, flags, &bytes]() { ssize_t n = send((char *) _data + bytes, _len - bytes, flags); CHECK_RETURN_VALUE(n, SW_READY); bytes += n; return bytes == (ssize_t) _len ? SW_READY : SW_CONTINUE; }, SW_EVENT_WRITE, sec2msec(write_timeout)); return rv ? bytes : -1; } ssize_t Socket::recv_sync(void *_data, size_t _len, int flags) { ssize_t bytes = 0; auto rv = wait_for( [this, _data, _len, flags, &bytes]() { /** * In non-blocking mode, the MSG_WAITALL flag must be cleared. Otherwise, receiving a small amount of data * may cause the poll listener to return 1 for readable events, yet the recv operation returns -1 with errno * set to EAGAIN, resulting in an infinite loop. */ ssize_t n = recv((char *) _data + bytes, _len - bytes, flags & ~MSG_WAITALL); CHECK_RETURN_VALUE(n, SW_READY); bytes += n; if (flags & MSG_WAITALL) { return bytes == (ssize_t) _len ? SW_READY : SW_CONTINUE; } else { return SW_READY; } }, SW_EVENT_READ, sec2msec(read_timeout)); return rv ? bytes : -1; } Socket *Socket::accept() { auto *socket = new Socket(); socket->removed = 1; socket->socket_type = socket_type; socket->info.len = sizeof(socket->info); #ifdef HAVE_ACCEPT4 int flags = SOCK_CLOEXEC; if (nonblock) { flags |= SOCK_NONBLOCK; } socket->fd = ::accept4(fd, reinterpret_cast(&socket->info.addr), &socket->info.len, flags); #else socket->fd = ::accept(fd, (struct sockaddr *) &socket->info.addr, &socket->info.len); if (socket->fd >= 0) { set_fd_option(nonblock, 1); } #endif if (socket->fd < 0) { delete socket; return nullptr; } socket->info.type = socket_type; socket->nonblock = nonblock; socket->cloexec = 1; return socket; } ssize_t Socket::sendto_sync(const Address &sa, const void *_buf, size_t _n, int flags) { ssize_t bytes = 0; auto rv = wait_for( [this, &sa, _buf, _n, flags, &bytes]() { ssize_t n = sendto(sa, _buf, _n, flags); CHECK_RETURN_VALUE(n, SW_READY); bytes = n; return SW_READY; }, SW_EVENT_WRITE, sec2msec(write_timeout)); return rv ? bytes : -1; } ssize_t Socket::recvfrom(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) const { ssize_t n = 0; SW_LOOP_N(SW_SOCKET_RETRY_COUNT) { n = ::recvfrom(fd, buf, len, flags, addr, addr_len); if (n < 0 && errno == EINTR) { continue; } break; } return n; } ssize_t Socket::recvfrom_sync(char *buf, size_t len, int flags, Address *sa) { return recvfrom_sync(buf, len, flags, &sa->addr.ss, &sa->len); } ssize_t Socket::recvfrom_sync(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) { ssize_t bytes = 0; auto rv = wait_for( [this, buf, len, flags, addr, addr_len, &bytes]() { ssize_t n = recvfrom(buf, len, flags, addr, addr_len); CHECK_RETURN_VALUE(n, SW_READY); bytes = n; return SW_READY; }, SW_EVENT_READ, sec2msec(read_timeout)); return rv ? bytes : -1; } static void socket_free_defer(void *ptr) { auto *sock = static_cast(ptr); if (sock->is_local() && sock->bound) { ::unlink(sock->get_addr()); } if (sock->fd != -1 && close(sock->fd) != 0) { swoole_sys_warning("close(%d) failed", sock->fd); } delete sock; } void Socket::free() { if (recv_timer) { swoole_timer_del(recv_timer); } if (send_timer) { swoole_timer_del(send_timer); } delete in_buffer; delete out_buffer; if (swoole_event_is_available()) { if (fd != -1 && !removed) { swoole_event_del(this); } swoole_event_defer(socket_free_defer, this); } else { socket_free_defer(this); } } int Socket::get_name() { info.len = sizeof(info.addr); if (getsockname(fd, &info.addr.ss, &info.len) < 0) { return -1; } info.type = socket_type; return 0; } int Socket::get_peer_name(Address *sa) const { sa->len = sizeof(sa->addr); sa->type = socket_type; if (::getpeername(fd, &sa->addr.ss, &sa->len) != 0) { return SW_ERR; } return SW_OK; } int Socket::set_tcp_nopush(int nopush) { #ifdef TCP_CORK if (set_option(IPPROTO_TCP, TCP_CORK, nopush) == SW_ERR) { return -1; } else { tcp_nopush = nopush; return 0; } #else return -1; #endif } int Socket::bind(const std::string &_host, int port) { Address addr; if (!addr.assign(socket_type, _host, port, false)) { return SW_ERR; } return bind(addr); } int Socket::bind(const struct sockaddr *sa, socklen_t len) { if (::bind(fd, sa, len) < 0) { return SW_ERR; } bound = 1; return SW_OK; } int Socket::listen(int backlog) { if (::listen(fd, backlog <= 0 ? SW_BACKLOG : backlog) < 0) { return SW_ERR; } listened = 1; return SW_OK; } bool Socket::set_buffer_size(uint32_t _buffer_size) const { return set_send_buffer_size(_buffer_size) && set_recv_buffer_size(_buffer_size); } bool Socket::set_recv_buffer_size(uint32_t _buffer_size) const { return set_option(SOL_SOCKET, SO_RCVBUF, _buffer_size) == 0; } bool Socket::set_send_buffer_size(uint32_t _buffer_size) const { return set_option(SOL_SOCKET, SO_SNDBUF, _buffer_size) == 0; } bool Socket::check_liveness() { char buf; errno = 0; ssize_t retval = peek(&buf, sizeof(buf), MSG_DONTWAIT); return !(retval == 0 || (retval < 0 && catch_read_error(errno) == SW_CLOSE)); } bool Socket::set_tcp_nodelay(int nodelay) { if (set_option(IPPROTO_TCP, TCP_NODELAY, nodelay) == SW_ERR) { return false; } else { tcp_nodelay = nodelay; return true; } } bool Socket::cork() { if (tcp_nopush) { return false; } #ifdef TCP_CORK if (set_tcp_nopush(1) < 0) { swoole_sys_warning("set_tcp_nopush(fd=%d, ON) failed", fd); return false; } #endif // Need to turn off tcp nodelay when using nopush if (tcp_nodelay && !set_tcp_nodelay(0)) { swoole_sys_warning("set_tcp_nodelay(fd=%d, OFF) failed", fd); } return true; } bool Socket::uncork() { if (!tcp_nopush) { return false; } #ifdef TCP_CORK if (set_tcp_nopush(0) < 0) { swoole_sys_warning("set_tcp_nopush(fd=%d, OFF) failed", fd); return false; } #endif // Restore tcp_nodelay setting if (enable_tcp_nodelay && tcp_nodelay == 0 && !set_tcp_nodelay(1)) { swoole_sys_warning("set_tcp_nodelay(fd=%d, ON) failed", fd); return false; } return true; } Socket *Socket::dup() const { auto *_socket = new Socket(); *_socket = *this; _socket->fd = ::dup(fd); return _socket; } static bool _set_timeout(int fd, int type, double timeout) { timeval timeo; timeo.tv_sec = (int) timeout; timeo.tv_usec = (int) ((timeout - timeo.tv_sec) * 1000 * 1000); int ret = setsockopt(fd, SOL_SOCKET, type, &timeo, sizeof(timeo)); if (ret < 0) { swoole_sys_warning("setsockopt(SO_SNDTIMEO, %s) failed", type == SO_SNDTIMEO ? "SEND" : "RECV"); return false; } else { return true; } } static bool _fcntl_set_option(int sock, int nonblock, int cloexec) { int opts, ret; if (nonblock >= 0) { do { opts = fcntl(sock, F_GETFL); } while (opts < 0 && errno == EINTR); if (opts < 0) { swoole_sys_warning("fcntl(%d, GETFL) failed", sock); } if (nonblock) { opts = opts | O_NONBLOCK; } else { opts = opts & ~O_NONBLOCK; } do { ret = fcntl(sock, F_SETFL, opts); } while (ret < 0 && errno == EINTR); if (ret < 0) { swoole_sys_warning("fcntl(%d, SETFL, opts) failed", sock); return false; } } #ifdef FD_CLOEXEC if (cloexec >= 0) { do { opts = fcntl(sock, F_GETFD); } while (opts < 0 && errno == EINTR); if (opts < 0) { swoole_sys_warning("fcntl(%d, GETFL) failed", sock); } if (cloexec) { opts = opts | FD_CLOEXEC; } else { opts = opts & ~FD_CLOEXEC; } do { ret = fcntl(sock, F_SETFD, opts); } while (ret < 0 && errno == EINTR); if (ret < 0) { swoole_sys_warning("fcntl(%d, SETFD, opts) failed", sock); return false; } } #endif return true; } bool Socket::set_fd_option(int _nonblock, int _cloexec) { if (_fcntl_set_option(fd, _nonblock, _cloexec)) { nonblock = _nonblock; cloexec = _cloexec; return true; } else { return false; } } void Socket::set_timeout(double timeout, int type) { if (timeout == 0) { return; } if (type & SW_TIMEOUT_DNS) { dns_timeout = timeout; } if (type & SW_TIMEOUT_CONNECT) { connect_timeout = timeout; } if (type & SW_TIMEOUT_READ) { read_timeout = timeout; } if (type & SW_TIMEOUT_WRITE) { write_timeout = timeout; } } double Socket::get_timeout(TimeoutType type) const { SW_ASSERT_1BYTE(type); if (type == SW_TIMEOUT_DNS) { return dns_timeout; } else if (type == SW_TIMEOUT_CONNECT) { return connect_timeout; } else if (type == SW_TIMEOUT_READ) { return read_timeout; } else if (type == SW_TIMEOUT_WRITE) { return write_timeout; } else { assert(0); return -1; } } bool Socket::has_timedout() const { return errno == EAGAIN || errno == ETIMEDOUT || swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT; } bool Socket::has_kernel_nobufs() { return std::exchange(kernel_nobufs, 0); } bool Socket::set_kernel_read_timeout(double timeout) { if (_set_timeout(fd, SO_SNDTIMEO, timeout)) { write_timeout = timeout; return true; } else { return false; } } bool Socket::set_kernel_write_timeout(double timeout) { if (_set_timeout(fd, SO_RCVTIMEO, timeout)) { read_timeout = timeout; return true; } else { return false; } } int Socket::handle_sendfile() { Buffer *buffer = out_buffer; BufferChunk *chunk = buffer->front(); auto *task = (SendfileRequest *) chunk->value.ptr; if (task->corked == 0) { if (task->end - task->begin > SW_SOCKET_CORK_MIN_SIZE) { task->corked = cork() ? 1 : -1; } else { task->corked = -1; } } size_t sendn = get_sendfile_chunk_size(task->begin, task->end); ssize_t rv = sendfile(task->file, &task->begin, sendn); swoole_trace("rv=%ld|begin=%ld|sendn=%lu|end=%lu", rv, (long) task->begin, sendn, task->end); if (rv <= 0) { switch (catch_write_error(errno)) { case SW_ERROR: swoole_sys_warning("sendfile(%s, %ld, %zu) failed", task->get_filename(), (long) task->begin, sendn); buffer->pop(); return SW_OK; case SW_CLOSE: close_wait = 1; return SW_ERR; case SW_WAIT: send_wait = 1; return SW_ERR; default: break; } } else { if (send_timer) { last_sent_time = time(true); } } // sendfile completed if (task->begin == task->end) { if (task->corked == 1) { uncork(); task->corked = 0; } buffer->pop(); } return SW_OK; } /** * send buffer to client */ int Socket::handle_send() { Buffer *buffer = out_buffer; BufferChunk *chunk = buffer->front(); uint32_t sendn = chunk->length - chunk->offset; if (sendn == 0) { buffer->pop(); return SW_OK; } ssize_t ret = send(chunk->value.str + chunk->offset, sendn, 0); if (ret < 0) { switch (catch_write_error(errno)) { case SW_ERROR: swoole_sys_warning("send to fd[%d] failed", fd); break; case SW_CLOSE: close_wait = 1; return SW_ERR; case SW_WAIT: send_wait = 1; return SW_ERR; default: break; } return SW_OK; } // chunk full send else if (ret == sendn) { buffer->pop(); } else { chunk->offset += ret; // kernel is not fully processing and socket buffer is full if (ret < sendn) { send_wait = 1; return SW_ERR; } } return SW_OK; } static void Socket_sendfile_destructor(BufferChunk *chunk) { auto *task = static_cast(chunk->value.ptr); delete task; } ssize_t Socket::sendfile(const File &fp, off_t *offset, size_t length) { if (ssl) { return ssl_sendfile(fp, offset, length); } else { return swoole_sendfile(fd, fp.get_fd(), offset, length); } } int Socket::sendfile_async(const char *filename, off_t offset, size_t length) { std::unique_ptr task(new SendfileRequest(filename, offset)); if (!check_sendfile_parameters(&task->file, offset, length, &task->end)) { return SW_ERR; } if (out_buffer == nullptr) { out_buffer = new Buffer(SW_SEND_BUFFER_SIZE); } BufferChunk *chunk = out_buffer->alloc(BufferChunk::TYPE_SENDFILE, 0); chunk->value.ptr = task.release(); chunk->destroy = Socket_sendfile_destructor; return SW_OK; } ssize_t Socket::recv(void *_buf, size_t _n, int _flags) { ssize_t total_bytes = 0; do { if (ssl) { ssize_t retval = 0; while (static_cast(total_bytes) < _n) { retval = ssl_recv(static_cast(_buf) + total_bytes, _n - total_bytes); if (retval <= 0) { if (total_bytes == 0) { total_bytes = retval; } break; } else { total_bytes += retval; if (!(nonblock || (_flags & MSG_WAITALL))) { break; } } } } else { total_bytes = ::recv(fd, _buf, _n, _flags); } } while (total_bytes < 0 && (errno == EINTR && !dont_restart)); if (total_bytes > 0) { total_recv_bytes += total_bytes; if (recv_timer) { last_received_time = time(true); } } // The POLLHUP event is triggered, but Socket::recv returns EAGAIN if (total_bytes < 0 && catch_read_error(errno) == SW_WAIT && event_hup) { total_bytes = 0; } swoole_trace_log(SW_TRACE_SOCKET, "recv %ld/%ld bytes, errno=%d", total_bytes, _n, errno); return total_bytes; } ssize_t Socket::send(const void *_buf, size_t _n, int _flags) { ssize_t retval; do { if (ssl) { retval = ssl_send(_buf, _n); } else { retval = ::send(fd, _buf, _n, _flags); } } while (retval < 0 && (errno == EINTR && !dont_restart)); if (retval > 0) { total_send_bytes += retval; if (send_timer) { last_sent_time = time(true); } } swoole_trace_log(SW_TRACE_SOCKET, "send %ld/%ld bytes, errno=%d", retval, _n, errno); return retval; } ssize_t Socket::send_async(const void *_buf, size_t _n) { if (!swoole_event_is_available()) { return send_sync(_buf, _n, 0); } else { return swoole_event_write(this, _buf, _n); } } ssize_t Socket::read_sync(void *_buf, size_t _len) { ssize_t bytes = 0; auto rv = wait_for( [this, _buf, _len, &bytes]() { ssize_t n = read((char *) _buf + bytes, _len - bytes); CHECK_RETURN_VALUE(n, SW_READY); bytes += n; return SW_READY; }, SW_EVENT_READ, sec2msec(read_timeout)); return rv ? bytes : -1; } ssize_t Socket::write_sync(const void *_buf, size_t _len) { ssize_t bytes = 0; auto rv = wait_for( [this, _buf, _len, &bytes]() { ssize_t n = write((char *) _buf + bytes, _len - bytes); CHECK_RETURN_VALUE(n, SW_READY); bytes += n; return SW_READY; }, SW_EVENT_WRITE, sec2msec(write_timeout)); return rv ? bytes : -1; } ssize_t Socket::readv(IOVector *io_vector) { ssize_t retval; do { if (ssl) { retval = ssl_readv(io_vector); } else { retval = ::readv(fd, io_vector->get_iterator(), io_vector->get_remain_count()); io_vector->update_iterator(retval); } } while (retval < 0 && errno == EINTR); return retval; } ssize_t Socket::writev(IOVector *io_vector) { ssize_t retval; do { if (ssl) { retval = ssl_writev(io_vector); } else { retval = ::writev(fd, io_vector->get_iterator(), io_vector->get_remain_count()); io_vector->update_iterator(retval); } } while (retval < 0 && errno == EINTR); return retval; } ssize_t Socket::peek(void *_buf, size_t _n, int _flags) const { ssize_t retval; _flags |= MSG_PEEK; do { if (ssl) { retval = SSL_peek(ssl, _buf, _n); } else { retval = ::recv(fd, _buf, _n, _flags); } } while (retval < 0 && errno == EINTR); swoole_trace_log(SW_TRACE_SOCKET, "peek %ld/%ld bytes, errno=%d", retval, _n, errno); return retval; } int Socket::catch_error(const int err) { switch (err) { case EFAULT: abort(); return SW_ERROR; case EBADF: case ENOENT: return SW_INVALID; case ECONNRESET: case ECONNABORTED: case EPIPE: case ENOTCONN: case ETIMEDOUT: case ECONNREFUSED: case ENETDOWN: case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: case SW_ERROR_SSL_BAD_CLIENT: case SW_ERROR_SSL_RESET: return SW_CLOSE; case EAGAIN: #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif case 0: return SW_WAIT; case ENOBUFS: kernel_nobufs = true; return SW_WAIT; default: return SW_ERROR; } } SocketType Socket::convert_to_type(const int domain, const int type) { if (domain == AF_INET && type == SOCK_STREAM) { return SW_SOCK_TCP; } else if (domain == AF_INET6 && type == SOCK_STREAM) { return SW_SOCK_TCP6; } else if (domain == AF_UNIX && type == SOCK_STREAM) { return SW_SOCK_UNIX_STREAM; } else if (domain == AF_INET && type == SOCK_DGRAM) { return SW_SOCK_UDP; } else if (domain == AF_INET6 && type == SOCK_DGRAM) { return SW_SOCK_UDP6; } else if (domain == AF_UNIX && type == SOCK_DGRAM) { return SW_SOCK_UNIX_DGRAM; } else if (domain == AF_INET && type == SOCK_RAW) { return SW_SOCK_RAW; } else if (domain == AF_INET6 && type == SOCK_RAW) { return SW_SOCK_RAW6; } else { return SW_SOCK_RAW; } } SocketType Socket::convert_to_type(std::string &host) { if (host.compare(0, 6, "unix:/", 0, 6) == 0) { host = host.substr(sizeof("unix:") - 1); host.erase(0, host.find_first_not_of('/') - 1); return SW_SOCK_UNIX_STREAM; } if (host.find(':') != std::string::npos) { return SW_SOCK_TCP6; } return SW_SOCK_TCP; } int Socket::get_domain_and_type(SocketType type, int *sock_domain, int *sock_type) { switch (type) { case SW_SOCK_TCP6: *sock_domain = AF_INET6; *sock_type = SOCK_STREAM; break; case SW_SOCK_UNIX_STREAM: *sock_domain = AF_UNIX; *sock_type = SOCK_STREAM; break; case SW_SOCK_UDP: *sock_domain = AF_INET; *sock_type = SOCK_DGRAM; break; case SW_SOCK_UDP6: *sock_domain = AF_INET6; *sock_type = SOCK_DGRAM; break; case SW_SOCK_UNIX_DGRAM: *sock_domain = AF_UNIX; *sock_type = SOCK_DGRAM; break; case SW_SOCK_TCP: *sock_domain = AF_INET; *sock_type = SOCK_STREAM; break; case SW_SOCK_RAW: *sock_domain = AF_INET; *sock_type = SOCK_RAW; break; case SW_SOCK_RAW6: *sock_domain = AF_INET6; *sock_type = SOCK_RAW; break; default: return SW_ERR; } return SW_OK; } #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT static int ssl_check_name(const char *name, ASN1_STRING *pattern) { char *s, *end; size_t slen, plen; s = (char *) name; slen = strlen(name); uchar *p = ASN1_STRING_data(pattern); plen = ASN1_STRING_length(pattern); if (swoole_strcaseeq(s, slen, (char *) p, plen)) { return SW_OK; } if (plen > 2 && p[0] == '*' && p[1] == '.') { plen -= 1; p += 1; end = s + slen; s = swoole_strlchr(s, end, '.'); if (s == nullptr) { return SW_ERR; } slen = end - s; if (swoole_strcaseeq(s, slen, (char *) p, plen)) { return SW_OK; } } return SW_ERR; } #endif bool Socket::ssl_check_host(const char *tls_host_name) const { X509 *cert = ssl_get_peer_certificate(); if (cert == nullptr) { return false; } #ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT /* X509_check_host() is only available in OpenSSL 1.0.2+ */ if (X509_check_host(cert, tls_host_name, strlen(tls_host_name), 0, nullptr) != 1) { swoole_warning("X509_check_host(): no match"); goto _failed; } goto _found; #else int n, i; X509_NAME *sname; ASN1_STRING *str; X509_NAME_ENTRY *entry; GENERAL_NAME *altname; STACK_OF(GENERAL_NAME) * altnames; /* * As per RFC6125 and RFC2818, we check subjectAltName extension, * and if it's not present - commonName in Subject is checked. */ altnames = (STACK_OF(GENERAL_NAME) *) X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr); if (altnames) { n = sk_GENERAL_NAME_num(altnames); for (i = 0; i < n; i++) { altname = sk_GENERAL_NAME_value(altnames, i); if (altname->type != GEN_DNS) { continue; } str = altname->d.dNSName; swoole_trace("SSL subjectAltName: \"%.*s\"", ASN1_STRING_length(str), ASN1_STRING_data(str)); if (ssl_check_name(tls_host_name, str) == SW_OK) { swoole_trace("SSL subjectAltName: match"); GENERAL_NAMES_free(altnames); goto _found; } } swoole_trace("SSL subjectAltName: no match"); GENERAL_NAMES_free(altnames); goto _failed; } /* * If there is no subjectAltName extension, check commonName * in Subject. While RFC2818 requires to only check "most specific" * CN, both Apache and OpenSSL check all CNs, and so do we. */ sname = X509_get_subject_name(cert); if (sname == nullptr) { goto _failed; } i = -1; for (;;) { i = X509_NAME_get_index_by_NID(sname, NID_commonName, i); if (i < 0) { break; } entry = X509_NAME_get_entry(sname, i); str = X509_NAME_ENTRY_get_data(entry); swoole_trace("SSL commonName: \"%.*s\"", ASN1_STRING_length(str), ASN1_STRING_data(str)); if (ssl_check_name(tls_host_name, str) == SW_OK) { swoole_trace("SSL commonName: match"); goto _found; } } swoole_trace("SSL commonName: no match"); #endif _failed: X509_free(cert); return false; _found: X509_free(cert); return true; } bool Socket::ssl_verify(bool allow_self_signed) const { long err = SSL_get_verify_result(ssl); switch (err) { case X509_V_OK: break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: if (allow_self_signed) { break; } else { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SSL_VERIFY_FAILED, "self signed certificate from fd#%d is not allowed", fd); return false; } default: swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SSL_VERIFY_FAILED, "can not verify peer from fd#%d with error#%ld: %s", fd, err, X509_verify_cert_error_string(err)); return false; } return true; } X509 *Socket::ssl_get_peer_certificate() const { if (!ssl) { return nullptr; } return SSL_get_peer_certificate(ssl); } STACK_OF(X509) * Socket::ssl_get_peer_cert_chain() const { if (!ssl) { return nullptr; } return SSL_get_peer_cert_chain(ssl); } static int _ssl_read_x509_file(X509 *cert, char *buffer, size_t length) { BIO *bio = BIO_new(BIO_s_mem()); ON_SCOPE_EXIT { BIO_free(bio); }; if (bio == nullptr) { swoole_warning("BIO_new() failed"); return -1; } if (PEM_write_bio_X509(bio, cert) == 0) { swoole_warning("PEM_write_bio_X509() failed"); return -1; } int len = BIO_pending(bio); if (len < 0 && len > static_cast(length)) { swoole_warning("certificate length[%d] is too big", len); return -1; } return BIO_read(bio, buffer, len); } std::vector Socket::ssl_get_peer_cert_chain(int limit) const { std::vector list; STACK_OF(X509) *chain = ssl_get_peer_cert_chain(); if (chain == nullptr) { return list; } auto n = sk_X509_num(chain); #ifdef OPENSSL_IS_BORINGSSL n = std::min((int) n, limit); #else n = std::min(n, limit); #endif SW_LOOP_N(n) { X509 *cert = sk_X509_value(chain, i); auto rv = _ssl_read_x509_file(cert, sw_tg_buffer()->str, sw_tg_buffer()->size); if (rv > 0) { list.emplace_back(sw_tg_buffer()->str, rv); } } return list; } bool Socket::ssl_get_peer_certificate(String *buf) const { int n = ssl_get_peer_certificate(buf->str, buf->size); if (n < 0) { return false; } else { buf->length = n; return true; } } int Socket::ssl_get_peer_certificate(char *buffer, size_t length) const { X509 *cert = ssl_get_peer_certificate(); if (cert == nullptr) { return SW_ERR; } ON_SCOPE_EXIT { if (cert) { X509_free(cert); } }; return _ssl_read_x509_file(cert, buffer, length); } const char *Socket::ssl_get_error_reason(int *reason) { ulong_t error = ERR_get_error(); if (reason) { *reason = ERR_GET_REASON(error); } return ERR_reason_error_string(error); } ReturnCode Socket::ssl_accept() { ssl_clear_error(); int n = SSL_accept(ssl); /** * The TLS/SSL handshake was successfully completed */ if (n == 1) { ssl_state = SW_SSL_STATE_READY; #if OPENSSL_VERSION_NUMBER < 0x10100000L #ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS if (ssl->s3) { ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; } #endif #endif return SW_READY; } /** * The TLS/SSL handshake was not successful but was shutdown. */ else if (n == 0) { return SW_ERROR; } long err = SSL_get_error(ssl, n); if (err == SSL_ERROR_WANT_READ) { ssl_want_read = 1; ssl_want_write = 0; return SW_WAIT; } else if (err == SSL_ERROR_WANT_WRITE) { ssl_want_read = 0; ssl_want_write = 1; return SW_WAIT; } else if (err == SSL_ERROR_SSL) { int reason; const char *error_string = ssl_get_error_reason(&reason); swoole_warning("bad SSL client[%s:%d], reason=%d, error_string=%s", info.get_addr(), info.get_port(), reason, error_string); return SW_ERROR; } else if (err == SSL_ERROR_SYSCALL) { #ifdef SW_SUPPORT_DTLS if (dtls && errno == 0) { ssl_want_read = 1; return SW_WAIT; } #endif return SW_ERROR; } swoole_warning("SSL_do_handshake() failed. Error: %s[%ld|%d]", strerror(errno), err, errno); return SW_ERROR; } int Socket::ssl_connect() { ssl_clear_error(); int n = SSL_connect(ssl); if (n == 1) { ssl_state = SW_SSL_STATE_READY; #ifdef SW_LOG_TRACE_OPEN const char *ssl_version = SSL_get_version(ssl); const char *ssl_cipher = SSL_get_cipher_name(ssl); swoole_trace_log(SW_TRACE_SSL, "connected (%s %s)", ssl_version, ssl_cipher); #endif return SW_OK; } long err = SSL_get_error(ssl, n); if (err == SSL_ERROR_WANT_READ) { ssl_want_read = 1; ssl_want_write = 0; ssl_state = SW_SSL_STATE_WAIT_STREAM; return SW_OK; } else if (err == SSL_ERROR_WANT_WRITE) { ssl_want_read = 0; ssl_want_write = 1; ssl_state = SW_SSL_STATE_WAIT_STREAM; return SW_OK; } else if (err == SSL_ERROR_ZERO_RETURN) { swoole_debug("SSL_connect(fd=%d) closed", fd); swoole_set_last_error(SW_ERROR_SSL_RESET); return SW_ERR; } else if (err == SSL_ERROR_SYSCALL) { if (n) { swoole_set_last_error(errno); return SW_ERR; } } else { swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); } ulong_t err_code = ERR_get_error(); char error_buf[512]; ERR_error_string_n(err_code, error_buf, sizeof(error_buf)); swoole_notice("ssl_connect(fd=%d) to server[%s:%d] failed. Error: %s[%ld|%d]", fd, info.get_addr(), info.get_port(), error_buf, err, ERR_GET_REASON(err_code)); return SW_ERR; } ssize_t Socket::ssl_sendfile(const File &fp, off_t *_offset, size_t _size) { char buf[SW_BUFFER_SIZE_BIG]; ssize_t readn = _size > sizeof(buf) ? sizeof(buf) : _size; ssize_t n = fp.pread(buf, readn, *_offset); if (n > 0) { ssize_t ret = ssl_send(buf, n); if (ret < 0) { if (catch_write_error(errno) == SW_ERROR) { swoole_sys_warning("write() failed"); } } else { *_offset += ret; } swoole_trace_log(SW_TRACE_REACTOR, "fd=%d, readn=%ld, n=%ld, ret=%ld", fd, readn, n, ret); return ret; } else { swoole_sys_warning("pread() failed"); return SW_ERR; } } bool Socket::ssl_shutdown() { if (ssl_closed_) { return false; } if (SSL_in_init(ssl)) { return false; } /** * If the peer close first, local should be set to quiet mode and do not send any data, * otherwise the peer will send RST segment. */ if (ssl_quiet_shutdown) { SSL_set_quiet_shutdown(ssl, 1); } int mode = SSL_get_shutdown(ssl); SSL_set_shutdown(ssl, mode | SSL_RECEIVED_SHUTDOWN | SSL_SENT_SHUTDOWN); int n = SSL_shutdown(ssl); ssl_closed_ = 1; swoole_trace("SSL_shutdown: %d", n); int sslerr = 0; /* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */ if (n != 1 && ERR_peek_error()) { sslerr = SSL_get_error(ssl, n); swoole_trace("SSL_get_error: %d", sslerr); } if (!(n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN)) { int reason; const char *error_string = ssl_get_error_reason(&reason); swoole_warning("SSL_shutdown() failed, reason=%d, error_string=%s", reason, error_string); return false; } return true; } void Socket::ssl_close() { /* * OpenSSL 1.0.2f complains if SSL_shutdown() is called during * an SSL handshake, while previous versions always return 0. * Avoid calling SSL_shutdown() if handshake wasn't completed. */ if (!ssl_closed_) { ssl_shutdown(); } SSL_free(ssl); ssl = nullptr; } void Socket::ssl_catch_error() const { int level = SW_LOG_NOTICE; int reason = ERR_GET_REASON(ERR_peek_error()); #if 0 /* handshake failures */ switch (reason) { case SSL_R_BAD_CHANGE_CIPHER_SPEC: /* 103 */ case SSL_R_BLOCK_CIPHER_PAD_IS_WRONG: /* 129 */ case SSL_R_DIGEST_CHECK_FAILED: /* 149 */ case SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST: /* 151 */ case SSL_R_EXCESSIVE_MESSAGE_SIZE: /* 152 */ case SSL_R_LENGTH_MISMATCH:/* 159 */ case SSL_R_NO_CIPHERS_PASSED:/* 182 */ case SSL_R_NO_CIPHERS_SPECIFIED:/* 183 */ case SSL_R_NO_COMPRESSION_SPECIFIED: /* 187 */ case SSL_R_NO_SHARED_CIPHER:/* 193 */ case SSL_R_RECORD_LENGTH_MISMATCH: /* 213 */ #ifdef SSL_R_PARSE_TLSEXT case SSL_R_PARSE_TLSEXT:/* 227 */ #endif case SSL_R_UNEXPECTED_MESSAGE:/* 244 */ case SSL_R_UNEXPECTED_RECORD:/* 245 */ case SSL_R_UNKNOWN_ALERT_TYPE: /* 246 */ case SSL_R_UNKNOWN_PROTOCOL:/* 252 */ case SSL_R_WRONG_VERSION_NUMBER:/* 267 */ case SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC: /* 281 */ #ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG case SSL_R_RENEGOTIATE_EXT_TOO_LONG:/* 335 */ case SSL_R_RENEGOTIATION_ENCODING_ERR:/* 336 */ case SSL_R_RENEGOTIATION_MISMATCH:/* 337 */ #endif #ifdef SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED case SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED: /* 338 */ #endif #ifdef SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING case SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING:/* 345 */ #endif #ifdef SSL_R_INAPPROPRIATE_FALLBACK case SSL_R_INAPPROPRIATE_FALLBACK: /* 373 */ #endif case 1000:/* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */ case SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE:/* 1010 */ case SSL_R_SSLV3_ALERT_BAD_RECORD_MAC:/* 1020 */ case SSL_R_TLSV1_ALERT_DECRYPTION_FAILED:/* 1021 */ case SSL_R_TLSV1_ALERT_RECORD_OVERFLOW:/* 1022 */ case SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE:/* 1030 */ case SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE:/* 1040 */ case SSL_R_SSLV3_ALERT_NO_CERTIFICATE:/* 1041 */ case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:/* 1042 */ case SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE: /* 1043 */ case SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED:/* 1044 */ case SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED:/* 1045 */ case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:/* 1046 */ case SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER:/* 1047 */ case SSL_R_TLSV1_ALERT_UNKNOWN_CA:/* 1048 */ case SSL_R_TLSV1_ALERT_ACCESS_DENIED:/* 1049 */ case SSL_R_TLSV1_ALERT_DECODE_ERROR:/* 1050 */ case SSL_R_TLSV1_ALERT_DECRYPT_ERROR:/* 1051 */ case SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION:/* 1060 */ case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:/* 1070 */ case SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY:/* 1071 */ case SSL_R_TLSV1_ALERT_INTERNAL_ERROR:/* 1080 */ case SSL_R_TLSV1_ALERT_USER_CANCELLED:/* 1090 */ case SSL_R_TLSV1_ALERT_NO_RENEGOTIATION: /* 1100 */ level = SW_LOG_WARNING; break; #endif swoole_error_log(level, SW_ERROR_SSL_BAD_PROTOCOL, "SSL connection#%d[%s:%d] protocol error[%d]", fd, info.get_addr(), info.get_port(), reason); } ssize_t Socket::ssl_recv(void *_buf, size_t _n) { ssl_clear_error(); int n = SSL_read(ssl, _buf, _n); if (n < 0) { int _errno = SSL_get_error(ssl, n); switch (_errno) { case SSL_ERROR_WANT_READ: ssl_want_read = 1; errno = EAGAIN; return SW_ERR; case SSL_ERROR_WANT_WRITE: ssl_want_write = 1; errno = EAGAIN; return SW_ERR; case SSL_ERROR_SYSCALL: return errno == 0 ? 0 : SW_ERR; case SSL_ERROR_SSL: ssl_catch_error(); errno = SW_ERROR_SSL_BAD_CLIENT; return SW_ERR; default: break; } } return n; } ssize_t Socket::ssl_send(const void *_buf, size_t _n) { ssl_clear_error(); #ifdef SW_SUPPORT_DTLS if (dtls && chunk_size && _n > chunk_size) { _n = chunk_size; } #endif int n = SSL_write(ssl, _buf, _n); if (n < 0) { int _errno = SSL_get_error(ssl, n); switch (_errno) { case SSL_ERROR_WANT_READ: ssl_want_read = 1; errno = EAGAIN; return SW_ERR; case SSL_ERROR_WANT_WRITE: ssl_want_write = 1; errno = EAGAIN; return SW_ERR; case SSL_ERROR_SYSCALL: errno = SW_ERROR_SSL_RESET; return SW_ERR; case SSL_ERROR_SSL: ssl_catch_error(); errno = SW_ERROR_SSL_BAD_CLIENT; return SW_ERR; default: break; } } return n; } ssize_t Socket::ssl_readv(IOVector *io_vector) { ssize_t retval, total_bytes = 0; do { retval = ssl_recv(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); total_bytes += retval > 0 ? retval : 0; io_vector->update_iterator(retval); } while (retval > 0 && io_vector->get_remain_count() > 0); return total_bytes > 0 ? total_bytes : retval; } ssize_t Socket::ssl_writev(IOVector *io_vector) { ssize_t retval, total_bytes = 0; do { retval = ssl_send(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); total_bytes += retval > 0 ? retval : 0; io_vector->update_iterator(retval); } while (retval > 0 && io_vector->get_remain_count() > 0); return total_bytes > 0 ? total_bytes : retval; } int Socket::ssl_create(SSLContext *ssl_context, int _flags) { ssl_clear_error(); ssl = SSL_new(ssl_context->get_context()); if (ssl == nullptr) { swoole_warning("SSL_new() failed"); return SW_ERR; } if (!SSL_set_fd(ssl, fd)) { ulong_t err = ERR_peek_error(); swoole_warning("SSL_set_fd() failed. Error: %s[%lu]", ERR_reason_error_string(err), err); return SW_ERR; } if (_flags & SW_SSL_CLIENT) { SSL_set_connect_state(ssl); } else if (_flags & SW_SSL_SERVER) { SSL_set_accept_state(ssl); } if (SSL_set_ex_data(ssl, swoole_ssl_get_ex_connection_index(), this) == 0) { swoole_warning("SSL_set_ex_data() failed"); return SW_ERR; } #ifdef OPENSSL_IS_BORINGSSL SSL_set_enable_ech_grease(ssl, ssl_context->grease); #endif ssl_state = 0; return SW_OK; } } // namespace network using network::Socket; Socket *make_socket(SocketType type, FdType fd_type, int flags) { int sock_domain; int sock_type; if (Socket::get_domain_and_type(type, &sock_domain, &sock_type) < 0) { swoole_warning("unknown socket type [%d]", type); errno = ESOCKTNOSUPPORT; swoole_set_last_error(errno); return nullptr; } return make_socket(type, fd_type, sock_domain, sock_type, 0, flags); } Socket *make_socket(SocketType type, FdType fd_type, int sock_domain, int sock_type, int socket_protocol, int flags) { int sockfd = swoole::socket(sock_domain, sock_type, socket_protocol, flags); if (sockfd < 0) { swoole_set_last_error(errno); return nullptr; } auto _socket = make_socket(sockfd, fd_type); _socket->nonblock = !!(flags & SW_SOCK_NONBLOCK); _socket->cloexec = !!(flags & SW_SOCK_CLOEXEC); _socket->socket_type = type; return _socket; } int socket(int sock_domain, int sock_type, int socket_protocol, int flags) { bool nonblock = flags & SW_SOCK_NONBLOCK; bool cloexec = flags & SW_SOCK_CLOEXEC; #if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) int sock_flags = 0; if (nonblock) { sock_flags |= SOCK_NONBLOCK; } if (cloexec) { sock_flags |= SOCK_CLOEXEC; } int sockfd = ::socket(sock_domain, sock_type | sock_flags, socket_protocol); if (sockfd < 0) { return sockfd; } #else int sockfd = ::socket(sock_domain, sock_type, socket_protocol); if (sockfd < 0) { return sockfd; } if (nonblock || cloexec) { if (!network::_fcntl_set_option(sockfd, nonblock ? 1 : -1, cloexec ? 1 : -1)) { close(sockfd); return sockfd; } } #endif return sockfd; } Socket *make_server_socket(SocketType type, const char *address, int port, int backlog) { Socket *sock = swoole::make_socket(type, SW_FD_STREAM_SERVER, SW_SOCK_CLOEXEC); if (sock == nullptr) { swoole_sys_warning("socket() failed"); return nullptr; } if (sock->bind(address, port) < 0) { swoole_sys_warning("bind(%d, %s:%d, %d) failed", sock->get_fd(), address, port, backlog); goto __cleanup; } if (sock->is_stream() && sock->listen(backlog) < 0) { swoole_sys_warning("listen(%d, %s:%d, %d) failed", sock->get_fd(), address, port, backlog); goto __cleanup; } if (sock->get_name() < 0) { swoole_sys_warning("getsockname(%d) failed", sock->get_fd()); __cleanup: sock->free(); return nullptr; } return sock; } Socket *make_socket(int fd, FdType fd_type) { auto *socket = new Socket(); socket->fd = fd; socket->fd_type = fd_type; socket->removed = 1; return socket; } } // namespace swoole ================================================ FILE: src/network/stream.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" #include "swoole_client.h" namespace swoole { namespace network { static void Stream_onConnect(Client *cli) { auto *stream = static_cast(cli->object); if (stream->cancel) { cli->close(); } *reinterpret_cast(stream->buffer->str) = ntohl(stream->buffer->length - 4); if (cli->send(stream->buffer->str, stream->buffer->length, 0) < 0) { cli->close(); } else { delete stream->buffer; stream->buffer = nullptr; } } static void Stream_onError(const Client *cli) { auto *stream = static_cast(cli->object); stream->errCode = swoole_get_last_error(); swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_CONNECT_FAIL, " connect() failed (%d: %s) while connecting to worker process", stream->errCode, swoole_strerror(stream->errCode)); if (!stream->response) { return; } stream->response(stream, nullptr, 0); delete stream; } static void Stream_onReceive(const Client *cli, const char *data, uint32_t length) { auto *stream = static_cast(cli->object); if (length == 4) { cli->socket->close_wait = 1; } else { stream->response(stream, data + 4, length - 4); } } static void Stream_onClose(Client *cli) { swoole_event_defer( [](void *data) { const auto *cli = static_cast(data); delete static_cast(cli->object); }, cli); } Stream::Stream(const char *dst_host, int dst_port, SocketType type) : client(type, true) { if (client.socket == nullptr) { return; } client.onConnect = Stream_onConnect; client.onReceive = Stream_onReceive; client.onError = Stream_onError; client.onClose = Stream_onClose; client.object = this; client.open_length_check = true; set_protocol(&client.protocol); if (client.connect(dst_host, dst_port, -1, 0) < 0) { swoole_sys_warning("failed to connect to [%s:%d]", dst_host, dst_port); return; } connected = true; } Stream *Stream::create(const char *dst_host, int dst_port, SocketType type) { auto *stream = new Stream(dst_host, dst_port, type); if (!stream->connected) { delete stream; return nullptr; } else { return stream; } } Stream::~Stream() { delete buffer; } /** * Stream Protocol: Length(32bit/Network Byte Order) + Body */ void Stream::set_protocol(Protocol *protocol) { protocol->get_package_length = Protocol::default_length_func; protocol->package_length_type = 'N'; protocol->package_length_size = swoole_type_size(protocol->package_length_type); protocol->package_body_offset = 4; protocol->package_length_offset = 0; } void Stream::set_max_length(uint32_t max_length) { client.protocol.package_max_length = max_length; } int Stream::send(const char *data, size_t length) { assert(data != nullptr); assert(length > 0); if (buffer == nullptr) { buffer = new String(swoole_size_align(length + 4, swoole_pagesize())); buffer->length = 4; } buffer->append(data, length); return SW_OK; } ssize_t Stream::recv_sync(Socket *sock, void *_buf, size_t _len) { int tmp = 0; ssize_t ret = sock->recv_sync(&tmp, sizeof(tmp), MSG_WAITALL); if (ret <= 0) { return SW_ERR; } const int length = static_cast(ntohl(tmp)); if (length <= 0 || length > static_cast(_len)) { return SW_ERR; } return sock->recv_sync(_buf, length, MSG_WAITALL); } } // namespace network } // namespace swoole ================================================ FILE: src/os/async_thread.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_pipe.h" #include "swoole_async.h" #include "swoole_util.h" #include "swoole_thread.h" #include #include #include #include #include #include #include #include static std::mutex async_thread_lock; static std::shared_ptr async_thread_pool; swoole::AsyncThreads *sw_async_threads() { return SwooleTG.async_threads; } namespace swoole { namespace async { //------------------------------------------------------------------------------- class EventQueue { public: void push(AsyncEvent *event) { queue_.push(event); } AsyncEvent *pop() { if (queue_.empty()) { return nullptr; } AsyncEvent *retval = queue_.front(); queue_.pop(); return retval; } double get_max_wait_time() const { if (queue_.empty()) { return 0; } const AsyncEvent *event = queue_.front(); return microtime() - event->timestamp; } size_t count() const { return queue_.size(); } bool empty() const { return queue_.empty(); } private: std::queue queue_; }; class ThreadPool { public: ThreadPool(size_t _core_worker_num, size_t _worker_num, double _max_wait_time, double _max_idle_time) { running = false; core_worker_num = _core_worker_num == 0 ? SW_CPU_NUM : SW_MAX(1, _core_worker_num); worker_num = _worker_num == 0 ? SW_CPU_NUM * SW_AIO_THREAD_NUM_MULTIPLE : SW_MAX(core_worker_num, _worker_num); max_wait_time = _max_wait_time == 0 ? SW_AIO_TASK_MAX_WAIT_TIME : _max_wait_time; max_idle_time = _max_idle_time == 0 ? SW_AIO_THREAD_MAX_IDLE_TIME : _max_idle_time; } ~ThreadPool() { shutdown(); } bool is_running() const { return running; } bool start() { running = true; current_task_id = 0; for (size_t i = 0; i < core_worker_num; i++) { create_thread(true); } return true; } bool shutdown() { if (!running) { return false; } event_mutex.lock(); running = false; event_mutex.unlock(); _cv.notify_all(); for (auto &i : threads) { std::thread *_thread = i.second; if (_thread->joinable()) { _thread->join(); } delete _thread; } threads.clear(); return true; } void schedule() { if (n_waiting == 0 && threads.size() < worker_num && max_wait_time > 0) { double _max_wait_time = queue_.get_max_wait_time(); if (_max_wait_time > max_wait_time) { size_t n = 1; /** * maybe we can find a better strategy */ if (threads.size() + n > worker_num) { n = worker_num - threads.size(); } swoole_trace_log(SW_TRACE_AIO, "Create %zu thread due to wait %fs, we will have %zu threads", n, _max_wait_time, threads.size() + n); while (n--) { create_thread(); } } } } AsyncEvent *dispatch(const AsyncEvent *request) { auto _event_copy = new AsyncEvent(*request); event_mutex.lock(); schedule(); _event_copy->task_id = current_task_id++; _event_copy->timestamp = microtime(); _event_copy->pipe_socket = SwooleTG.async_threads->write_socket; queue_.push(_event_copy); event_mutex.unlock(); _cv.notify_one(); swoole_debug("push and notify one: %f", microtime()); return _event_copy; } size_t get_worker_num() const { return threads.size(); } size_t get_queue_size() { std::unique_lock lock(event_mutex); return queue_.count(); } void release_thread(std::thread::id tid) { auto i = threads.find(tid); if (i == threads.end()) { swoole_warning("AIO thread#%s is missing", swoole_thread_id_to_str(tid).c_str()); return; } std::thread *_thread = i->second; swoole_trace_log(SW_TRACE_AIO, "release idle thread#%s, we have %zu now", swoole_thread_id_to_str(tid).c_str(), threads.size() - 1); if (_thread->joinable()) { _thread->join(); } threads.erase(i); delete _thread; } static void release_callback(AsyncEvent *event) { auto *tid = static_cast(event->object); SwooleTG.async_threads->pool->release_thread(*tid); delete tid; // balance SwooleTG.async_threads->task_num++; } void notify_one() { _cv.notify_one(); } private: void create_thread(bool is_core_worker = false); void main_func(bool is_core_worker); size_t core_worker_num; size_t worker_num; double max_wait_time; double max_idle_time; bool running; std::atomic n_waiting{0}; std::atomic n_closing{0}; size_t current_task_id = 0; std::unordered_map threads; EventQueue queue_; std::mutex event_mutex; std::condition_variable _cv; }; void ThreadPool::main_func(const bool is_core_worker) { bool exit_flag = false; swoole_thread_init(false); while (running) { bool timeout = false; std::unique_lock lock(event_mutex); ++n_waiting; if (is_core_worker || max_idle_time <= 0) { _cv.wait(lock, [this] { return !queue_.empty() || !running; }); } else { timeout = !_cv.wait_for(lock, std::chrono::microseconds(static_cast(max_idle_time) * 1000 * 1000), [this] { return !queue_.empty() || !running; }); } --n_waiting; AsyncEvent *event = queue_.pop(); lock.unlock(); swoole_debug("%s: %f", event ? "pop 1 event" : "no event", microtime()); if (event) { if (sw_unlikely(event->handler == nullptr)) { event->error = SW_ERROR_AIO_BAD_REQUEST; event->retval = -1; } else if (sw_unlikely(event->canceled)) { event->error = SW_ERROR_AIO_CANCELED; event->retval = -1; } else { event->handler(event); } swoole_trace_log(SW_TRACE_AIO, "aio_thread %s. ret=%ld, error=%d", event->retval > 0 ? "ok" : "failed", event->retval, event->error); _send_event: if (event->pipe_socket->write_sync(&event, sizeof(event)) <= 0) { swoole_sys_warning("sendto swoole_aio_pipe_write failed"); delete event; } // exit if (exit_flag) { --n_closing; break; } } else if (timeout) { if (n_closing != 0) { // wait for the next round continue; } /* notifies the main thread to release this thread */ event = new AsyncEvent; event->object = new std::thread::id(std::this_thread::get_id()); event->callback = release_callback; event->pipe_socket = SwooleG.aio_default_socket; event->canceled = false; ++n_closing; exit_flag = true; goto _send_event; } } swoole_thread_clean(false); } void ThreadPool::create_thread(const bool is_core_worker) { try { auto *_thread = new std::thread([this, is_core_worker]() { main_func(is_core_worker); }); threads[_thread->get_id()] = _thread; } catch (const std::system_error &e) { swoole_sys_notice("create aio thread failed, please check your system configuration or adjust aio_worker_num"); } } AsyncEvent *dispatch(const AsyncEvent *request) { if (sw_unlikely(!SwooleTG.async_threads)) { SwooleTG.async_threads = new AsyncThreads(); } AsyncEvent *event = SwooleTG.async_threads->pool->dispatch(request); if (sw_likely(event)) { SwooleTG.async_threads->task_num++; } return event; } //------------------------------------------------------------------------------- } // namespace async int AsyncThreads::callback(Reactor *reactor, Event *event) { AsyncEvent *events[SW_AIO_EVENT_NUM]; ssize_t n = event->socket->read(events, sizeof(AsyncEvent *) * SW_AIO_EVENT_NUM); if (n < 0) { swoole_sys_warning("read() aio events failed"); return SW_ERR; } for (size_t i = 0; i < n / sizeof(AsyncEvent *); i++) { AsyncEvent *_event = events[i]; if (!_event->canceled) { _event->callback(_event); } SwooleTG.async_threads->task_num--; delete _event; } return SW_OK; } size_t AsyncThreads::get_worker_num() const { return pool ? pool->get_worker_num() : 0; } size_t AsyncThreads::get_queue_size() const { return pool ? pool->get_queue_size() : 0; } void AsyncThreads::notify_one() const { if (pool) { pool->notify_one(); } } AsyncThreads::AsyncThreads() { if (!SwooleTG.reactor) { swoole_warning("no event loop, cannot initialized"); throw Exception(SW_ERROR_WRONG_OPERATION); } pipe = new Pipe(false); if (!pipe->ready()) { delete pipe; pipe = nullptr; swoole_throw_error(SW_ERROR_SYSTEM_CALL_FAIL); return; } read_socket = pipe->get_socket(false); write_socket = pipe->get_socket(true); read_socket->fd_type = SW_FD_AIO; write_socket->fd_type = SW_FD_AIO; swoole_event_add(read_socket, SW_EVENT_READ); sw_reactor()->add_destroy_callback([](void *data) { if (!SwooleTG.async_threads) { return; } swoole_event_del(SwooleTG.async_threads->read_socket); delete SwooleTG.async_threads; SwooleTG.async_threads = nullptr; }); sw_reactor()->set_exit_condition(Reactor::EXIT_CONDITION_AIO_TASK, [](Reactor *reactor, size_t &event_num) -> bool { if (SwooleTG.async_threads && SwooleTG.async_threads->task_num == 0) { event_num--; } return true; }); async_thread_lock.lock(); if (!async_thread_pool) { async_thread_pool = std::make_shared( SwooleG.aio_core_worker_num, SwooleG.aio_worker_num, SwooleG.aio_max_wait_time, SwooleG.aio_max_idle_time); } if (!async_thread_pool->is_running()) { async_thread_pool->start(); } pool = async_thread_pool; async_thread_lock.unlock(); SwooleG.aio_default_socket = write_socket; SwooleTG.async_threads = this; } AsyncThreads::~AsyncThreads() { pool.reset(); async_thread_lock.lock(); /** * When the reference count is 1, it means that all reactor threads have ended * and all aio threads can be terminated. */ if (async_thread_pool.use_count() == 1) { async_thread_pool->shutdown(); } async_thread_lock.unlock(); pipe->close(); read_socket = nullptr; write_socket = nullptr; delete pipe; pipe = nullptr; } }; // namespace swoole ================================================ FILE: src/os/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2018 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_async.h" #include "swoole_signal.h" #include "swoole_api.h" #include #include #if defined(__linux__) #include #elif defined(__FreeBSD__) #include #endif #if defined(__APPLE__) && defined(HAVE_CCRANDOMGENERATEBYTES) #include #if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) || \ (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) #define OPENSSL_APPLE_CRYPTO_RANDOM 1 #include #include #endif #endif #include #include #if __APPLE__ int swoole_daemon(int nochdir, int noclose) { pid_t pid; if (!nochdir && chdir("/") != 0) { swoole_sys_warning("chdir() failed"); return -1; } if (!noclose) { int fd = open("/dev/null", O_RDWR); if (fd < 0) { swoole_sys_warning("open() failed"); return -1; } if (dup2(fd, 0) < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) { close(fd); swoole_sys_warning("dup2() failed"); return -1; } close(fd); } pid = swoole_fork(SW_FORK_DAEMON); if (pid < 0) { swoole_sys_warning("fork() failed"); return -1; } if (pid > 0) { _exit(0); } if (setsid() < 0) { swoole_sys_warning("setsid() failed"); return -1; } return 0; } #else int swoole_daemon(int nochdir, int noclose) { if (swoole_fork(SW_FORK_PRECHECK) < 0) { return -1; } auto rv = daemon(nochdir, noclose); if (rv == 0) { /** * The daemon function forks the process multiple times, and the pid changes, * which can lead to PHP assertion failures. * After PHP 8.5, it is required that the process must call `refresh_memory_manager()` after forking, * but this does not seem to take into account the invocation of the daemon function. * If not modified, it will crash during shutdown, and users will think it is a bug in Swoole. */ if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_FORK)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_FORK, nullptr); } } return rv; } #endif #ifdef HAVE_GETRANDOM #include #else static ssize_t getrandom(void *buffer, size_t size, unsigned int __flags) { #if defined(HAVE_CCRANDOMGENERATEBYTES) /* * arc4random_buf on macOS uses ccrng_generate internally from which * the potential error is silented to respect the portable arc4random_buf interface contract */ if (CCRandomGenerateBytes(buffer, size) == kCCSuccess) { return size; } return -1; #elif defined(HAVE_ARC4RANDOM) arc4random_buf(buffer, size); return size; #else int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { return -1; } size_t read_bytes; ssize_t n; for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) { n = read(fd, (char *) buffer + read_bytes, size - read_bytes); if (n <= 0) { break; } } close(fd); return read_bytes; #endif } #endif #ifdef __ANDROID__ static ssize_t getrandom(char *buf, size_t buflen, uint flags) { int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { return -1; } ssize_t n = read(fd, buf, buflen); close(fd); return n; } int pthread_getname_np(pthread_t thread, char *buf, size_t len) { sw_snprintf(buf, len, "thread-%lu", (unsigned long) thread); return 0; } #endif size_t swoole_random_bytes(char *buf, size_t size) { size_t read_bytes = 0; while (read_bytes < size) { size_t amount_to_read = size - read_bytes; ssize_t n = getrandom(buf + read_bytes, amount_to_read, 0); if (n == -1) { if (errno == EINTR || errno == EAGAIN) { continue; } else { break; } } read_bytes += (size_t) n; } return read_bytes; } bool swoole_is_root_user() { return geteuid() == 0; } void swoole_set_isolation(const std::string &group_, const std::string &user_, const std::string &chroot_) { group *_group = nullptr; passwd *_passwd = nullptr; // get group info if (!group_.empty()) { _group = getgrnam(group_.c_str()); if (!_group) { swoole_warning("get group [%s] info failed", group_.c_str()); } } // get user info if (!user_.empty()) { _passwd = getpwnam(user_.c_str()); if (!_passwd) { swoole_warning("get user [%s] info failed", user_.c_str()); } } // set process group if (_group && setgid(_group->gr_gid) < 0) { swoole_sys_warning("setgid to [%s] failed", group_.c_str()); } // set process user if (_passwd && setuid(_passwd->pw_uid) < 0) { swoole_sys_warning("setuid to [%s] failed", user_.c_str()); } // chroot if (!chroot_.empty()) { if (::chroot(chroot_.c_str()) == 0) { if (chdir("/") < 0) { swoole_sys_warning("chdir('/') failed"); } } else { swoole_sys_warning("chroot('%s') failed", chroot_.c_str()); } } } void swoole_set_process_death_signal(int signal) { #if defined(__linux__) prctl(PR_SET_PDEATHSIG, signal); #elif defined(__FreeBSD__) procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signal); #else #warning "no `PDEATHSIG` supports" #endif } #ifdef HAVE_CPU_AFFINITY int swoole_set_cpu_affinity(cpu_set_t *set) { #ifdef __FreeBSD__ return cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(*set), set); #else return sched_setaffinity(getpid(), sizeof(*set), set); #endif } int swoole_get_cpu_affinity(cpu_set_t *set) { #ifdef __FreeBSD__ return cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(*set), set); #else return sched_getaffinity(getpid(), sizeof(*set), set); #endif } #endif #if defined(__linux__) #include /* syscall(SYS_gettid) */ #elif defined(__FreeBSD__) #include /* pthread_getthreadid_np() */ #elif defined(__OpenBSD__) #include /* getthrid() */ #elif defined(_AIX) #include /* thread_self() */ #elif defined(__NetBSD__) #include /* _lwp_self() */ #elif defined(__CYGWIN__) || defined(WIN32) #include /* GetCurrentThreadId() */ #endif long swoole_thread_get_native_id() { #ifdef __APPLE__ uint64_t native_id; (void) pthread_threadid_np(NULL, &native_id); #elif defined(__linux__) pid_t native_id = syscall(SYS_gettid); #elif defined(__FreeBSD__) int native_id = pthread_getthreadid_np(); #elif defined(__OpenBSD__) pid_t native_id = getthrid(); #elif defined(_AIX) tid_t native_id = thread_self(); #elif defined(__NetBSD__) lwpid_t native_id = _lwp_self(); #elif defined(__CYGWIN__) || defined(WIN32) DWORD native_id = GetCurrentThreadId(); #endif return native_id; } static bool check_pthread_return_value(int rc) { if (rc == 0) { return true; } else { swoole_set_last_error(rc); return false; } } bool swoole_thread_set_name(const char *name) { #if defined(__APPLE__) return check_pthread_return_value(pthread_setname_np(name)); #else return check_pthread_return_value(pthread_setname_np(pthread_self(), name)); #endif } bool swoole_thread_get_name(char *buf, size_t len) { return check_pthread_return_value(pthread_getname_np(pthread_self(), buf, len)); } std::string swoole_thread_id_to_str(std::thread::id id) { std::stringstream ss; ss << id; return ss.str(); } namespace swoole { GethostbynameRequest::GethostbynameRequest(std::string _name, int _family) : name(std::move(_name)), family(_family) {} GetaddrinfoRequest::GetaddrinfoRequest( std::string _hostname, int _family, int _socktype, int _protocol, std::string _service) : hostname(std::move(_hostname)), service(std::move(_service)) { family = _family; socktype = _socktype; protocol = _protocol; count = 0; error = 0; } namespace async { void handler_gethostbyname(AsyncEvent *event) { auto req = dynamic_cast(event->data.get()); event->retval = network::gethostbyname(req); if (event->retval < 0) { event->error = swoole_get_last_error(); } else { event->error = 0; } } void handler_getaddrinfo(AsyncEvent *event) { auto req = dynamic_cast(event->data.get()); event->retval = network::getaddrinfo(req); event->error = req->error; } } // namespace async } // namespace swoole ================================================ FILE: src/os/file.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_file.h" int swoole_tmpfile(char *filename) { #if defined(HAVE_MKOSTEMP) && defined(HAVE_EPOLL) int tmp_fd = mkostemp(filename, O_WRONLY | O_CREAT); #else int tmp_fd = mkstemp(filename); #endif if (tmp_fd < 0) { swoole_sys_warning("mkstemp('%s') failed", filename); return SW_ERR; } else { return tmp_fd; } } namespace swoole { ssize_t file_get_size(FILE *fp) { fflush(fp); return file_get_size(fileno(fp)); } ssize_t file_get_size(const std::string &filename) { File file(filename, File::READ); if (!file.ready()) { swoole_set_last_error(errno); return -1; } return file.get_size(); } ssize_t file_get_size(int fd) { FileStatus file_stat; if (fstat(fd, &file_stat) < 0) { swoole_set_last_error(errno); return -1; } if (!S_ISREG(file_stat.st_mode)) { swoole_set_last_error(EISDIR); return -1; } return file_stat.st_size; } std::shared_ptr file_get_contents(const std::string &filename) { File fp(filename, O_RDONLY); if (!fp.ready()) { swoole_sys_warning("open('%s') failed", filename.c_str()); return nullptr; } ssize_t filesize = fp.get_size(); if (filesize < 0) { return nullptr; } else if (filesize == 0) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_FILE_EMPTY, "file[%s] is empty", filename.c_str()); return nullptr; } else if (filesize > SwooleG.max_file_content) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_TOO_LARGE, "file[%s] is too large", filename.c_str()); return nullptr; } std::shared_ptr content = std::make_shared(filesize + 1); ssize_t read_bytes = fp.read_all(content->str, filesize); content->length = read_bytes; content->str[read_bytes] = '\0'; return content; } File make_tmpfile() { char *tmpfile = sw_tg_buffer()->str; size_t l = swoole_strlcpy(tmpfile, SwooleG.task_tmpfile.c_str(), SW_TASK_TMP_PATH_SIZE); int tmp_fd = swoole_tmpfile(tmpfile); if (tmp_fd < 0) { return File(-1); } else { return {tmp_fd, std::string(tmpfile, l)}; } } bool file_put_contents(const std::string &filename, const char *content, size_t length) { if (length == 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_EMPTY, "content is empty"); return false; } if (length > SwooleG.max_file_content) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_TOO_LARGE, "content is too large"); return false; } File file(filename, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (!file.ready()) { swoole_sys_warning("open('%s') failed", filename.c_str()); return false; } return file.write_all(content, length); } bool file_exists(const std::string &filename) { return access(filename.c_str(), F_OK) == 0; } File::File(const std::string &path, int oflags) { fd_ = -1; open(path, oflags); } File::File(const std::string &path, int oflags, int mode) { fd_ = -1; open(path, oflags, mode); } bool File::open(const std::string &path, int oflags, int mode) { if (fd_ != -1) { ::close(fd_); } if (oflags & CREATE) { fd_ = ::open(path.c_str(), oflags, mode == 0 ? 0644 : mode); } else { fd_ = ::open(path.c_str(), oflags); } path_ = path; flags_ = oflags; return ready(); } bool File::close() { if (fd_ == -1) { return false; } int tmp_fd = fd_; fd_ = -1; return ::close(tmp_fd) == 0; } bool File::stat(FileStatus *_stat) const { if (::fstat(fd_, _stat) < 0) { swoole_sys_warning("fstat() failed"); return false; } return true; } File::~File() { if (fd_ >= 0) { ::close(fd_); } } static swReturnCode catch_fs_error(const ssize_t rv, const int error) { if (rv == 0) { return SW_CLOSE; } if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK) { return SW_CONTINUE; } return SW_ERROR; } size_t File::write_all(const void *data, size_t len) const { size_t written_bytes = 0; while (written_bytes < len) { ssize_t n; if (flags_ & APPEND) { n = write((char *) data + written_bytes, len - written_bytes); } else { n = pwrite((char *) data + written_bytes, len - written_bytes, written_bytes); } if (n > 0) { written_bytes += n; } else { const auto rc = catch_fs_error(n, errno); if (rc == SW_ERROR) { swoole_sys_warning("pwrite(%d, %p, %lu, %lu) failed", fd_, data, len - written_bytes, written_bytes); } else if (rc == SW_CONTINUE) { continue; } break; } } return written_bytes; } size_t File::read_all(void *buf, size_t len) const { size_t read_bytes = 0; while (read_bytes < len) { ssize_t n = pread((char *) buf + read_bytes, len - read_bytes, read_bytes); if (n > 0) { read_bytes += n; } else { const auto rc = catch_fs_error(n, errno); if (rc == SW_ERROR) { swoole_sys_warning("pread(%d, %p, %lu, %lu) failed", fd_, buf, len - read_bytes, read_bytes); } else if (rc == SW_CONTINUE) { continue; } break; } } return read_bytes; } ssize_t File::read_line(void *_buf, size_t _n) const { char *buf = (char *) _buf; auto offset = get_offset(); ssize_t read_bytes = read(buf, _n - 1); if (read_bytes <= 0) { return read_bytes; } for (ssize_t i = 0; i < read_bytes; ++i) { if (buf[i] == '\0' || buf[i] == '\n') { buf[i + 1] = '\0'; set_offset(offset + i + 1); return i + 1; } } buf[read_bytes] = '\0'; set_offset(offset + read_bytes + 1); return read_bytes; } std::shared_ptr File::read_content() const { ssize_t n = 0; auto data = std::make_shared(SW_BUFFER_SIZE_STD); while (true) { n = read(data->str + data->length, data->size - data->length); if (n <= 0) { break; } data->grow((size_t) n); } return data; } } // namespace swoole ================================================ FILE: src/os/msg_queue.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_msg_queue.h" namespace swoole { #ifdef HAVE_MSGQUEUE #include #include bool MsgQueue::destroy() { if (msgctl(msg_id_, IPC_RMID, nullptr) < 0) { swoole_sys_warning("msgctl(%d, IPC_RMID) failed", msg_id_); return false; } msg_id_ = -1; return true; } void MsgQueue::set_blocking(const bool blocking) { if (blocking == 0) { flags_ = flags_ | IPC_NOWAIT; } else { flags_ = flags_ & (~IPC_NOWAIT); } } MsgQueue::MsgQueue(key_t msg_key, bool blocking, int perms) { if (perms <= 0 || perms >= 01000) { perms = 0666; } msg_key_ = msg_key; flags_ = 0; perms_ = perms; blocking_ = blocking; msg_id_ = msgget(msg_key, IPC_CREAT | perms); if (msg_id_ < 0) { swoole_sys_warning("msgget() failed"); } else { set_blocking(blocking); } } MsgQueue::~MsgQueue() { // private queue must be destroyed if (msg_key_ == IPC_PRIVATE && msg_id_ >= 0) { destroy(); } } ssize_t MsgQueue::pop(QueueNode *data, size_t mdata_size) const { ssize_t ret = msgrcv(msg_id_, data, mdata_size, data->mtype, flags_); if (ret < 0) { swoole_set_last_error(errno); if (errno != ENOMSG && errno != EINTR) { swoole_sys_warning("msgrcv(%d, %zu, %ld) failed", msg_id_, mdata_size, data->mtype); } } return ret; } bool MsgQueue::push(const QueueNode *in, size_t mdata_length) const { while (true) { if (msgsnd(msg_id_, in, mdata_length, flags_) == 0) { return true; } if (errno == EINTR) { continue; } if (errno != EAGAIN) { swoole_sys_warning("msgsnd(%d, %lu, %ld) failed", msg_id_, mdata_length, in->mtype); } swoole_set_last_error(errno); break; } return false; } bool MsgQueue::stat(size_t *queue_num, size_t *queue_bytes) const { msqid_ds _stat; if (msgctl(msg_id_, IPC_STAT, &_stat) == 0) { *queue_num = _stat.msg_qnum; #ifndef __NetBSD__ *queue_bytes = _stat.msg_cbytes; #else *queue_bytes = __stat._msg_cbytes; #endif return true; } return false; } bool MsgQueue::set_capacity(size_t queue_bytes) const { msqid_ds _stat; if (msgctl(msg_id_, IPC_STAT, &_stat) != 0) { return false; } _stat.msg_qbytes = queue_bytes; if (msgctl(msg_id_, IPC_SET, &_stat)) { swoole_sys_warning("msgctl(msqid=%d, IPC_SET, msg_qbytes=%lu) failed", msg_id_, queue_bytes); return false; } return true; } #else MsgQueue::MsgQueue(key_t msg_key, bool blocking, int perms) { swoole_error("current platform does not support `sysvmsg`"); } void MsgQueue::set_blocking(bool blocking) {} bool MsgQueue::set_capacity(size_t queue_bytes) const { return false; } bool MsgQueue::push(const QueueNode *in, size_t mdata_length) const { return false; } ssize_t MsgQueue::pop(QueueNode *out, size_t mdata_size) const { return -1; } bool MsgQueue::stat(size_t *queue_num, size_t *queue_bytes) const { return false; } bool MsgQueue::destroy() { return false; } MsgQueue::~MsgQueue() { } #endif } // namespace swoole ================================================ FILE: src/os/pipe.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_pipe.h" #include "swoole_socket.h" namespace swoole { using network::Socket; void SocketPair::init_socket(int master_fd, int worker_fd) { master_socket = make_socket(master_fd, SW_FD_PIPE); worker_socket = make_socket(worker_fd, SW_FD_PIPE); set_blocking(blocking); } Pipe::Pipe(bool _blocking) : SocketPair(_blocking) { if (pipe(socks) < 0) { swoole_sys_warning("pipe() failed"); return; } // socks[0]: (read end) // socks[1]: (write end) init_socket(socks[1], socks[0]); } void SocketPair::set_blocking(bool _blocking) { if (_blocking) { worker_socket->set_block(); master_socket->set_block(); } else { worker_socket->set_nonblock(); master_socket->set_nonblock(); } blocking = _blocking; } ssize_t SocketPair::read(void *data, size_t length) const { if (blocking) { return worker_socket->read_sync(data, length); } else { return worker_socket->read(data, length); } } ssize_t SocketPair::write(const void *data, size_t length) const { if (blocking) { return master_socket->write_sync(data, length); } else { return master_socket->write(data, length); } } void SocketPair::clean() const { char buf[1024]; while (worker_socket->wait_event(0, SW_EVENT_READ) == SW_OK) { if (worker_socket->read(buf, sizeof(buf)) <= 0) { break; } } } bool SocketPair::close(int which) { if (which == SW_PIPE_CLOSE_MASTER) { if (master_socket == nullptr) { return false; } master_socket->free(); master_socket = nullptr; } else if (which == SW_PIPE_CLOSE_WORKER) { if (worker_socket == nullptr) { return false; } worker_socket->free(); worker_socket = nullptr; } else { close(SW_PIPE_CLOSE_MASTER); close(SW_PIPE_CLOSE_WORKER); } return true; } SocketPair::~SocketPair() { if (master_socket) { close(SW_PIPE_CLOSE_MASTER); } if (worker_socket) { close(SW_PIPE_CLOSE_WORKER); } } } // namespace swoole ================================================ FILE: src/os/process_pool.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_memory.h" #include "swoole_socket.h" #include "swoole_string.h" #include "swoole_msg_queue.h" #include "swoole_pipe.h" #include "swoole_server.h" #include "swoole_util.h" #include "swoole_process_pool.h" #include "swoole_client.h" SW_THREAD_LOCAL swoole::WorkerGlobal SwooleWG = {}; namespace swoole { using network::Socket; using network::Stream; static inline swReturnCode catch_system_error(int error) { switch (error) { case SW_SUCCESS: case EAGAIN: case EINTR: return SW_CONTINUE; default: return SW_ERROR; } } static inline void worker_end_callback() { swoole_timer_select(); swoole_signal_dispatch(); } /** * Process manager */ int ProcessPool::create(uint32_t _worker_num, key_t _msgqueue_key, swIPCMode _ipc_mode) { #ifndef HAVE_MSGQUEUE if (_ipc_mode == SW_IPC_MSGQUEUE) { swoole_warning("current platform does not support `sysvmsg`"); return SW_ERR; } #endif worker_num = _worker_num; /** * Shared memory is used here */ workers = static_cast(sw_mem_pool()->alloc(_worker_num * sizeof(Worker))); if (workers == nullptr) { swoole_sys_warning("malloc[1] failed"); return SW_ERR; } if (create_message_box(SW_MESSAGE_BOX_SIZE) < 0) { return SW_ERR; } if (_ipc_mode == SW_IPC_MSGQUEUE) { use_msgqueue = 1; msgqueue_key = _msgqueue_key; queue = new MsgQueue(msgqueue_key); if (!queue->ready()) { delete queue; queue = nullptr; return SW_ERR; } } else if (_ipc_mode == SW_IPC_UNIXSOCK) { pipes = new std::vector>; SW_LOOP_N(_worker_num) { auto sock = new UnixSocket(true, SOCK_DGRAM); if (!sock->ready()) { delete sock; delete pipes; pipes = nullptr; return SW_ERR; } pipes->emplace_back(sock); workers[i].pipe_master = sock->get_socket(true); workers[i].pipe_worker = sock->get_socket(false); workers[i].pipe_object = sock; } } else if (_ipc_mode == SW_IPC_SOCKET) { use_socket = 1; stream_info_ = new StreamInfo(); } else { _ipc_mode = SW_IPC_NONE; } map_ = new std::unordered_map; ipc_mode = _ipc_mode; main_loop = run_with_task_protocol; protocol_type_ = SW_PROTOCOL_TASK; max_packet_size_ = SW_INPUT_BUFFER_SIZE; max_wait_time = SW_WORKER_MAX_WAIT_TIME; SW_LOOP_N(_worker_num) { workers[i].pool = this; } return SW_OK; } int ProcessPool::create_message_box(size_t memory_size) { message_box = Channel::make(memory_size, sizeof(EventData), SW_CHAN_LOCK | SW_CHAN_SHM); if (message_box == nullptr) { return SW_ERR; } return SW_OK; } int ProcessPool::create_message_bus() { if (ipc_mode != SW_IPC_UNIXSOCK) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_UNIXSOCK"); return SW_ERR; } if (message_bus) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "the message bus has been created"); return SW_ERR; } auto *msg_id = static_cast(sw_mem_pool()->alloc(sizeof(sw_atomic_long_t))); if (msg_id == nullptr) { swoole_sys_warning("malloc[1] failed"); return SW_ERR; } *msg_id = 1; message_bus = new MessageBus(); message_bus->set_id_generator([msg_id]() { return sw_atomic_fetch_add(msg_id, 1); }); size_t ipc_max_size; #ifndef __linux__ ipc_max_size = SW_IPC_MAX_SIZE; #else int bufsize; /** * Get the maximum ipc[unix socket with dgram] transmission length */ if (workers[0].pipe_master->get_option(SOL_SOCKET, SO_SNDBUF, &bufsize) != 0) { bufsize = SW_IPC_MAX_SIZE; } ipc_max_size = SW_MIN(bufsize, SW_IPC_BUFFER_MAX_SIZE); #endif message_bus->set_buffer_size(ipc_max_size); if (!message_bus->alloc_buffer()) { return SW_ERR; } return SW_OK; } int ProcessPool::listen(const char *socket_file, int backlog) const { if (ipc_mode != SW_IPC_SOCKET) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_SOCKET"); return SW_ERR; } stream_info_->socket_file = sw_strdup(socket_file); if (stream_info_->socket_file == nullptr) { return SW_ERR; } stream_info_->socket_port = 0; stream_info_->socket = make_server_socket(SW_SOCK_UNIX_STREAM, stream_info_->socket_file, 0, backlog); if (!stream_info_->socket) { return SW_ERR; } return SW_OK; } int ProcessPool::listen(const char *host, int port, int backlog) const { if (ipc_mode != SW_IPC_SOCKET) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_SOCKET"); return SW_ERR; } stream_info_->socket_file = sw_strdup(host); if (stream_info_->socket_file == nullptr) { return SW_ERR; } stream_info_->socket_port = port; stream_info_->socket = make_server_socket(SW_SOCK_TCP, host, port, backlog); if (!stream_info_->socket) { return SW_ERR; } return SW_OK; } void ProcessPool::set_protocol(ProtocolType _protocol_type) { switch (_protocol_type) { case SW_PROTOCOL_TASK: main_loop = run_with_task_protocol; break; case SW_PROTOCOL_STREAM: main_loop = run_with_stream_protocol; break; case SW_PROTOCOL_MESSAGE: main_loop = run_with_message_protocol; break; default: abort(); break; } protocol_type_ = _protocol_type; } int ProcessPool::start_check() { if (ipc_mode == SW_IPC_SOCKET && (stream_info_ == nullptr || stream_info_->socket == nullptr)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "must first listen to an tcp port"); return SW_ERR; } running = started = true; master_pid = getpid(); swoole_set_worker_type(SW_MASTER); if (async) { main_loop = run_async; } SW_LOOP_N(worker_num) { workers[i].pool = this; workers[i].id = start_id + i; workers[i].type = type; if (workers[i].pipe_worker) { workers[i].pipe_worker->buffer_size = UINT_MAX; } if (workers[i].pipe_master) { workers[i].pipe_master->buffer_size = UINT_MAX; } } return SW_OK; } /** * start workers */ int ProcessPool::start() { if (start_check() < 0) { return SW_ERR; } if (onStart) { onStart(this); } SW_LOOP_N(worker_num) { if (spawn(&(workers[i])) < 0) { return SW_ERR; } } return SW_OK; } int ProcessPool::schedule() { // schedule by system message queue if (schedule_by_sysvmsg) { return 0; } uint32_t target_worker_id = 0; uint8_t found = 0; for (uint32_t i = 0; i < worker_num + 1; i++) { target_worker_id = sw_atomic_fetch_add(&round_id, 1) % worker_num; if (workers[target_worker_id].is_idle()) { found = 1; break; } } if (found == 0) { scheduler_warning = 1; } return target_worker_id; } int ProcessPool::response(const char *data, uint32_t length) const { if (data == nullptr || length == 0 || stream_info_ == nullptr || stream_info_->last_connection == nullptr || stream_info_->response_buffer == nullptr) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); return SW_ERR; } stream_info_->response_buffer->append(data, length); return SW_OK; } bool ProcessPool::send_message(WorkerId worker_id, const char *message, size_t l_message) const { Worker *worker = get_worker(worker_id); if (message_bus) { SendData _task{}; _task.info.reactor_id = swoole_get_worker_id(); _task.info.len = l_message; _task.data = message; return message_bus->write(worker->pipe_master, &_task); } else { return worker->pipe_master->send_async(message, l_message); } } int ProcessPool::push_message(const EventData *msg) const { if (message_box->push(msg, msg->size()) < 0) { return SW_ERR; } return swoole_kill(master_pid, SIGIO); } int ProcessPool::push_message(uint8_t _type, const void *data, size_t length) const { if (!message_box) { return SW_ERR; } EventData msg; assert(length < sizeof(msg.data)); msg.info = {}; msg.info.type = _type; msg.info.len = length; memcpy(msg.data, data, length); return push_message(&msg); } int ProcessPool::pop_message(void *data, size_t size) const { if (!message_box) { return SW_ERR; } return message_box->pop(data, size); } swResultCode ProcessPool::dispatch(EventData *data, int *dst_worker_id) { if (use_socket) { Stream *stream = Stream::create(stream_info_->socket_file, 0, SW_SOCK_UNIX_STREAM); if (!stream) { return SW_ERR; } stream->response = nullptr; if (stream->send(reinterpret_cast(data), data->size()) < 0) { stream->cancel = true; delete stream; return SW_ERR; } return SW_OK; } if (*dst_worker_id < 0) { *dst_worker_id = schedule(); } *dst_worker_id += start_id; Worker *worker = get_worker(*dst_worker_id); if (worker->send_pipe_message(data, data->size(), SW_PIPE_MASTER | SW_PIPE_NONBLOCK) < 0) { swoole_warning("send %d bytes to worker#%d failed", data->size(), *dst_worker_id); return SW_ERR; } return SW_OK; } swResultCode ProcessPool::dispatch_sync(const char *data, uint32_t len) const { assert(use_socket); network::Client client(stream_info_->socket->socket_type, false); if (!client.ready()) { return SW_ERR; } if (client.connect(stream_info_->socket_file, stream_info_->socket_port, -1, 0) < 0) { return SW_ERR; } uint32_t packed_len = htonl(len); if (client.send((char *) &packed_len, 4, 0) < 0) { return SW_ERR; } if (client.send(data, len, 0) < 0) { return SW_ERR; } client.close(); return SW_OK; } swResultCode ProcessPool::dispatch_sync(EventData *data, int *dst_worker_id) { if (use_socket) { return dispatch_sync(reinterpret_cast(data), data->size()); } if (*dst_worker_id < 0) { *dst_worker_id = schedule(); } *dst_worker_id += start_id; Worker *worker = get_worker(*dst_worker_id); if (worker->send_pipe_message(data, data->size(), SW_PIPE_MASTER) < 0) { swoole_warning("send %d bytes to worker#%d failed", data->size(), *dst_worker_id); return SW_ERR; } return SW_OK; } bool ProcessPool::reload() { if (reload_task) { return false; } if (onBeforeReload) { onBeforeReload(this); } reload_task = new ReloadTask(); if (max_wait_time) { reload_task->add_timeout_killer(max_wait_time); } reload_count++; reload_init = true; reload_last_time = ::time(nullptr); return true; } void ProcessPool::stop(Worker *worker) { worker->shutdown(); if (!swoole_event_is_available()) { return; } auto reactor = sw_reactor(); if (worker->pipe_worker && !worker->pipe_worker->removed) { swoole_event_del(worker->pipe_worker); } if (onWorkerExit) { reactor->set_end_callback(Reactor::PRIORITY_TRY_EXIT, [this, worker](Reactor *reactor) { onWorkerExit(this, worker); if (reactor->if_exit()) { reactor->running = false; } }); } } void ProcessPool::reopen_logger() { sw_logger()->reopen(); if (is_master()) { kill_all_workers(SIGWINCH); } } void ProcessPool::kill_all_workers(int signo) { SW_LOOP_N(worker_num) { swoole_kill(workers[i].pid, signo); } } bool ProcessPool::shutdown() { if (is_master()) { running = false; return true; } else { return swoole_kill(master_pid, SIGTERM) == 0; } } pid_t ProcessPool::spawn(Worker *worker) { pid_t pid = swoole_fork(0); int ret_code = 0; switch (pid) { // child case 0: worker->init(); worker->pid = getpid(); swoole_set_worker_type(SW_WORKER); swoole_set_worker_id(worker->id); swoole_set_worker_pid(worker->pid); SwooleWG.worker = worker; if (async) { if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { exit(254); } sw_reactor()->ptr = this; } if (onWorkerStart != nullptr) { onWorkerStart(this, worker); } if (main_loop) { ret_code = main_loop(this, worker); } if (onWorkerStop != nullptr) { onWorkerStop(this, worker); } exit(ret_code); break; case -1: swoole_sys_warning("fork() failed"); break; // parent default: // remove old process if (worker->pid) { map_->erase(worker->pid); } worker->pid = pid; // insert new process map_->emplace(std::make_pair(pid, worker)); break; } return pid; } void ProcessPool::set_max_request(uint32_t _max_request, uint32_t _max_request_grace) { max_request = _max_request; max_request_grace = _max_request_grace; } bool ProcessPool::is_worker_running(Worker *worker) const { return running && !worker->is_shutdown() && !worker->has_exceeded_max_request(); } void ProcessPool::at_worker_enter(Worker *worker) const { if (worker->pipe_worker) { worker->pipe_worker->dont_restart = 1; } if (ipc_mode == SW_IPC_UNIXSOCK) { if (swoole_timer_is_available()) { sw_timer()->reinit(true); } else { swoole_timer_create(true); } } } void ProcessPool::at_worker_exit(Worker *worker) { if (swoole_timer_is_available()) { swoole_timer_free(); } } int ProcessPool::run_with_task_protocol(ProcessPool *pool, Worker *worker) { struct { long mtype; EventData buf; } out{}; ssize_t n = 0; out.buf.info.server_fd = worker->id; if (pool->schedule_by_sysvmsg) { out.mtype = 0; } else { out.mtype = worker->id + 1; } pool->at_worker_enter(worker); while (pool->is_worker_running(worker)) { /** * fetch task */ if (pool->use_msgqueue) { n = pool->queue->pop((QueueNode *) &out, sizeof(out.buf)); if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] msgrcv(%d) failed", worker->id, pool->queue->get_id()); break; } } else if (pool->use_socket) { Socket *conn = pool->stream_info_->socket->accept(); if (conn == nullptr) { if (catch_system_error(errno) == SW_ERROR) { swoole_sys_warning( "[Worker#%d] accept(%d) failed", worker->id, pool->stream_info_->socket->get_fd()); break; } else { goto _end; } } n = Stream::recv_sync(conn, &out.buf, sizeof(out.buf)); if (n <= 0) { conn->free(); goto _end; } pool->stream_info_->last_connection = conn; } else { worker->pipe_worker->set_timeout(msec2sec(swoole_timer_get_next_msec()), SW_TIMEOUT_READ); n = worker->pipe_worker->read_sync(&out.buf, sizeof(out.buf)); if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] read(%d) failed", worker->id, worker->pipe_worker->fd); break; } } if (n < 0) { goto _end; } if (n != (ssize_t) out.buf.size()) { swoole_warning("[Worker#%d] bad task packet, The received data-length[%ld] is inconsistent with the " "packet-length[%ld]", worker->id, n, out.buf.info.len + sizeof(out.buf.info)); } if (pool->onTask(pool, worker, &out.buf) < 0) { swoole_warning("[Worker#%d] the execution of task#%ld has failed", worker->id, pool->get_task_id(&out.buf)); } if (pool->use_socket && pool->stream_info_->last_connection) { int _end = 0; pool->stream_info_->last_connection->send_sync((void *) &_end, sizeof(_end)); pool->stream_info_->last_connection->free(); pool->stream_info_->last_connection = nullptr; } _end: worker_end_callback(); } pool->at_worker_exit(worker); return SW_OK; } int ProcessPool::recv_packet(Reactor *reactor, Event *event) { auto *pool = static_cast(reactor->ptr); ssize_t n = event->socket->read(pool->packet_buffer, pool->max_packet_size_); if (n < 0 && errno != EINTR) { swoole_sys_warning("failed to read(%d) pipe", event->fd); } RecvData msg{}; msg.info.reactor_id = -1; msg.info.len = n; msg.data = pool->packet_buffer; pool->onMessage(pool, &msg); return SW_OK; } int ProcessPool::recv_message(Reactor *reactor, Event *event) { auto *pool = static_cast(reactor->ptr); if (pool->message_bus->read(event->socket) <= 0) { return SW_OK; } auto pipe_buffer = pool->message_bus->get_buffer(); auto packet = pool->message_bus->get_packet(); RecvData msg; msg.info = pipe_buffer->info; msg.info.len = packet.length; msg.data = packet.data; pool->onMessage(pool, &msg); pool->message_bus->pop(); return SW_OK; } int ProcessPool::run_async(ProcessPool *pool, Worker *worker) { if (pool->ipc_mode == SW_IPC_UNIXSOCK && pool->onMessage) { swoole_event_add(worker->pipe_worker, SW_EVENT_READ); if (pool->message_bus) { swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, recv_message); } else { pool->packet_buffer = new char[pool->max_packet_size_]; if (pool->stream_info_) { pool->stream_info_->response_buffer = new String(SW_BUFFER_SIZE_STD); } swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, recv_packet); } } return swoole_event_wait(); } int ProcessPool::run_with_stream_protocol(ProcessPool *pool, Worker *worker) { ssize_t n; RecvData msg{}; msg.info.reactor_id = -1; pool->packet_buffer = new char[pool->max_packet_size_]; if (pool->stream_info_) { pool->stream_info_->response_buffer = new String(SW_BUFFER_SIZE_STD); } auto *outbuf = reinterpret_cast(pool->packet_buffer); outbuf->mtype = 0; pool->at_worker_enter(worker); while (pool->is_worker_running(worker)) { /** * fetch task */ if (pool->use_msgqueue) { n = pool->queue->pop(outbuf, SW_MSGMAX); /** * A fatal error has occurred; the message queue is no longer available, and the loop must be exited. */ if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] msgrcv(%d) failed", worker->id, pool->queue->get_id()); break; } swoole_trace_log(SW_TRACE_WORKER, "pop from MsgQ#%d %lu bytes", pool->queue->get_id(), (ulong_t) n); msg.info.len = n - sizeof(msg.info); msg.data = outbuf->mdata; outbuf->mtype = 0; } else if (pool->use_socket) { Socket *conn = pool->stream_info_->socket->accept(); if (conn == nullptr) { if (catch_system_error(errno) == SW_ERROR) { swoole_sys_warning( "[Worker#%d] accept(%d) failed", worker->id, pool->stream_info_->socket->get_fd()); break; } else { goto _end; } } uint32_t packet_len = 0; if (conn->recv_sync(&packet_len, sizeof(packet_len), MSG_WAITALL) <= 0) { goto _close; } n = ntohl(packet_len); /** * Errors occurring during client connections do not affect subsequent requests, * they continue after closure. */ if (n <= 0) { goto _close; } else if (n > pool->max_packet_size_) { goto _close; } if (conn->recv_sync(pool->packet_buffer, n, MSG_WAITALL) <= 0) { _close: conn->free(); goto _end; } msg.data = pool->packet_buffer; pool->stream_info_->last_connection = conn; } else { worker->pipe_worker->set_timeout(msec2sec(swoole_timer_get_next_msec()), SW_TIMEOUT_READ); n = worker->pipe_worker->read_sync(pool->packet_buffer, pool->max_packet_size_); if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] read(%d) failed", worker->id, worker->pipe_worker->fd); break; } msg.data = pool->packet_buffer; } if (n < 0) { goto _end; } msg.info.len = n; pool->onMessage(pool, &msg); if (pool->use_socket && pool->stream_info_->last_connection) { String *resp_buf = pool->stream_info_->response_buffer; if (resp_buf && resp_buf->length > 0) { int _l = htonl(resp_buf->length); pool->stream_info_->last_connection->send_sync(&_l, sizeof(_l)); pool->stream_info_->last_connection->send_sync(resp_buf->str, resp_buf->length); resp_buf->clear(); } pool->stream_info_->last_connection->free(); pool->stream_info_->last_connection = nullptr; } _end: worker_end_callback(); } pool->at_worker_exit(worker); return SW_OK; } int ProcessPool::run_with_message_protocol(ProcessPool *pool, Worker *worker) { if (pool->ipc_mode != SW_IPC_UNIXSOCK) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_UNIXSOCK"); return SW_ERR; } auto fn = [&]() -> ReturnCode { while (true) { if (worker->pipe_worker->wait_event(swoole_timer_get_next_msec(), SW_EVENT_READ) < 0) { return errno == EINTR ? SW_CONTINUE : SW_ERROR; } auto rv = pool->message_bus->read(worker->pipe_worker); if (rv < 0) { return errno == EINTR ? SW_CONTINUE : SW_ERROR; } else if (rv > 0) { auto pipe_buffer = pool->message_bus->get_buffer(); auto packet = pool->message_bus->get_packet(); RecvData msg; msg.info = pipe_buffer->info; msg.info.len = packet.length; msg.data = packet.data; pool->onMessage(pool, &msg); pool->message_bus->pop(); return SW_READY; } } }; if (pool->message_bus == nullptr) { pool->create_message_bus(); } pool->at_worker_enter(worker); while (pool->is_worker_running(worker)) { switch (fn()) { case SW_CONTINUE: worker_end_callback(); break; case SW_READY: break; case SW_ERROR: default: swoole_sys_warning("[Worker #%d]failed to read data from pipe", worker->id); worker->shutdown(); break; } } pool->at_worker_exit(worker); return SW_OK; } void ProcessPool::add_worker(Worker *worker) const { map_->emplace(worker->pid, worker); } bool ProcessPool::del_worker(const Worker *worker) const { return map_->erase(worker->pid) > 0; } Worker *ProcessPool::get_worker_by_pid(pid_t pid) const { const auto iter = map_->find(pid); if (iter == map_->end()) { return nullptr; } return iter->second; } void ProcessPool::set_type(int _type) { type = _type; for (uint32_t i = 0; i < worker_num; i++) { workers[i].type = type; } } void ProcessPool::set_start_id(int _start_id) { start_id = _start_id; for (uint32_t i = 0; i < worker_num; i++) { workers[i].id = start_id + i; } } bool ProcessPool::wait_detached_worker(std::unordered_set &detached_workers, pid_t pid) { auto iter = detached_workers.find(pid); if (iter == detached_workers.end()) { swoole_warning("received an exit signal from an unknown child process[pid=%d]", pid); return false; } detached_workers.erase(iter); return true; } bool ProcessPool::detach() { if (!running || !message_box) { return false; } auto worker = SwooleWG.worker; worker->shutdown(); if (async) { swoole_event_del(worker->pipe_worker); } WorkerStopMessage msg; msg.pid = worker->pid; msg.worker_id = worker->id; if (push_message(SW_WORKER_MESSAGE_STOP, &msg, sizeof(msg)) < 0) { return false; } running = false; return true; } int ProcessPool::wait() { std::unordered_set detached_workers; while (running) { ExitStatus exit_status = wait_process(); const auto wait_error = errno; swoole_signal_dispatch(); if (sw_timer()) { sw_timer()->select(); } if (read_message) { EventData msg; while (pop_message(&msg, sizeof(msg)) > 0) { if (!running) { continue; } if (msg.info.type != SW_WORKER_MESSAGE_STOP && onWorkerMessage) { onWorkerMessage(this, &msg); continue; } WorkerStopMessage worker_stop_msg; memcpy(&worker_stop_msg, msg.data, sizeof(worker_stop_msg)); Worker *exit_worker = get_worker_by_pid(worker_stop_msg.pid); if (exit_worker == nullptr) { continue; } pid_t new_pid = spawn(exit_worker); if (new_pid < 0) { swoole_sys_warning("fork worker process failed"); return SW_ERR; } detached_workers.insert(worker_stop_msg.pid); map_->erase(worker_stop_msg.pid); } read_message = false; } if (exit_status.get_pid() < 0) { if (!running) { break; } if (!reload_task) { if (wait_error > 0 && wait_error != EINTR) { swoole_sys_warning("wait() failed"); } continue; } } if (running) { if (reload_init) { reload_init = false; reload_task->add_workers(workers, worker_num); goto _kill_worker; } else if (exit_status.get_pid() < 0) { continue; } Worker *exit_worker = get_worker_by_pid(exit_status.get_pid()); if (exit_worker == nullptr) { if (onWorkerNotFound) { onWorkerNotFound(this, exit_status); } else { wait_detached_worker(detached_workers, exit_status.get_pid()); } continue; } if (!exit_status.is_normal_exit()) { exit_worker->report_error(exit_status); if (onWorkerError) { onWorkerError(this, exit_worker, exit_status); } } pid_t new_pid = spawn(exit_worker); if (new_pid < 0) { swoole_sys_warning("Fork worker process failed"); return SW_ERR; } map_->erase(exit_status.get_pid()); if (reload_task) { reload_task->remove(exit_status.get_pid()); } } if (reload_task) { if (reload_task->is_completed()) { delete reload_task; reload_task = nullptr; if (onAfterReload) { onAfterReload(this); } } else { _kill_worker: reload_task->kill_one(); } } } uint32_t i; int status; Worker *worker; running = false; delete reload_task; if (onShutdown) { onShutdown(this); } // concurrent kill for (i = 0; i < worker_num; i++) { worker = &workers[i]; if (swoole_kill(worker->pid, SIGTERM) < 0) { swoole_sys_warning("kill(%d, SIGTERM) failed", worker->pid); continue; } } if (max_wait_time) { swoole_timer_add((long) max_wait_time * 1000, false, [this](Timer *, TimerNode *) { kill_all_workers(); }); } for (i = 0; i < worker_num; i++) { worker = &workers[i]; if (swoole_waitpid(worker->pid, &status, 0) < 0) { swoole_sys_warning("waitpid(%d) failed", worker->pid); } break; } started = false; return SW_OK; } void ProcessPool::destroy() { if (pipes) { delete pipes; pipes = nullptr; } if (queue) { delete queue; queue = nullptr; } if (stream_info_) { if (stream_info_->socket) { unlink(stream_info_->socket_file); sw_free(stream_info_->socket_file); } if (stream_info_->socket) { stream_info_->socket->free(); stream_info_->socket = nullptr; } delete stream_info_->response_buffer; delete stream_info_; stream_info_ = nullptr; } if (packet_buffer) { delete[] packet_buffer; packet_buffer = nullptr; } if (map_) { delete map_; map_ = nullptr; } if (message_box) { message_box->destroy(); message_box = nullptr; } if (message_bus) { delete message_bus; message_bus = nullptr; } sw_mem_pool()->free(workers); } void Worker::init() { start_time = ::time(nullptr); request_count = 0; set_status_to_idle(); SwooleWG.running = true; SwooleWG.shutdown = false; } void Worker::set_max_request(uint32_t max_request, uint32_t max_request_grace) { if (max_request > 0 && max_request_grace > 0) { max_request += swoole_system_random(1, max_request_grace); } SwooleWG.max_request = max_request; } bool Worker::has_exceeded_max_request() const { return SwooleWG.max_request > 0 && request_count >= SwooleWG.max_request; } void Worker::shutdown() { status = SW_WORKER_EXIT; SwooleWG.shutdown = true; } bool Worker::is_shutdown() { return SwooleWG.shutdown; } bool Worker::is_running() { return SwooleWG.running; } ssize_t Worker::send_pipe_message(const void *buf, size_t n, int flags) const { Socket *pipe_sock; if (flags & SW_PIPE_MASTER) { pipe_sock = pipe_master; } else { pipe_sock = pipe_worker; } // message-queue if (pool->use_msgqueue) { struct { long mtype; EventData buf; } msg; msg.mtype = id + 1; memcpy(&msg.buf, buf, n); swoole_trace_log(SW_TRACE_WORKER, "push to MsgQ#%d %lu bytes", pool->queue->get_id(), (ulong_t) n); return pool->queue->push((QueueNode *) &msg, n) ? n : -1; } if ((flags & SW_PIPE_NONBLOCK) && swoole_event_is_available()) { return swoole_event_write(pipe_sock, buf, n); } else { return pipe_sock->send_sync(buf, n); } } void Worker::report_error(const ExitStatus &exit_status) const { swoole_warning("worker(pid=%d, id=%d) abnormal exit, status=%d, signal=%d" "%s", exit_status.get_pid(), id, exit_status.get_code(), exit_status.get_signal(), exit_status.get_signal() == SIGSEGV ? SwooleG.bug_report_message.c_str() : ""); } void ReloadTask::add_workers(Worker *list, size_t n) { SW_LOOP_N(n) { workers[list[i].pid] = &list[i]; kill_queue.push(list[i].pid); } } void ReloadTask::add_timeout_killer(int timeout) { timer = swoole_timer_add(sec2msec(timeout), false, [this](Timer *, TimerNode *) { kill_all(); timer = nullptr; }); } bool ReloadTask::remove(pid_t pid) { auto iter = workers.find(pid); if (iter != workers.end()) { workers.erase(iter); return true; } else { return false; } } ReloadTask::~ReloadTask() { if (timer) { swoole_timer_del(timer); timer = nullptr; } } void ReloadTask::kill_all(int signal_number) { for (auto &kv : workers) { if (swoole_kill(kv.first, signal_number) < 0) { if (errno == ECHILD || errno == ESRCH) { continue; } swoole_sys_warning("failed to kill(%d, SIGTERM) worker#[%d]", kv.first, kv.second->id); } else if (signal_number == SIGKILL) { swoole_warning("force kill worker process(pid=%d, id=%d)", kv.first, kv.second->id); } } clear_queue(); } void ReloadTask::kill_one(int signal_number) { while (!kill_queue.empty()) { auto pid = kill_queue.front(); kill_queue.pop(); auto iter = workers.find(pid); if (iter == workers.end()) { continue; } if (swoole_kill(pid, signal_number) < 0) { if (errno == ECHILD || errno == ESRCH) { workers.erase(iter); continue; } swoole_sys_warning("kill(%d, SIGTERM) [%d] failed", pid, iter->second->id); } break; } } void ReloadTask::clear_queue() { while (!kill_queue.empty()) { kill_queue.pop(); } } } // namespace swoole swoole::WorkerId swoole_get_worker_id() { return SwooleWG.id; } pid_t swoole_get_worker_pid() { return SwooleWG.pid; } int swoole_get_worker_type() { return SwooleWG.type; } void swoole_set_worker_id(swoole::WorkerId worker_id) { SwooleWG.id = worker_id; } void swoole_set_worker_pid(pid_t pid) { SwooleWG.pid = pid; } void swoole_set_worker_type(int type) { SwooleWG.type = type; } char swoole_get_worker_symbol() { switch (swoole_get_worker_type()) { case SW_MASTER: return '#'; case SW_MANAGER: return '$'; case SW_WORKER: return '*'; case SW_TASK_WORKER: return '^'; case SW_USER_WORKER: return '@'; default: return '%'; } } ================================================ FILE: src/os/sendfile.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #if defined(__linux__) #include ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { return sendfile(out_fd, in_fd, offset, size); } #elif defined(HAVE_SENDFILE) && defined(HAVE_KQUEUE) #include #include ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { ssize_t ret; #ifdef __MACH__ struct sf_hdtr hdtr; hdtr.headers = nullptr; hdtr.hdr_cnt = 0; hdtr.trailers = nullptr; hdtr.trl_cnt = 0; #else off_t sent_bytes; #endif _do_sendfile: #ifdef __MACH__ ret = sendfile(in_fd, out_fd, *offset, (off_t *) &size, &hdtr, 0); #else ret = sendfile(in_fd, out_fd, *offset, size, 0, &sent_bytes, 0); #endif // sent_bytes = (off_t)size; swoole_trace( "send file, ret: %zd, out_fd:%d, in_fd:%d, offset:%jd, size:%zu", ret, out_fd, in_fd, (intmax_t) *offset, size); #ifdef __MACH__ *offset += size; #else *offset += sent_bytes; #endif if (ret == -1) { if (errno == EINTR) { goto _do_sendfile; } else { return ret; } } else if (ret == 0) { return size; } else { swoole_sys_warning("sendfile failed"); return SW_ERR; } return SW_OK; } #elif !defined(HAVE_SENDFILE) ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { char buf[SW_BUFFER_SIZE_BIG]; size_t readn = size > sizeof(buf) ? sizeof(buf) : size; ssize_t n = pread(in_fd, buf, readn, *offset); if (n > 0) { ssize_t ret = write(out_fd, buf, n); if (ret < 0) { swoole_sys_warning("write() failed"); } else { *offset += ret; } return ret; } else { swoole_sys_warning("pread() failed"); return SW_ERR; } } #endif ================================================ FILE: src/os/signal.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_signal.h" #include "swoole_socket.h" #include "swoole_reactor.h" #ifdef HAVE_SIGNALFD #include #endif #include #ifdef HAVE_KQUEUE #ifdef USE_KQUEUE_IDE_HELPER #include "helper/kqueue.h" #else #include #endif #endif #ifdef __NetBSD__ #include #endif using swoole::Event; using swoole::Reactor; using swoole::Signal; using swoole::SignalHandler; using swoole::network::Socket; #ifdef HAVE_SIGNALFD static SignalHandler swoole_signalfd_set(int signo, SignalHandler handler); static bool swoole_signalfd_create(); static bool swoole_signalfd_setup(Reactor *reactor); static int swoole_signalfd_event_callback(Reactor *reactor, Event *event); static void swoole_signalfd_clear(); #endif #ifdef HAVE_KQUEUE static SignalHandler swoole_signal_kqueue_set(int signo, SignalHandler handler); static void swoole_signal_kqueue_clear(); #endif static void signal_handler_safety(int signo); static void signal_handler_simple(int signo); #ifdef HAVE_SIGNALFD static sigset_t signalfd_mask; static int signal_fd = -1; static pid_t signalfd_create_pid; static Socket *signal_socket = nullptr; static inline bool swoole_signalfd_is_available() { return signal_fd != -1; } #endif static Signal signals[SW_SIGNO_MAX]; static bool triggered_signals[SW_SIGNO_MAX]; char *swoole_signal_to_str(int sig) { static char buf[64]; snprintf(buf, sizeof(buf), "%s", strsignal(sig)); if (strchr(buf, ':') == nullptr) { size_t len = strlen(buf); snprintf(buf + len, sizeof(buf) - len, ": %d", sig); } return buf; } void swoole_signal_block_all() { if (SwooleTG.signal_blocking_all) { return; } sigset_t mask; sigfillset(&mask); int ret = pthread_sigmask(SIG_BLOCK, &mask, nullptr); if (ret < 0) { swoole_sys_warning("pthread_sigmask(SIG_BLOCK) failed"); } else { SwooleTG.signal_blocking_all = true; } } void swoole_signal_unblock_all() { if (!SwooleTG.signal_blocking_all) { return; } sigset_t mask; sigfillset(&mask); int ret = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); if (ret < 0) { swoole_sys_warning("pthread_sigmask(SIG_UNBLOCK) failed"); } else { SwooleTG.signal_blocking_all = false; } } /** * set new signal handler and return origin signal handler */ SignalHandler swoole_signal_set(int signo, SignalHandler func, int restart, int mask) { // ignore if (func == nullptr) { func = SIG_IGN; } // clear else if ((long) func == -1) { func = SIG_DFL; } if (func == SIG_IGN || func == SIG_DFL) { signals[signo].handler = nullptr; signals[signo].activated = false; } struct sigaction act{}, oact{}; act.sa_handler = func; if (mask) { sigfillset(&act.sa_mask); } else { sigemptyset(&act.sa_mask); } act.sa_flags = 0; if (restart) { act.sa_flags |= SA_RESTART; } if (sigaction(signo, &act, &oact) < 0) { return nullptr; } return oact.sa_handler; } SW_API bool swoole_signal_isset(int signo) { return signals[signo].handler && signals[signo].activated; } /** * set new signal handler and return origin signal handler */ SignalHandler swoole_signal_set(int signo, SignalHandler handler, bool safety) { #ifdef HAVE_SIGNALFD if (SwooleG.enable_signalfd && swoole_event_is_available()) { return swoole_signalfd_set(signo, handler); } #endif #ifdef HAVE_KQUEUE // SIGCHLD can not be monitored by kqueue, if blocked by SIG_IGN // see https://www.freebsd.org/cgi/man.cgi?kqueue // if there's no main reactor, signals cannot be monitored either if (SwooleG.enable_kqueue && swoole_event_is_available() && signo != SIGCHLD) { return swoole_signal_kqueue_set(signo, handler); } #endif signals[signo].handler = handler; signals[signo].activated = true; signals[signo].signo = signo; return swoole_signal_set(signo, safety ? signal_handler_safety : signal_handler_simple, 0, 0); } static void signal_handler_safety(int signo) { triggered_signals[signo] = true; SwooleG.signal_dispatch = true; } static void signal_handler_simple(int signo) { if (sw_reactor()) { signal_handler_safety(signo); } else { static int _lock = 0; // discard signal if (_lock) { return; } _lock = 1; swoole_signal_callback(signo); _lock = 0; } } void swoole_signal_dispatch() { if (!SwooleG.signal_dispatch) { return; } SW_LOOP_N(SW_SIGNO_MAX) { if (triggered_signals[i]) { swoole_signal_callback(i); triggered_signals[i] = false; } } SwooleG.signal_dispatch = false; } void swoole_signal_callback(int signo) { if (signo >= SW_SIGNO_MAX) { swoole_warning("signal[%d] numberis invalid", signo); return; } SignalHandler callback = signals[signo].handler; if (!callback) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_UNREGISTERED_SIGNAL, SW_UNREGISTERED_SIGNAL_FMT, swoole_signal_to_str(signo)); return; } if (callback == SIG_IGN || callback == SIG_DFL) { return; } callback(signo); } SignalHandler swoole_signal_get_handler(int signo) { if (signo >= SW_SIGNO_MAX) { swoole_warning("signal[%d] numberis invalid", signo); return nullptr; } else { return signals[signo].handler; } } uint32_t swoole_signal_get_listener_num() { return SwooleG.signal_listener_num + SwooleG.signal_async_listener_num; } void swoole_signal_clear() { #ifdef HAVE_SIGNALFD if (SwooleG.enable_signalfd && swoole_signalfd_is_available()) { swoole_signalfd_clear(); return; } #endif #ifdef HAVE_KQUEUE if (SwooleG.enable_kqueue) { swoole_signal_kqueue_clear(); return; } #endif SW_LOOP_N(SW_SIGNO_MAX) { if (signals[i].activated) { swoole_signal_set(signals[i].signo, reinterpret_cast(-1), 1, 0); } } sw_memset_zero(signals, sizeof(signals)); } #ifdef HAVE_SIGNALFD void swoole_signalfd_init() { sigemptyset(&signalfd_mask); sw_memset_zero(signals, sizeof(signals)); } /** * set new signal handler and return origin signal handler */ static SignalHandler swoole_signalfd_set(int signo, SignalHandler handler) { SignalHandler origin_handler = nullptr; if (handler == nullptr && signals[signo].activated) { sigdelset(&signalfd_mask, signo); sw_memset_zero(&signals[signo], sizeof(Signal)); } else { sigaddset(&signalfd_mask, signo); origin_handler = signals[signo].handler; signals[signo].handler = handler; signals[signo].signo = signo; signals[signo].activated = true; } if (swoole_signalfd_is_available()) { sigprocmask(SIG_SETMASK, &signalfd_mask, nullptr); signalfd(signal_fd, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC); } swoole_signalfd_setup(sw_reactor()); return origin_handler; } static void swoole_signalfd_close() { if (!swoole_signalfd_is_available()) { return; } signal_socket->fd = -1; signal_socket->free(); close(signal_fd); signal_socket = nullptr; signal_fd = -1; } static bool swoole_signalfd_create() { if (swoole_signalfd_is_available()) { return false; } signal_fd = signalfd(-1, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC); if (signal_fd < 0) { swoole_sys_warning("signalfd() failed"); return false; } signal_socket = swoole::make_socket(signal_fd, SW_FD_SIGNAL); if (sigprocmask(SIG_BLOCK, &signalfd_mask, nullptr) == -1) { swoole_sys_warning("sigprocmask() failed"); swoole_signalfd_close(); return false; } signalfd_create_pid = getpid(); SwooleG.signal_fd = signal_fd; return true; } bool swoole_signalfd_setup(Reactor *reactor) { if (!swoole_signalfd_is_available() && !swoole_signalfd_create()) { return false; } if (!swoole_event_isset_handler(SW_FD_SIGNAL, SW_EVENT_READ)) { swoole_event_set_handler(SW_FD_SIGNAL, SW_EVENT_READ, swoole_signalfd_event_callback); reactor->set_exit_condition(Reactor::EXIT_CONDITION_SIGNALFD, [](Reactor *reactor, size_t &event_num) -> bool { event_num--; return true; }); reactor->add_destroy_callback([](void *) { // child process removes signal socket, parent process will not be able to trigger signal if (signal_socket && signalfd_create_pid == getpid()) { swoole_event_del(signal_socket); } }); } if (!(signal_socket->events & SW_EVENT_READ) && swoole_event_add(signal_socket, SW_EVENT_READ) < 0) { return false; } reactor->erase_end_callback(Reactor::PRIORITY_SIGNAL_CALLBACK); return true; } static void swoole_signalfd_clear() { if (!swoole_signalfd_is_available()) { return; } if (sigprocmask(SIG_UNBLOCK, &signalfd_mask, nullptr) < 0) { swoole_sys_warning("sigprocmask(SIG_UNBLOCK) failed"); } sw_memset_zero(signals, sizeof(signals)); sw_memset_zero(&signalfd_mask, sizeof(signalfd_mask)); swoole_signalfd_close(); } static int swoole_signalfd_event_callback(Reactor *reactor, Event *event) { signalfd_siginfo siginfo; ssize_t n = read(event->fd, &siginfo, sizeof(siginfo)); if (n < 0) { swoole_sys_warning("read from signalfd failed"); return SW_OK; } if (siginfo.ssi_signo >= SW_SIGNO_MAX) { swoole_warning("unknown signal[%d]", siginfo.ssi_signo); return SW_OK; } if (signals[siginfo.ssi_signo].activated) { SignalHandler handler = signals[siginfo.ssi_signo].handler; if (handler == SIG_IGN) { return SW_OK; } else if (handler) { handler(siginfo.ssi_signo); } else { swoole_error_log(SW_LOG_WARNING, SW_ERROR_UNREGISTERED_SIGNAL, SW_UNREGISTERED_SIGNAL_FMT, swoole_signal_to_str(siginfo.ssi_signo)); } } return SW_OK; } #endif #ifdef HAVE_KQUEUE /** * set new signal handler and return origin signal handler */ static SignalHandler swoole_signal_kqueue_set(int signo, SignalHandler handler) { struct kevent ev; SignalHandler origin_handler = nullptr; Reactor *reactor = sw_reactor(); // clear signal if (handler == nullptr) { signal(signo, SIG_DFL); sw_memset_zero(&signals[signo], sizeof(Signal)); EV_SET(&ev, signo, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL); } // add/update signal else { signal(signo, SIG_IGN); origin_handler = signals[signo].handler; signals[signo].handler = handler; signals[signo].signo = signo; signals[signo].activated = true; #if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sigptr = &signals[signo]; #else auto sigptr = reinterpret_cast(&signals[signo]); #endif // save swSignal* as udata EV_SET(&ev, signo, EVFILT_SIGNAL, EV_ADD, 0, 0, sigptr); } int n = kevent(reactor->native_handle, &ev, 1, nullptr, 0, nullptr); if (n < 0 && sw_unlikely(handler)) { swoole_sys_warning("kevent set signal[%d] error", signo); } return origin_handler; } static void swoole_signal_kqueue_clear() { SW_LOOP_N(SW_SIGNO_MAX) { if (signals[i].activated && swoole_event_is_available()) { signals[i].activated = false; signals[i].handler = nullptr; swoole_signal_kqueue_set(signals[i].signo, nullptr); } } sw_memset_zero(signals, sizeof(signals)); } #endif ================================================ FILE: src/os/timer.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_timer.h" #include "swoole_signal.h" #include #include namespace swoole { static int SystemTimer_set(Timer *timer, long next_msec); void Timer::init_with_system_timer() { set = SystemTimer_set; close = [](Timer *timer) { SystemTimer_set(timer, -1); }; swoole_signal_set(SIGALRM, [](int sig) { SwooleG.signal_alarm = true; }); } static int SystemTimer_set(Timer *timer, long next_msec) { itimerval timer_set{}; if (next_msec > 0) { timer_set.it_interval = {next_msec / 1000, static_cast((next_msec % 1000) * 1000)}; timer_set.it_value = timer_set.it_interval; } return setitimer(ITIMER_REAL, &timer_set, nullptr) < 0 ? SW_ERR : SW_OK; } void realtime_get(timespec *time) { auto now = std::chrono::system_clock::now(); auto ns = std::chrono::duration_cast(now.time_since_epoch()); time->tv_sec = ns.count() / SW_NUM_BILLION; time->tv_nsec = ns.count() % SW_NUM_BILLION; } void realtime_add(timespec *time, const int64_t add_msec) { time->tv_sec += add_msec / 1000; time->tv_nsec += add_msec % 1000 * SW_NUM_MILLION; if (time->tv_nsec >= SW_NUM_BILLION) { int secs = time->tv_nsec / SW_NUM_BILLION; time->tv_sec += secs; time->tv_nsec -= secs * SW_NUM_BILLION; } } } // namespace swoole ================================================ FILE: src/os/unix_socket.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_pipe.h" #include "swoole_socket.h" namespace swoole { UnixSocket::UnixSocket(bool blocking, int _protocol) : SocketPair(blocking), protocol_(_protocol) { if (socketpair(AF_UNIX, protocol_, 0, socks) < 0) { swoole_sys_warning("socketpair() failed"); return; } init_socket(socks[1], socks[0]); set_buffer_size(network::Socket::default_buffer_size); } bool UnixSocket::set_buffer_size(size_t _size) const { if (!master_socket->set_buffer_size(_size)) { return false; } if (!worker_socket->set_buffer_size(_size)) { return false; } return true; } } // namespace swoole ================================================ FILE: src/os/wait.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_process_pool.h" #include "swoole_coroutine.h" #include "swoole_coroutine_api.h" #include "swoole_coroutine_system.h" #include "swoole_iouring.h" #include "swoole_signal.h" #include #include using namespace swoole; using swoole::coroutine::System; struct WaitTask { Coroutine *co; pid_t pid; int status; }; /** * Wait, waitpid, and signal cannot be used in a multithreaded environment; * they are only applicable to the main thread. There is no need to treat them as thread-local variables. */ static std::list wait_list; static std::unordered_map waitpid_map; static std::unordered_map child_processes; bool signal_ready = false; static void signal_handler(int signo) { if (signo != SIGCHLD) { return; } while (true) { auto exit_status = swoole::wait_process(-1, WNOHANG); if (exit_status.get_pid() <= 0) { break; } WaitTask *task = nullptr; if (waitpid_map.find(exit_status.get_pid()) != waitpid_map.end()) { task = waitpid_map[exit_status.get_pid()]; } else if (!wait_list.empty()) { task = wait_list.front(); } else { child_processes[exit_status.get_pid()] = exit_status.get_status(); } if (task) { task->status = exit_status.get_status(); task->pid = exit_status.get_pid(); task->co->resume(); } } } static void signal_init() { if (!signal_ready) { Reactor *reactor = SwooleTG.reactor; swoole_signal_set(SIGCHLD, signal_handler); reactor->set_exit_condition(Reactor::EXIT_CONDITION_WAIT_PID, [](Reactor *reactor, size_t &event_num) -> bool { return swoole_coroutine_wait_count() == 0; }); reactor->add_destroy_callback([](void *) { signal_ready = false; swoole_signal_clear(); }); signal_ready = true; } } #if SW_USE_IOURING static pid_t iouring_waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout = -1) { auto rs = Iouring::waitpid(_pid, _stat_loc, _options, timeout); if (rs < 0) { swoole_set_last_error(errno); } return rs; } #endif pid_t System::wait(int *_stat_loc, double timeout) { return System::waitpid(-1, _stat_loc, 0, timeout); } pid_t System::waitpid_safe(pid_t _pid, int *_stat_loc, int _options) { if (sw_unlikely(SwooleTG.reactor == nullptr || !Coroutine::get_current() || (_options & WNOHANG))) { return ::waitpid(_pid, _stat_loc, _options); } #if SW_USE_IOURING return iouring_waitpid(_pid, _stat_loc, _options); #endif pid_t retval = -1; wait_for([_pid, &retval, _stat_loc]() -> bool { retval = ::waitpid(_pid, _stat_loc, WNOHANG); return retval != 0; }); return retval; } /** * @error: errno & swoole_get_last_error() */ pid_t System::waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout) { #if SW_USE_IOURING return iouring_waitpid(_pid, _stat_loc, _options, timeout); #endif if (_pid < 0) { if (!child_processes.empty()) { auto i = child_processes.begin(); pid_t pid = i->first; *_stat_loc = i->second; child_processes.erase(i); return pid; } } else { auto i = child_processes.find(_pid); if (i != child_processes.end()) { *_stat_loc = i->second; child_processes.erase(i); return _pid; } } if (sw_unlikely(SwooleTG.reactor == nullptr || !Coroutine::get_current() || (_options & WNOHANG))) { return ::waitpid(_pid, _stat_loc, _options); } /* try once if failed to init the task, and must register SIGCHLD before try waitpid, or may lose the SIGCHLD */ WaitTask task; signal_init(); task.pid = ::waitpid(_pid, _stat_loc, _options | WNOHANG); if (task.pid != 0) { return task.pid; } task.pid = -1; task.status = 0; task.co = Coroutine::get_current(); /* enqueue */ if (_pid < 0) { wait_list.push_back(&task); } else { waitpid_map[_pid] = &task; } task.co->yield_ex(timeout); /* dequeue */ if (_pid < 0) { if (task.pid > 0) { wait_list.pop_front(); } else { /* timeout so we should remove it from the list */ wait_list.remove(&task); } } else { waitpid_map.erase(_pid); } /* clear and assign result */ if (task.pid > 0) { *_stat_loc = task.status; } else if (task.co->is_timedout()) { errno = ETIMEDOUT; swoole_set_last_error(ETIMEDOUT); } return task.pid; } extern "C" { size_t swoole_coroutine_wait_count() { return wait_list.size() + waitpid_map.size(); } pid_t swoole_coroutine_wait(int *_stat_loc) { return System::wait(_stat_loc); } pid_t swoole_coroutine_waitpid(pid_t _pid, int *_stat_loc, int _options) { return System::waitpid(_pid, _stat_loc, _options); } } pid_t swoole_waitpid(pid_t _pid, int *_stat_loc, int _options) { pid_t retval; SW_LOOP { retval = waitpid(_pid, _stat_loc, _options); if (!(retval < 0 && errno == EINTR)) { break; } swoole_signal_dispatch(); if (sw_timer()) { sw_timer()->select(); } } return retval; } ================================================ FILE: src/protocol/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" namespace swoole { #define proto_error(_sock, _error, _fmt, ...) \ network::Address _remote_addr; \ if (_sock->get_peer_name(&_remote_addr) < 0) { \ _remote_addr = _sock->info; \ } \ swoole_error_log( \ SW_LOG_WARNING, _error, _fmt "<%s:%d>", ##__VA_ARGS__, _remote_addr.get_addr(), _remote_addr.get_port()) /** * return the package total length */ ssize_t Protocol::default_length_func(const Protocol *protocol, network::Socket *socket, PacketLength *pl) { uint16_t length_offset = protocol->package_length_offset; uint8_t package_length_size = protocol->get_package_length_size ? protocol->get_package_length_size(socket) : protocol->package_length_size; if (package_length_size == 0) { // protocol error return SW_ERR; } /** * no have length field, wait more data */ if (pl->buf_size < length_offset + package_length_size) { pl->header_len = length_offset + package_length_size; return 0; } int64_t body_length = swoole_unpack(protocol->package_length_type, pl->buf + length_offset); // Length error // Protocol length is not legitimate, out of bounds or exceed the allocated length if (body_length < 0) { return SW_ERR; } swoole_debug("length=%ld", protocol->package_body_offset + body_length); pl->header_len = protocol->package_length_size; // total package length return protocol->package_body_offset + body_length; } int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) const { RecvData rdata{}; if (buffer->length < package_eof_len) { return SW_CONTINUE; } ssize_t n = buffer->split(package_eof, package_eof_len, [&](const char *data, size_t length) -> int { rdata.info.len = length; rdata.data = data; if (onPackage(this, socket, &rdata) < 0) { return false; } if (socket->removed) { return false; } return true; }); if (socket->removed || n < 0) { return SW_CLOSE; } else if (n == 0) { return SW_CONTINUE; } else if (n < (ssize_t) buffer->length) { off_t offset; buffer->reduce(n); offset = buffer->length - package_eof_len; buffer->offset = offset > 0 ? offset : 0; } else { buffer->clear(); } if (socket->ssl) { return SW_CONTINUE; } return SW_OK; } /** * @return SW_ERR: close the connection * @return SW_OK: continue */ int Protocol::recv_with_length_protocol(network::Socket *socket, String *buffer) const { RecvData rdata{}; PacketLength pl{}; ssize_t package_length; uint8_t _package_length_size = get_package_length_size ? get_package_length_size(socket) : package_length_size; uint32_t recv_size = 0; ssize_t recv_n = 0; // protocol error if (get_package_length_size && _package_length_size == 0) { return SW_ERR; } if (socket->skip_recv) { socket->skip_recv = 0; goto _do_get_length; } _do_recv: if (socket->removed) { return SW_OK; } if (buffer->offset > 0) { recv_size = buffer->offset - buffer->length; } else { recv_size = package_length_offset + _package_length_size; } recv_n = socket->recv(buffer->str + buffer->length, recv_size, 0); if (recv_n < 0) { switch (socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("recv(%d, %d) failed", socket->fd, recv_size); return SW_OK; case SW_CLOSE: return SW_ERR; default: return SW_OK; } } else if (recv_n == 0) { return SW_ERR; } else { buffer->length += recv_n; if (socket->recv_wait) { if (buffer->length >= (size_t) buffer->offset) { _do_dispatch: rdata.info.len = buffer->offset; rdata.data = buffer->str; if (onPackage(this, socket, &rdata) < 0) { return SW_ERR; } if (socket->removed) { return SW_OK; } socket->recv_wait = 0; if (buffer->length > (size_t) buffer->offset) { buffer->reduce(buffer->offset); goto _do_get_length; } else { buffer->clear(); } } if (socket->ssl) { goto _do_recv; } return SW_OK; } else { _do_get_length: pl.buf = buffer->str; pl.buf_size = buffer->length; // TODO: support dynamic calculation of header buffer length (recv_size) pl.header_len = 0; package_length = get_package_length(this, socket, &pl); // invalid package, close connection. if (package_length < 0) { proto_error(socket, SW_ERROR_PACKAGE_MALFORMED_DATA, "received %zu bytes of malformed data, remote_addr=", buffer->length); return SW_ERR; } // no length else if (package_length == 0) { if (buffer->length == recv_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_NOT_FOUND, "bad request, no length found in %zu bytes, remote_addr=", buffer->length); return SW_ERR; } else { return SW_OK; } } else if (package_length > package_max_length) { proto_error(socket, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "the received packet length %ld is too large, remote_addr=", package_length); return SW_ERR; } // get length success else { if (buffer->size < (size_t) package_length) { buffer->extend(package_length); } socket->recv_wait = 1; buffer->offset = package_length; if (buffer->length >= (size_t) package_length) { goto _do_dispatch; } else { goto _do_recv; } } } } return SW_OK; } /** * @return SW_ERR: close the connection * @return SW_OK: continue */ int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) const { bool recv_again = false; size_t buf_size; RecvData rdata{}; _recv_data: buf_size = buffer->size - buffer->length; char *buf_ptr = buffer->str + buffer->length; if (buf_size > SW_BUFFER_SIZE_STD) { buf_size = SW_BUFFER_SIZE_STD; } ssize_t n = socket->recv(buf_ptr, buf_size, 0); if (n < 0) { switch (socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("recv from socket#%d failed", socket->fd); return SW_OK; case SW_CLOSE: return SW_ERR; default: return SW_OK; } } else if (n == 0) { return SW_ERR; } else { buffer->length += n; if (buffer->length < package_eof_len) { return SW_OK; } if (split_by_eof) { int retval = recv_split_by_eof(socket, buffer); if (retval == SW_CONTINUE) { recv_again = true; } else if (retval == SW_CLOSE) { return SW_ERR; } else { return SW_OK; } } else if (memcmp(buffer->str + buffer->length - package_eof_len, package_eof, package_eof_len) == 0) { buffer->offset = buffer->length; rdata.info.len = buffer->length; rdata.data = buffer->str; if (onPackage(this, socket, &rdata) < 0) { return SW_ERR; } if (socket->removed) { return SW_OK; } buffer->clear(); if (socket->ssl && SSL_pending(socket->ssl) > 0) { goto _recv_data; } return SW_OK; } // over max length, will discard if (buffer->length == package_max_length) { proto_error(socket, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "The received data packet is too large, length=%lu", (ulong_t) buffer->length); return SW_ERR; } // buffer is full, may have not read data if (buffer->length == buffer->size) { recv_again = true; if (buffer->size < package_max_length) { uint32_t extend_size = swoole_size_align(buffer->size * 2, swoole_pagesize()); if (extend_size > package_max_length) { extend_size = package_max_length; } buffer->extend(extend_size); } } // no eof if (recv_again) { goto _recv_data; } } return SW_OK; } } // namespace swoole int64_t swoole_unpack(char type, const void *data) { switch (type) { /*-------------------------8bit-----------------------------*/ case 'c': // signed char return *(const int8_t *) data; case 'C': // unsigned char return *(const uint8_t *) data; /*-------------------------16bit-----------------------------*/ case 's': { // signed short (16 bit, host byte order) int16_t val; memcpy(&val, data, sizeof(val)); return val; } case 'S': { // unsigned short (16 bit, host byte order) uint16_t val; memcpy(&val, data, sizeof(val)); return val; } case 'n': { // unsigned short (16 bit, big endian byte order) uint16_t val; memcpy(&val, data, sizeof(val)); return ntohs(val); } case 'v': { // unsigned short (16 bit, little endian byte order) uint16_t val; memcpy(&val, data, sizeof(val)); #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN return val; #else return swoole_swap_endian16(val); #endif } /*-------------------------Machine dependent size-----------------------------*/ case 'i': { // signed integer (machine dependent size and byte order) int val; memcpy(&val, data, sizeof(val)); return val; } case 'I': { // unsigned integer (machine dependent size and byte order) unsigned int val; memcpy(&val, data, sizeof(val)); return val; } /*-------------------------32bit-----------------------------*/ case 'l': { // signed long (32 bit, host byte order) int32_t val; memcpy(&val, data, sizeof(val)); return val; } case 'L': { // unsigned long (32 bit, host byte order) uint32_t val; memcpy(&val, data, sizeof(val)); return val; } case 'N': { // unsigned long (32 bit, big endian byte order) uint32_t val; memcpy(&val, data, sizeof(val)); return ntohl(val); } case 'V': { // unsigned long (32 bit, little endian byte order) uint32_t val; memcpy(&val, data, sizeof(val)); #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN return val; #else return swoole_swap_endian32(val); #endif } /*-------------------------64bit-----------------------------*/ case 'q': { // signed long long (64 bit, host byte order) int64_t val; memcpy(&val, data, sizeof(val)); return val; } case 'Q': { // unsigned long long (64 bit, host byte order) uint64_t val; memcpy(&val, data, sizeof(val)); return val; } case 'J': { // unsigned long long (64 bit, big endian byte order) uint64_t val; memcpy(&val, data, sizeof(val)); return swoole_ntoh64(val); // Network byte order (big endian) to host byte order } case 'P': { // unsigned long long (64 bit, little endian byte order) uint64_t val; memcpy(&val, data, sizeof(val)); #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN return val; #else return swoole_swap_endian64(val); #endif } default: { swoole_error_log(SW_LOG_ERROR, SW_ERROR_INVALID_PARAMS, "Invalid format specifier '%c'", type); return -1; } } } uint64_t swoole_hton64(uint64_t value) { #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN return swoole_swap_endian64(value); #else return value; #endif } uint64_t swoole_ntoh64(uint64_t value) { #if SW_BYTE_ORDER == SW_LITTLE_ENDIAN return swoole_swap_endian64(value); #else return value; #endif } ================================================ FILE: src/protocol/dtls.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #ifdef SW_SUPPORT_DTLS namespace swoole { namespace dtls { //------------------------------------------------------------------------------- int BIO_write(BIO *b, const char *data, int dlen) { swoole_trace_log(SW_TRACE_SSL, "BIO_write(%d)", dlen); auto *session = (Session *) BIO_get_data(b); return session->socket->write(data, dlen); } int BIO_read(BIO *b, char *data, int len) { auto *session = (Session *) BIO_get_data(b); BIO_clear_retry_flags(b); if (!session->rxqueue.empty()) { Buffer *buffer = session->rxqueue.front(); swoole_trace("BIO_read(%d, peek=%d)=%d", len, session->peek_mode, buffer->length); int n = (buffer->length <= len) ? buffer->length : len; memmove(data, buffer->data, n); if (!session->peek_mode) { session->rxqueue.pop_front(); sw_free(buffer); } return n; } else { BIO_set_retry_read(b); return -1; } } long BIO_ctrl(BIO *b, int cmd, long lval, void *ptrval) { long retval = 0; auto *session = (Session *) BIO_get_data(b); swoole_trace_log(SW_TRACE_SSL, "BIO_ctrl(BIO[%p], cmd[%d], lval[%ld], ptrval[%p])", b, cmd, lval, ptrval); switch (cmd) { case BIO_CTRL_EOF: return session->rxqueue.empty(); case BIO_CTRL_GET_CLOSE: return BIO_get_shutdown(b); case BIO_CTRL_SET_CLOSE: BIO_set_shutdown(b, (int) lval); break; case BIO_CTRL_WPENDING: return 0; case BIO_CTRL_PENDING: return (long) session->get_buffer_length(); case BIO_CTRL_FLUSH: case BIO_CTRL_DGRAM_SET_CONNECTED: case BIO_CTRL_DGRAM_SET_PEER: retval = 1; break; case BIO_CTRL_DGRAM_GET_PEER: if (ptrval) { memcpy(ptrval, &session->socket->info, sizeof(session->socket->info.addr)); } retval = 1; break; case BIO_CTRL_DGRAM_QUERY_MTU: case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: retval = 1500; break; #ifdef BIO_CTRL_DGRAM_GET_MTU_OVERHEAD case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: retval = 96; // random guess break; #endif #ifdef BIO_CTRL_DGRAM_SET_PEEK_MODE case BIO_CTRL_DGRAM_SET_PEEK_MODE: ((Session *) BIO_get_data(b))->peek_mode = !!lval; retval = 1; break; #endif case BIO_CTRL_PUSH: case BIO_CTRL_POP: case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: retval = 0; break; #if OPENSSL_VERSION_NUMBER >= 0x30000000L case BIO_CTRL_GET_KTLS_SEND: case BIO_CTRL_GET_KTLS_RECV: retval = 0; break; #endif default: swoole_warning("unknown cmd: %d", cmd); retval = 0; break; } return retval; } int BIO_create(BIO *b) { return 1; } int BIO_destroy(BIO *b) { swoole_trace_log(SW_TRACE_SSL, "BIO_destroy(BIO[%p])\n", b); return 1; } static BIO_METHOD *_bio_methods = nullptr; static int dtls_session_index = 0; BIO_METHOD *BIO_get_methods() { if (_bio_methods) { return _bio_methods; } dtls_session_index = BIO_get_new_index(); _bio_methods = BIO_meth_new(dtls_session_index | BIO_TYPE_SOURCE_SINK, "swoole_dtls_bio"); BIO_meth_set_write(_bio_methods, BIO_write); BIO_meth_set_read(_bio_methods, BIO_read); BIO_meth_set_ctrl(_bio_methods, BIO_ctrl); BIO_meth_set_create(_bio_methods, BIO_create); BIO_meth_set_destroy(_bio_methods, BIO_destroy); #ifdef OPENSSL_IS_BORINGSSL BIO_meth_set_ctrl(_bio_methods, (long (*)(BIO *, int, long, void *)) BIO_ctrl); #else #if OPENSSL_VERSION_NUMBER > 0x1010007fL BIO_meth_set_callback_ctrl(_bio_methods, (long (*)(BIO *, int, BIO_info_cb *)) BIO_callback_ctrl); #else BIO_meth_set_callback_ctrl(_bio_methods, (long (*)(BIO *, int, bio_info_cb *)) BIO_callback_ctrl); #endif #endif return _bio_methods; } void BIO_meth_free() { if (_bio_methods) { BIO_meth_free(_bio_methods); } _bio_methods = nullptr; } void Session::append(const char *data, ssize_t len) { auto *buffer = static_cast(sw_malloc(sizeof(Buffer) + len)); buffer->length = len; memcpy(buffer->data, data, buffer->length); rxqueue.push_back(buffer); } bool Session::init() { if (socket->ssl) { return false; } if (socket->ssl_create(ctx.get(), SW_SSL_SERVER) < 0) { swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } socket->set_buffer_size(Socket::default_buffer_size); socket->dtls = 1; BIO *bio = BIO_new(BIO_get_methods()); BIO_set_data(bio, (void *) this); BIO_set_init(bio, 1); SSL_set_bio(socket->ssl, bio, bio); return true; } bool Session::listen() { #ifdef OPENSSL_IS_BORINGSSL return SSL_is_dtls(socket->ssl); #else if (listened) { return false; } ERR_clear_error(); int retval = DTLSv1_listen(socket->ssl, nullptr); if (retval == 0) { return true; } else if (retval < 0) { int reason = ERR_GET_REASON(ERR_peek_error()); swoole_warning("DTLSv1_listen() failed, client[%s:%d], reason=%d, error_string=%s", socket->get_addr(), socket->get_port(), reason, swoole_ssl_get_error()); return false; } else { listened = true; } return true; #endif } //------------------------------------------------------------------------------- } // namespace dtls } // namespace swoole #endif ================================================ FILE: src/protocol/http.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_http.h" #include "swoole_proxy.h" #include "swoole_base64.h" #include "swoole_util.h" #include "swoole_http2.h" #include "swoole_websocket.h" #include "swoole_static_handler.h" #include "thirdparty/multipart_parser.h" using swoole::http_server::Request; using swoole::http_server::StaticHandler; using swoole::network::SendfileTask; using swoole::network::Socket; // clang-format off static const char *method_strings[] = { "DELETE", "GET", "HEAD", "POST", "PUT", "PATCH", "CONNECT", "OPTIONS", "TRACE", "COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK", "REPORT", "MKACTIVITY", "CHECKOUT", "MERGE", "M-SEARCH", "NOTIFY", "SUBSCRIBE", "UNSUBSCRIBE", "PURGE", "PRI", }; // clang-format on namespace swoole { HttpProxy *HttpProxy::create(const std::string &host, int port, const std::string &user, const std::string &pwd) { auto http_proxy = new HttpProxy(); http_proxy->host = host; http_proxy->port = port; if (!user.empty() && !pwd.empty()) { http_proxy->username = user; http_proxy->password = pwd; } return http_proxy; } std::string HttpProxy::get_auth_str() const { char auth_buf[256]; char encode_buf[512]; size_t n = sw_snprintf(auth_buf, sizeof(auth_buf), "%.*s:%.*s", (int) username.length(), username.c_str(), (int) password.length(), password.c_str()); base64_encode((unsigned char *) auth_buf, n, encode_buf); return {encode_buf}; } size_t HttpProxy::pack(const String *send_buffer, const std::string &host_name) const { if (!password.empty()) { auto auth_str = get_auth_str(); return sw_snprintf(send_buffer->str, send_buffer->size, SW_HTTP_PROXY_FMT "Proxy-Authorization: Basic %.*s\r\n\r\n", (int) target_host.length(), target_host.c_str(), target_port, (int) host_name.length(), host_name.c_str(), target_port, (int) auth_str.length(), auth_str.c_str()); } else { return sw_snprintf(send_buffer->str, send_buffer->size, SW_HTTP_PROXY_FMT "\r\n", (int) target_host.length(), target_host.c_str(), target_port, (int) host_name.length(), host_name.c_str(), target_port); } } bool HttpProxy::handshake(const String *recv_buffer) { bool ret = false; char *buf = recv_buffer->str; size_t len = recv_buffer->length; int state = 0; char *p = buf; char *pe = buf + len; if (recv_buffer->length < sizeof(SW_HTTP_PROXY_HANDSHAKE_RESPONSE) - 1) { return false; } for (; p < buf + len; p++) { if (state == 0) { if (SW_STR_ISTARTS_WITH(p, pe - p, "HTTP/1.1") || SW_STR_ISTARTS_WITH(p, pe - p, "HTTP/1.0")) { state = 1; p += sizeof("HTTP/1.x") - 1; } else { break; } } else if (state == 1) { if (isspace(*p)) { continue; } else { if (SW_STR_ISTARTS_WITH(p, pe - p, "200")) { state = 2; p += sizeof("200") - 1; } else { swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); break; } } } else if (state == 2) { ret = true; break; /** * The response message is generally "Connection established," * although it is not specified in the RFC documents, and thus will not be checked for now. */ #if SW_HTTP_PROXY_CHECK_MESSAGE if (isspace(*p)) { continue; } else { if (SW_STR_ISTARTS_WITH(p, pe - p, "Connection established")) { ret = true; } break; } #endif } } return ret; } namespace http_server { //----------------------------------------------------------------- static int multipart_on_header_field(multipart_parser *p, const char *at, size_t length) { auto *request = static_cast(p->data); request->form_data_->current_header_name = at; request->form_data_->current_header_name_len = length; swoole_trace("header_field: at=%.*s, length=%lu", (int) length, at, length); return 0; } static int multipart_on_header_value(multipart_parser *p, const char *at, size_t length) { swoole_trace("header_value: at=%.*s, length=%lu", (int) length, at, length); auto *request = static_cast(p->data); auto *form_data = request->form_data_; form_data->multipart_buffer_->append(form_data->current_header_name, form_data->current_header_name_len); form_data->multipart_buffer_->append(SW_STRL(": ")); form_data->multipart_buffer_->append(at, length); form_data->multipart_buffer_->append(SW_STRL("\r\n")); if (SW_STRCASEEQ(form_data->current_header_name, form_data->current_header_name_len, "content-disposition")) { ParseCookieCallback cb = [request, form_data, p](char *key, size_t key_len, char *value, size_t value_len) { if (SW_STRCASEEQ(key, key_len, "filename")) { memcpy(form_data->upload_tmpfile->str, form_data->upload_tmpfile_fmt_.c_str(), form_data->upload_tmpfile_fmt_.length()); form_data->upload_tmpfile->str[form_data->upload_tmpfile_fmt_.length()] = 0; form_data->upload_filesize = 0; int tmpfile = swoole_tmpfile(form_data->upload_tmpfile->str); if (tmpfile < 0) { request->excepted = true; return false; } FILE *fp = fdopen(tmpfile, "wb+"); if (fp == nullptr) { swoole_sys_warning("fopen(%s) failed", form_data->upload_tmpfile->str); return false; } p->fp = fp; return false; } return true; }; parse_cookie(at, length, cb); } return 0; } static int multipart_on_data(multipart_parser *p, const char *at, size_t length) { auto request = (Request *) p->data; auto form_data = request->form_data_; swoole_trace("on_data: length=%lu", length); if (!p->fp) { if (form_data->multipart_buffer_->length + length > request->max_length_) { request->excepted = 1; request->unavailable = 1; return 1; } form_data->multipart_buffer_->append(at, length); return 0; } form_data->upload_filesize += length; if (form_data->upload_filesize > form_data->upload_max_filesize) { request->excepted = 1; request->too_large = 1; return 1; } ssize_t n = fwrite(at, sizeof(char), length, p->fp); if (n != (off_t) length) { fclose(p->fp); p->fp = nullptr; request->excepted = 1; request->unavailable = 1; swoole_sys_warning("failed to write upload file"); return 1; } return 0; } static int multipart_on_header_complete(multipart_parser *p) { swoole_trace("on_header_complete"); auto *request = static_cast(p->data); auto *form_data = request->form_data_; if (p->fp) { form_data->multipart_buffer_->append(SW_STRL(SW_HTTP_UPLOAD_FILE ": ")); form_data->multipart_buffer_->append(form_data->upload_tmpfile->str, strlen(form_data->upload_tmpfile->str)); } request->multipart_header_parsed = 1; form_data->multipart_buffer_->append(SW_STRL("\r\n")); return 0; } static int multipart_on_data_end(multipart_parser *p) { swoole_trace("on_data_end\n"); auto *request = static_cast(p->data); auto *form_data = request->form_data_; request->multipart_header_parsed = 0; if (p->fp) { form_data->multipart_buffer_->append(SW_STRL("\r\n" SW_HTTP_UPLOAD_FILE)); fflush(p->fp); fclose(p->fp); p->fp = nullptr; } form_data->multipart_buffer_->append(SW_STRL("\r\n")); return 0; } static int multipart_on_part_begin(multipart_parser *p) { swoole_trace("on_part_begin"); auto *request = static_cast(p->data); auto *form_data = request->form_data_; form_data->multipart_buffer_->append(p->boundary, p->boundary_length); form_data->multipart_buffer_->append(SW_STRL("\r\n")); return 0; } static int multipart_on_body_end(multipart_parser *p) { auto *request = static_cast(p->data); auto *form_data = request->form_data_; form_data->multipart_buffer_->append(p->boundary, p->boundary_length); form_data->multipart_buffer_->append(SW_STRL("--")); request->content_length_ = form_data->multipart_buffer_->length - request->header_length_; request->tried_to_dispatch = 1; #if 0 /** * Replace content-length with the actual value */ char *ptr = request->multipart_buffer_->str - (sizeof("\r\n\r\n") - 1); char *ptr_end = request->multipart_buffer_->str + (request->multipart_buffer_->length - (sizeof("\r\n\r\n") - 1)); for (; ptr < ptr_end; ptr++) { if (SW_STR_ISTARTS_WITH(ptr, ptr_end - ptr, "Content-Length:")) { ptr += (sizeof("Content-Length:") - 1); // skip spaces while (*ptr == ' ') { ptr++; } break; } } std::string actual_content_length = std::to_string(request->content_length_); memcpy(ptr, actual_content_length.c_str(), actual_content_length.length()); ptr += actual_content_length.length(); SW_LOOP { if (*ptr == '\r') { break; } else { *ptr = ' '; ptr++; } } #endif swoole_trace("end, buffer=%.*s", (int) form_data->multipart_buffer_->length, form_data->multipart_buffer_->str); return 0; } static constexpr multipart_parser_settings mt_parser_settings = { multipart_on_header_field, multipart_on_header_value, multipart_on_data, multipart_on_part_begin, multipart_on_header_complete, multipart_on_data_end, multipart_on_body_end, }; static thread_local char http_status_message[128]; // clang-format off int list_of_status_code[128] = { 100, 101, 102, 103, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, -1, }; // clang-format on const char *get_status_message(int code) { switch (code) { case 100: return "100 Continue"; case 101: return "101 Switching Protocols"; case 102: return "102 Processing"; case 103: return "103 Early Hints"; case 201: return "201 Created"; case 202: return "202 Accepted"; case 203: return "203 Non-Authoritative Information"; case 204: return "204 No Content"; case 205: return "205 Reset Content"; case 206: return "206 Partial Content"; case 207: return "207 Multi-Status"; case 208: return "208 Already Reported"; case 226: return "226 IM Used"; case 300: return "300 Multiple Choices"; case 301: return "301 Moved Permanently"; case 302: return "302 Found"; case 303: return "303 See Other"; case 304: return "304 Not Modified"; case 305: return "305 Use Proxy"; case 306: return "306 (Unused)"; case 307: return "307 Temporary Redirect"; case 308: return "308 Permanent Redirect"; case 400: return "400 Bad Request"; case 401: return "401 Unauthorized"; case 402: return "402 Payment Required"; case 403: return "403 Forbidden"; case 404: return "404 Not Found"; case 405: return "405 Method Not Allowed"; case 406: return "406 Not Acceptable"; case 407: return "407 Proxy Authentication Required"; case 408: return "408 Request Timeout"; case 409: return "409 Conflict"; case 410: return "410 Gone"; case 411: return "411 Length Required"; case 412: return "412 Precondition Failed"; case 413: return "413 Payload Too Large"; case 414: return "414 URI Too Long"; case 415: return "415 Unsupported Media Type"; case 416: return "416 Range Not Satisfiable"; case 417: return "417 Expectation Failed"; case 418: return "418 I'm a teapot"; case 421: return "421 Misdirected Request"; case 422: return "422 Unprocessable Entity"; case 423: return "423 Locked"; case 424: return "424 Failed Dependency"; case 425: return "425 Too Early"; case 426: return "426 Upgrade Required"; case 428: return "428 Precondition Required"; case 429: return "429 Too Many Requests"; case 431: return "431 Request Header Fields Too Large"; case 451: return "451 Unavailable For Legal Reasons"; case 500: return "500 Internal Server Error"; case 501: return "501 Not Implemented"; case 502: return "502 Bad Gateway"; case 503: return "503 Service Unavailable"; case 504: return "504 Gateway Timeout"; case 505: return "505 HTTP Version Not Supported"; case 506: return "506 Variant Also Negotiates"; case 507: return "507 Insufficient Storage"; case 508: return "508 Loop Detected"; case 510: return "510 Not Extended"; case 511: return "511 Network Authentication Required"; case 200: case 0: return "200 OK"; default: sw_snprintf(http_status_message, sizeof(http_status_message), "%d Unknown Status", code); return http_status_message; } } void parse_cookie(const char *at, size_t length, const ParseCookieCallback &cb) { char *value; const char *separator = ";\0"; size_t key_len = 0; char *strtok_buf = nullptr; char *_c = sw_tg_buffer()->str; memcpy(_c, at, length); _c[length] = '\0'; char *key = strtok_r(_c, separator, &strtok_buf); while (key) { size_t value_len; value = strchr(key, '='); while (isspace(*key)) { key++; } if (key == value || *key == '\0') { goto next_cookie; } if (value) { *value++ = '\0'; value_len = strlen(value); } else { value = (char *) ""; value_len = 0; } key_len = strlen(key); if (!cb(key, key_len, value, value_len)) { break; } next_cookie: key = strtok_r(nullptr, separator, &strtok_buf); } } bool parse_multipart_boundary( const char *at, size_t length, size_t offset, char **out_boundary_str, int *out_boundary_len) { while (offset < length) { if (at[offset] == ' ' || at[offset] == ';') { offset++; continue; } if (offset + sizeof("boundary=") - 1 <= length && SW_STR_ISTARTS_WITH(at + offset, length - offset, "boundary=")) { offset += sizeof("boundary=") - 1; break; } void *delimiter = memchr((void *) (at + offset), ';', length - offset); if (delimiter == nullptr) { return false; } else { offset += (const char *) delimiter - (at + offset); } } if (offset >= length) { return false; } int boundary_len = length - offset; char *boundary_str = (char *) at + offset; // find eof of boundary if (boundary_len > 0) { // find ';' char *semicolon = (char *) memchr(boundary_str, ';', boundary_len); if (semicolon) { boundary_len = semicolon - boundary_str; } } if (boundary_len <= 0) { return false; } // trim '"' if (boundary_len >= 2 && boundary_str[0] == '"' && boundary_str[boundary_len - 1] == '"') { boundary_str++; boundary_len -= 2; if (boundary_len <= 0) { return false; } } *out_boundary_str = boundary_str; *out_boundary_len = boundary_len; return true; } static int url_htoi(const char *s) { int c = ((unsigned char *) s)[0]; if (isupper(c)) { c = tolower(c); } int value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; c = ((unsigned char *) s)[1]; if (isupper(c)) { c = tolower(c); } value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; return (value); } /* return value: length of decoded string */ size_t url_decode(char *str, size_t len) { char *dest = str; char *data = str; while (len--) { if (*data == '+') { *dest = ' '; } else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) && isxdigit((int) *(data + 2))) { *dest = (char) url_htoi(data + 1); data += 2; len -= 2; } else { *dest = *data; } data++; dest++; } *dest = '\0'; return dest - str; } char *url_encode(char const *str, size_t len) { static uchar hexchars[] = "0123456789ABCDEF"; size_t x, y; char *ret = (char *) sw_malloc(len * 3); for (x = 0, y = 0; len--; x++, y++) { char c = str[x]; ret[y] = c; if ((c < '0' && c != '-' && c != '.') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a' && c != '_') || (c > 'z' && c != '~')) { ret[y++] = '%'; ret[y++] = hexchars[(unsigned char) c >> 4]; ret[y] = hexchars[(unsigned char) c & 15]; } } ret[y] = '\0'; do { size_t size = y + 1; char *tmp = (char *) sw_malloc(size); memcpy(tmp, ret, size); sw_free(ret); ret = tmp; } while (false); return ret; } int Request::get_protocol() { char *p = buffer_->str; char *pe = p + buffer_->length; char state = 0; if (buffer_->length < (sizeof("GET / HTTP/1.x\r\n") - 1)) { return SW_ERR; } // HTTP Method SW_LOOP_N(sizeof(method_strings) / sizeof(char *) - 1) { auto method_str = method_strings[i]; auto n = strlen(method_str); if (memcmp(p, method_str, n) == 0) { method = i + 1; p += n; goto _found_method; } } // HTTP2 Connection Preface if (memcmp(p, SW_STRL("PRI")) == 0) { method = SW_HTTP_PRI; if (buffer_->length >= (sizeof(SW_HTTP2_PRI_STRING) - 1) && memcmp(p, SW_STRL(SW_HTTP2_PRI_STRING)) == 0) { buffer_->offset = sizeof(SW_HTTP2_PRI_STRING) - 1; return SW_OK; } else { goto _excepted; } } else { _excepted: excepted = 1; return SW_ERR; } // HTTP Version _found_method: for (; p < pe; p++) { switch (state) { case 0: if (*p == ' ') { continue; } state = 1; url_offset_ = p - buffer_->str; break; case 1: if (*p == ' ') { state = 2; url_length_ = p - buffer_->str - url_offset_; continue; } else if (*p == '\r' || *p == '\n') { goto _excepted; } break; case 2: if (*p == ' ') { continue; } if ((size_t) (pe - p) < (sizeof("HTTP/1.x") - 1)) { return SW_ERR; } if (memcmp(p, SW_STRL("HTTP/1.1")) == 0) { version = SW_HTTP_VERSION_11; goto _end; } else if (memcmp(p, SW_STRL("HTTP/1.0")) == 0) { version = SW_HTTP_VERSION_10; goto _end; } else { goto _excepted; } default: break; } } _end: p += sizeof("HTTP/1.x") - 1; request_line_length_ = buffer_->offset = p - buffer_->str; return SW_OK; } /** * simple get headers info */ void Request::parse_header_info() { // header field start char *p = buffer_->str + request_line_length_ + (sizeof("\r\n") - 1); // point-end: start + strlen(all-header) without strlen("\r\n\r\n") char *pe = buffer_->str + header_length_ - (sizeof("\r\n\r\n") - 1); for (; p < pe; p++) { if (*(p - 1) == '\n' && *(p - 2) == '\r') { if (SW_STR_ISTARTS_WITH(p, pe - p, "Content-Length:")) { // strlen("Content-Length:") p += (sizeof("Content-Length:") - 1); // skip spaces while (*p == ' ') { p++; } content_length_ = strtoull(p, nullptr, 10); known_length = 1; } else if (SW_STR_ISTARTS_WITH(p, pe - p, "Connection:")) { // strlen("Connection:") p += (sizeof("Connection:") - 1); // skip spaces while (*p == ' ') { p++; } if (SW_STR_ISTARTS_WITH(p, pe - p, "keep-alive")) { keep_alive = 1; } } else if (SW_STR_ISTARTS_WITH(p, pe - p, "Transfer-Encoding:")) { // strlen("Transfer-Encoding:") p += (sizeof("Transfer-Encoding:") - 1); // skip spaces while (*p == ' ') { p++; } if (SW_STR_ISTARTS_WITH(p, pe - p, "chunked")) { chunked = 1; } } else if (SW_STR_ISTARTS_WITH(p, pe - p, "Content-Type:")) { p += (sizeof("Content-Type:") - 1); while (*p == ' ') { p++; } if (SW_STR_ISTARTS_WITH(p, pe - p, "multipart/form-data")) { form_data_ = new FormData(); form_data_->multipart_boundary_buf = p + (sizeof("multipart/form-data") - 1); form_data_->multipart_boundary_len = strchr(p, '\r') - form_data_->multipart_boundary_buf; } } } } header_parsed = 1; if (chunked && known_length && content_length_ == 0) { nobody_chunked = 1; } } bool Request::init_multipart_parser(const Server *server) { char *boundary_str; int boundary_len; if (!parse_multipart_boundary( form_data_->multipart_boundary_buf, form_data_->multipart_boundary_len, 0, &boundary_str, &boundary_len)) { return false; } form_data_->multipart_parser_ = multipart_parser_init(boundary_str, boundary_len, &mt_parser_settings); if (!form_data_->multipart_parser_) { swoole_warning("multipart_parser_init() failed"); return false; } form_data_->multipart_parser_->data = this; auto tmp_buffer = new String(SW_BUFFER_SIZE_BIG); tmp_buffer->append(buffer_->str + header_length_, buffer_->length - header_length_); form_data_->multipart_buffer_ = buffer_; form_data_->multipart_buffer_->length = header_length_; buffer_ = tmp_buffer; form_data_->upload_tmpfile_fmt_ = server->upload_tmp_dir + "/swoole.upfile.XXXXXX"; form_data_->upload_tmpfile = new String(form_data_->upload_tmpfile_fmt_); form_data_->upload_max_filesize = server->upload_max_filesize; return true; } void Request::destroy_multipart_parser() { auto tmp_buffer = buffer_; delete tmp_buffer; buffer_ = form_data_->multipart_buffer_; form_data_->multipart_buffer_ = nullptr; if (form_data_->multipart_parser_->fp) { fclose(form_data_->multipart_parser_->fp); unlink(form_data_->upload_tmpfile->str); } multipart_parser_free(form_data_->multipart_parser_); form_data_->multipart_parser_ = nullptr; delete form_data_->upload_tmpfile; form_data_->upload_tmpfile = nullptr; delete form_data_; form_data_ = nullptr; } bool Request::parse_multipart_data(String *buffer) { excepted = 0; ssize_t n = multipart_parser_execute(form_data_->multipart_parser_, buffer->str, buffer->length); swoole_trace("multipart_parser_execute: buffer->length=%lu, n=%lu\n", buffer->length, n); if (n < 0) { int l_error = multipart_parser_error_msg(form_data_->multipart_parser_, sw_tg_buffer()->str, sw_tg_buffer()->size); swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, reason: %.*s", l_error, sw_tg_buffer()->str); return false; } else if ((size_t) n != buffer->length) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %zu/%zu bytes processed", n, buffer->length); return excepted; } buffer->clear(); return true; } Request::~Request() { if (form_data_) { destroy_multipart_parser(); } } bool Request::has_expect_header() const { char *buf = buffer_->str; size_t len = buffer_->length; char *pe = buf + len; for (char *p = buf; p < pe; p++) { if (*p == '\r' && (size_t) (pe - p) > sizeof("\r\nExpect")) { p += 2; if (SW_STR_ISTARTS_WITH(p, pe - p, "Expect: ")) { p += sizeof("Expect: ") - 1; if (SW_STR_ISTARTS_WITH(p, pe - p, "100-continue")) { return true; } else { return false; } } else { p++; } } } return false; } int Request::get_header_length() { char *p = buffer_->str + buffer_->offset; char *pe = buffer_->str + buffer_->length; for (; p <= pe - (sizeof("\r\n\r\n") - 1); p++) { if (memcmp(p, SW_STRL("\r\n\r\n")) == 0) { // strlen(header) + strlen("\r\n\r\n") header_length_ = buffer_->offset = p - buffer_->str + (sizeof("\r\n\r\n") - 1); return SW_OK; } } buffer_->offset = p - buffer_->str; return SW_ERR; } int Request::get_chunked_body_length() { const char *p = buffer_->str + buffer_->offset; const char *pe = buffer_->str + buffer_->length; /** * Ending with SW_HTTP_CHUNK_EOF indicates that the HTTP request may have been fully received, * but this is not certain. It is still necessary to skip over the data sections based on * the length of each chunk of the HTTP data until SW_HTTP_CHUNK_EOF (\0\r\n\r\n) is found. */ if (static_cast(pe - p) < sizeof(SW_HTTP_CHUNK_EOF) - 1 || memcmp(pe - sizeof(SW_HTTP_CHUNK_EOF) + 1, SW_STRL(SW_HTTP_CHUNK_EOF)) != 0) { return SW_ERR; } while (true) { char *endptr; size_t chunk_length = strtoul(p, &endptr, 16); if (endptr == nullptr || *endptr != '\r') { break; } swoole_trace_log(SW_TRACE_HTTP, "chunk_length=%zu, chunk_len_str=%.*s\n", chunk_length, (int) (endptr - p), p); // Found the HTTP Chunk EOF if (chunk_length == 0) { known_length = 1; content_length_ = endptr - (buffer_->str + header_length_) + 4; return SW_OK; } else { // chunk length [hex str] + CRLF + data + CRLF p = endptr + 2 + chunk_length + 2; // Continue to parse the next segment of HTTP CHUNK data. if (p < pe) { continue; } /** * If the calculated data length exceeds the length of the currently received data, * then SW_HTTP_CHUNK_EOF may be part of the data, * necessitating the reception of additional data and recalculating the HTTP Chunk length. */ return SW_ERR; } break; } excepted = 1; return SW_ERR; } std::string Request::get_header(const char *name) const { size_t name_len = strlen(name); char *p = buffer_->str + url_offset_ + url_length_ + 10; char *pe = buffer_->str + header_length_; char *buffer = nullptr; char *colon = nullptr; int state = 0; int i = 0; bool is_error_header_name = false; for (; p < pe; p++) { switch (state) { case 0: if (SW_STR_ISTARTS_WITH(p, pe - p, "\r\n")) { i = 0; is_error_header_name = false; break; } if (!is_error_header_name && swoole_str_istarts_with(p, pe - p, name, name_len)) { colon = p + name_len; if (colon[0] != ':' || i > 1) { is_error_header_name = true; break; } p += name_len; state = 1; } i++; break; case 1: if (!isspace(*p)) { buffer = p; state = 2; } break; case 2: if (SW_STR_ISTARTS_WITH(p, pe - p, "\r\n")) { return {buffer, static_cast(p - buffer)}; } break; default: break; } } return {}; } int get_method(const char *method_str, size_t method_len) { int i = 0; for (; i < SW_HTTP_PRI; i++) { if (swoole_strcaseeq(method_strings[i], strlen(method_strings[i]), method_str, method_len)) { return i + 1; } } return -1; } const char *get_method_string(int method) { if (method <= 0 || method > SW_HTTP_PRI) { return nullptr; } return method_strings[method - 1]; } int dispatch_request(Server *serv, const Protocol *proto, Socket *_socket, const RecvData *rdata) { if (serv->is_unavailable()) { _socket->send(SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET), 0); return SW_ERR; } return Server::dispatch_task(proto, _socket, rdata); } //----------------------------------------------------------------- static void protocol_status_error(Socket *socket, const Connection *conn) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PROTOCOL_ERROR, "unexpected protocol status of session#%ld<%s:%d>", conn->session_id, conn->info.get_addr(), conn->info.get_port()); } ssize_t get_package_length(const Protocol *protocol, Socket *socket, PacketLength *pl) { auto *conn = static_cast(socket->object); if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { return websocket::get_package_length(protocol, socket, pl); } else if (conn->http2_stream) { return http2::get_frame_length(protocol, socket, pl); } else { protocol_status_error(socket, conn); return SW_ERR; } } uint8_t get_package_length_size(Socket *socket) { auto *conn = (Connection *) socket->object; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { return SW_WEBSOCKET_FRAME_HEADER_SIZE; } else if (conn->http2_stream) { return SW_HTTP2_FRAME_HEADER_SIZE; } else { protocol_status_error(socket, conn); return 0; } } int dispatch_frame(const Protocol *proto, Socket *socket, const RecvData *rdata) { auto *conn = (Connection *) socket->object; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { return websocket::dispatch_frame(proto, socket, rdata); } else if (conn->http2_stream) { return Server::dispatch_task(proto, socket, rdata); } else { protocol_status_error(socket, conn); return SW_ERR; } } } // namespace http_server } // namespace swoole ================================================ FILE: src/protocol/http2.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_http2.h" #include "swoole_protocol.h" using swoole::PacketLength; using swoole::Protocol; using swoole::network::Socket; namespace swoole { namespace http2 { static Settings default_settings = { SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE, SW_HTTP2_DEFAULT_ENABLE_PUSH, SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE, SW_HTTP2_DEFAULT_MAX_FRAME_SIZE, SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE, }; void put_default_setting(enum swHttp2SettingId id, uint32_t value) { switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: default_settings.header_table_size = value; break; case SW_HTTP2_SETTINGS_ENABLE_PUSH: default_settings.enable_push = value; break; case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: default_settings.max_concurrent_streams = value; break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: default_settings.init_window_size = value; break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: default_settings.max_frame_size = value; break; case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: default_settings.max_header_list_size = value; break; default: assert(0); break; } } uint32_t get_default_setting(enum swHttp2SettingId id) { switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: return default_settings.header_table_size; case SW_HTTP2_SETTINGS_ENABLE_PUSH: return default_settings.enable_push; case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: return default_settings.max_concurrent_streams; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: return default_settings.init_window_size; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: return default_settings.max_frame_size; case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: return default_settings.max_header_list_size; default: assert(0); return 0; } } static inline void pack_setting_item(char *_buf, enum swHttp2SettingId _id, uint32_t _value) { uint16_t id; uint32_t value; id = htons(_id); memcpy(_buf, &id, sizeof(id)); value = htonl(_value); memcpy(_buf + 2, &value, sizeof(value)); } size_t pack_setting_frame(char *buf, const Settings &settings, bool server_side) { char *p = buf; size_t size = SW_HTTP2_SETTING_OPTION_SIZE * (server_side ? 5 : 6); set_frame_header(p, SW_HTTP2_TYPE_SETTINGS, size, 0, 0); p += SW_HTTP2_FRAME_HEADER_SIZE; pack_setting_item(p, SW_HTTP2_SETTING_HEADER_TABLE_SIZE, settings.header_table_size); p += SW_HTTP2_SETTING_OPTION_SIZE; if (!server_side) { pack_setting_item(p, SW_HTTP2_SETTINGS_ENABLE_PUSH, settings.enable_push); p += SW_HTTP2_SETTING_OPTION_SIZE; } pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, settings.max_concurrent_streams); p += SW_HTTP2_SETTING_OPTION_SIZE; pack_setting_item(p, SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, settings.init_window_size); p += SW_HTTP2_SETTING_OPTION_SIZE; pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, settings.max_frame_size); p += SW_HTTP2_SETTING_OPTION_SIZE; pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, settings.max_header_list_size); p += SW_HTTP2_SETTING_OPTION_SIZE; return p - buf; } ReturnCode unpack_setting_data(const char *buf, ssize_t length, const std::function &cb) { uint16_t id = 0; uint32_t value = 0; while (length > 0) { id = ntohs(*(uint16_t *) (buf)); value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t))); auto rc = cb(id, value); if (rc != SW_SUCCESS) { return rc; } buf += sizeof(id) + sizeof(value); length -= sizeof(id) + sizeof(value); } return SW_SUCCESS; } int send_setting_frame(Protocol *protocol, Socket *_socket) { char setting_frame[SW_HTTP2_SETTING_FRAME_SIZE]; size_t n = pack_setting_frame(setting_frame, default_settings, true); return _socket->send(setting_frame, n, 0); } /** +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ */ ssize_t get_frame_length(const Protocol *protocol, Socket *conn, PacketLength *pl) { if (pl->buf_size < SW_HTTP2_FRAME_HEADER_SIZE) { return 0; } return get_length(pl->buf) + SW_HTTP2_FRAME_HEADER_SIZE; } const char *get_type(int type) { switch (type) { case SW_HTTP2_TYPE_DATA: return "DATA"; case SW_HTTP2_TYPE_HEADERS: return "HEADERS"; case SW_HTTP2_TYPE_PRIORITY: return "PRIORITY"; case SW_HTTP2_TYPE_RST_STREAM: return "RST_STREAM"; case SW_HTTP2_TYPE_SETTINGS: return "SETTINGS"; case SW_HTTP2_TYPE_PUSH_PROMISE: return "PUSH_PROMISE"; case SW_HTTP2_TYPE_PING: return "PING"; case SW_HTTP2_TYPE_GOAWAY: return "GOAWAY"; case SW_HTTP2_TYPE_WINDOW_UPDATE: return "WINDOW_UPDATE"; case SW_HTTP2_TYPE_CONTINUATION: return "CONTINUATION"; default: return "UNKNOWN"; } } int get_type_color(int type) { switch (type) { case SW_HTTP2_TYPE_DATA: case SW_HTTP2_TYPE_WINDOW_UPDATE: return SW_COLOR_MAGENTA; case SW_HTTP2_TYPE_HEADERS: case SW_HTTP2_TYPE_SETTINGS: case SW_HTTP2_TYPE_PUSH_PROMISE: case SW_HTTP2_TYPE_CONTINUATION: return SW_COLOR_GREEN; case SW_HTTP2_TYPE_PING: case SW_HTTP2_TYPE_PRIORITY: return SW_COLOR_WHITE; case SW_HTTP2_TYPE_RST_STREAM: case SW_HTTP2_TYPE_GOAWAY: default: return SW_COLOR_RED; } } } // namespace http2 } // namespace swoole ================================================ FILE: src/protocol/message_bus.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_message_bus.h" #include "swoole_process_pool.h" #include using swoole::network::Address; using swoole::network::Socket; namespace swoole { PacketPtr MessageBus::get_packet() const { PacketPtr pkt; if (buffer_->info.flags & SW_EVENT_DATA_PTR) { memcpy(&pkt, buffer_->data, sizeof(pkt)); } else if (buffer_->info.flags & SW_EVENT_DATA_OBJ_PTR) { String *object; memcpy(&object, buffer_->data, sizeof(object)); pkt.data = object->str; pkt.length = object->length; } else { pkt.data = buffer_->data; pkt.length = buffer_->info.len; } return pkt; } bool MessageBus::alloc_buffer() { void *_ptr = allocator_->malloc(buffer_size_); if (_ptr) { buffer_ = (PipeBuffer *) _ptr; sw_memset_zero(&buffer_->info, sizeof(buffer_->info)); return true; } else { return false; } } void MessageBus::pass(const SendData *task) const { memcpy(&buffer_->info, &task->info, sizeof(buffer_->info)); if (task->info.len > 0) { buffer_->info.flags = SW_EVENT_DATA_PTR; PacketPtr pkt{task->info.len, (char *) task->data}; buffer_->info.len = sizeof(pkt); memcpy(buffer_->data, &pkt, sizeof(pkt)); } } char *MessageBus::move_packet() { uint64_t msg_id = buffer_->info.msg_id; auto iter = packet_pool_.find(msg_id); if (iter != packet_pool_.end()) { auto str = iter->second.get(); char *val = str->str; str->str = nullptr; return val; } else { return nullptr; } } String *MessageBus::get_packet_buffer() { String *packet_buffer = nullptr; auto iter = packet_pool_.find(buffer_->info.msg_id); if (iter == packet_pool_.end()) { if (!buffer_->is_begin()) { return nullptr; } packet_buffer = make_string(buffer_->info.len, allocator_); packet_pool_.emplace(buffer_->info.msg_id, std::shared_ptr(packet_buffer)); } else { packet_buffer = iter->second.get(); } return packet_buffer; } ReturnCode MessageBus::prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer) { recv_chunk_count++; if (!buffer_->is_end()) { /** * if the reactor thread sends too many chunks to the worker process, * the worker process may receive chunks all the time, * resulting in the worker process being unable to handle other tasks. * in order to make the worker process handle tasks fairly, * the maximum number of consecutive chunks received by the worker is limited. */ if (recv_chunk_count >= SW_WORKER_MAX_RECV_CHUNK_COUNT) { swoole_trace_log(SW_TRACE_WORKER, "worker#%d receives the chunk data to the maximum[%d], return to event loop", swoole_get_worker_id(), recv_chunk_count); return SW_WAIT; } return SW_CONTINUE; } else { /** * Because we don't want to split the EventData parameters into DataHead and data, * we store the value of the worker_buffer pointer in EventData.data. * The value of this pointer will be fetched in the Server::get_pipe_packet() function. */ buffer_->info.flags |= SW_EVENT_DATA_OBJ_PTR; memcpy(buffer_->data, &packet_buffer, sizeof(packet_buffer)); swoole_trace("msg_id=%" PRIu64 ", len=%u", buffer_->info.msg_id, buffer_->info.len); return SW_READY; } } /** * @return -1: a fatal error has occurred and needs to be terminated * @return 0: continue * @return >0: success */ ssize_t MessageBus::read(Socket *sock) { ssize_t recv_n = 0; uint16_t recv_chunk_count = 0; DataHead *info = &buffer_->info; struct iovec buffers[2]; _read_from_pipe: recv_n = recv(sock->get_fd(), info, sizeof(buffer_->info), MSG_PEEK); if (recv_n < 0) { if (sock->catch_read_error(errno) == SW_WAIT) { return SW_OK; } return SW_ERR; } else if (recv_n == 0) { swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); return SW_ERR; } if (!buffer_->is_chunked()) { return sock->read(buffer_, sizeof(buffer_->info) + buffer_->info.len); } auto packet_buffer = get_packet_buffer(); if (packet_buffer == nullptr) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", info->msg_id, sock->get_fd(), info->reactor_id); // Read data from the socket buffer and discard it. recv(sock->get_fd(), info, sizeof(buffer_->info), 0); return SW_OK; } size_t remain_len = buffer_->info.len - packet_buffer->length; buffers[0].iov_base = info; buffers[0].iov_len = sizeof(buffer_->info); buffers[1].iov_base = packet_buffer->str + packet_buffer->length; buffers[1].iov_len = SW_MIN(buffer_size_ - sizeof(buffer_->info), remain_len); recv_n = readv(sock->get_fd(), buffers, 2); if (recv_n == 0) { swoole_warning("receive pipeline data error, pipe_fd=%d, reactor_id=%d", sock->get_fd(), info->reactor_id); return SW_ERR; } if (recv_n < 0 && sock->catch_read_error(errno) == SW_WAIT) { return SW_OK; } if (recv_n > 0) { packet_buffer->length += (recv_n - sizeof(buffer_->info)); swoole_trace("append msgid=%" PRIu64 ", buffer=%p, n=%ld", buffer_->info.msg_id, packet_buffer, recv_n); } switch (prepare_packet(recv_chunk_count, packet_buffer)) { case SW_READY: return recv_n; case SW_CONTINUE: goto _read_from_pipe; case SW_WAIT: return SW_OK; default: assert(0); return SW_ERR; } } /** * Notice: only supports dgram type socket */ ssize_t MessageBus::read_with_buffer(Socket *sock) { ssize_t recv_n; uint16_t recv_chunk_count = 0; _read_from_pipe: recv_n = sock->read(buffer_, buffer_size_); if (recv_n < 0) { if (sock->catch_read_error(errno) == SW_WAIT) { return SW_OK; } return SW_ERR; } else if (recv_n == 0) { swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); return SW_ERR; } recv_chunk_count++; if (!buffer_->is_chunked()) { return recv_n; } String *packet_buffer = get_packet_buffer(); if (packet_buffer == nullptr) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", buffer_->info.msg_id, sock->get_fd(), buffer_->info.reactor_id); return SW_ERR; } packet_buffer->append(buffer_->data, recv_n - sizeof(buffer_->info)); switch (prepare_packet(recv_chunk_count, packet_buffer)) { case SW_READY: return recv_n; case SW_CONTINUE: goto _read_from_pipe; case SW_WAIT: return SW_OK; default: assert(0); return SW_ERR; } } bool MessageBus::write(Socket *sock, SendData *resp) const { const char *payload = resp->data; uint32_t l_payload = resp->info.len; off_t offset = 0; uint32_t copy_n; struct iovec iov[2]; uint64_t msg_id = id_generator_(); uint32_t max_length = buffer_size_ - sizeof(resp->info); resp->info.msg_id = msg_id; auto send_fn = [](Socket *sock, const iovec *iov, size_t iovcnt) { if (swoole_event_is_available()) { return swoole_event_writev(sock, iov, iovcnt); } else { return sock->writev_sync(iov, iovcnt); } }; if (l_payload == 0 || payload == nullptr) { resp->info.flags = 0; resp->info.len = 0; iov[0].iov_base = &resp->info; iov[0].iov_len = sizeof(resp->info); return send_fn(sock, iov, 1) == (ssize_t) iov[0].iov_len; } if (!always_chunked_transfer_ && l_payload <= max_length) { resp->info.flags = 0; resp->info.len = l_payload; iov[0].iov_base = &resp->info; iov[0].iov_len = sizeof(resp->info); iov[1].iov_base = (void *) payload; iov[1].iov_len = l_payload; if (send_fn(sock, iov, 2) == (ssize_t) (sizeof(resp->info) + l_payload)) { return true; } if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_IPC_MSG_MIN) { max_length = SW_IPC_MSG_MIN; } else { return false; } } resp->info.flags = SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN; resp->info.len = l_payload; while (l_payload > 0) { if (l_payload > max_length) { copy_n = max_length; } else { resp->info.flags |= SW_EVENT_DATA_END; copy_n = l_payload; } iov[0].iov_base = &resp->info; iov[0].iov_len = sizeof(resp->info); iov[1].iov_base = (void *) (payload + offset); iov[1].iov_len = copy_n; swoole_trace("finish, type=%d|len=%u", resp->info.type, copy_n); if (send_fn(sock, iov, 2) < 0) { if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_IPC_MSG_MIN) { max_length = SW_IPC_MSG_MIN; if (resp->info.flags & SW_EVENT_DATA_END) { resp->info.flags &= ~SW_EVENT_DATA_END; } continue; } return false; } if (resp->info.flags & SW_EVENT_DATA_BEGIN) { resp->info.flags &= ~SW_EVENT_DATA_BEGIN; } l_payload -= copy_n; offset += copy_n; } return true; } size_t MessageBus::get_memory_size() const { size_t size = buffer_size_; for (auto &p : packet_pool_) { size += p.second->size; } return size; } void MessageBus::init_pipe_socket(const Socket *sock) { int pipe_fd = sock->get_fd(); if ((size_t) pipe_fd >= pipe_sockets_.size()) { pipe_sockets_.resize(pipe_fd + 1); } auto _socket = make_socket(pipe_fd, SW_FD_PIPE); _socket->buffer_size = UINT_MAX; if (!_socket->nonblock) { _socket->set_nonblock(); } pipe_sockets_[pipe_fd] = _socket; } MessageBus::~MessageBus() { for (auto _socket : pipe_sockets_) { if (_socket) { _socket->fd = -1; _socket->free(); } } } } // namespace swoole ================================================ FILE: src/protocol/mime_type.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_mime_type.h" #include namespace swoole { namespace mime_type { static std::unordered_map map_( {{"ez", "application/andrew-inset"}, {"aw", "application/applixware"}, {"atom", "application/atom+xml"}, {"atomcat", "application/atomcat+xml"}, {"atomsvc", "application/atomsvc+xml"}, {"bdoc", "application/bdoc"}, {"ccxml", "application/ccxml+xml"}, {"cdmia", "application/cdmi-capability"}, {"cdmic", "application/cdmi-container"}, {"cdmid", "application/cdmi-domain"}, {"cdmio", "application/cdmi-object"}, {"cdmiq", "application/cdmi-queue"}, {"cu", "application/cu-seeme"}, {"mpd", "application/dash+xml"}, {"davmount", "application/davmount+xml"}, {"dbk", "application/docbook+xml"}, {"dssc", "application/dssc+der"}, {"xdssc", "application/dssc+xml"}, {"ecma", "application/ecmascript"}, {"emma", "application/emma+xml"}, {"epub", "application/epub+zip"}, {"exi", "application/exi"}, {"pfr", "application/font-tdpfr"}, {"woff", "application/font-woff"}, {"geojson", "application/geo+json"}, {"gml", "application/gml+xml"}, {"gpx", "application/gpx+xml"}, {"gxf", "application/gxf"}, {"gz", "application/gzip"}, {"hjson", "application/hjson"}, {"stk", "application/hyperstudio"}, {"ink", "application/inkml+xml"}, {"inkml", "application/inkml+xml"}, {"ipfix", "application/ipfix"}, {"jar", "application/java-archive"}, {"war", "application/java-archive"}, {"ear", "application/java-archive"}, {"apk", "application/vnd.android.package-archive"}, {"ser", "application/java-serialized-object"}, {"class", "application/java-vm"}, {"js", "application/javascript"}, {"mjs", "application/javascript"}, {"json", "application/json"}, {"map", "application/json"}, {"json5", "application/json5"}, {"jsonml", "application/jsonml+json"}, {"jsonld", "application/ld+json"}, {"lostxml", "application/lost+xml"}, {"hqx", "application/mac-binhex40"}, {"cpt", "application/mac-compactpro"}, {"mads", "application/mads+xml"}, {"webmanifest", "application/manifest+json"}, {"mrc", "application/marc"}, {"mrcx", "application/marcxml+xml"}, {"ma", "application/mathematica"}, {"nb", "application/mathematica"}, {"mb", "application/mathematica"}, {"mathml", "application/mathml+xml"}, {"mbox", "application/mbox"}, {"mscml", "application/mediaservercontrol+xml"}, {"metalink", "application/metalink+xml"}, {"meta4", "application/metalink4+xml"}, {"mets", "application/mets+xml"}, {"mods", "application/mods+xml"}, {"m21", "application/mp21"}, {"mp21", "application/mp21"}, {"mp4s", "application/mp4"}, {"m4p", "application/mp4"}, {"doc", "application/msword"}, {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {"dot", "application/msword"}, {"mxf", "application/mxf"}, {"bin", "application/octet-stream"}, {"dms", "application/octet-stream"}, {"lrf", "application/octet-stream"}, {"mar", "application/octet-stream"}, {"so", "application/octet-stream"}, {"dist", "application/octet-stream"}, {"distz", "application/octet-stream"}, {"pkg", "application/octet-stream"}, {"bpk", "application/octet-stream"}, {"dump", "application/octet-stream"}, {"elc", "application/octet-stream"}, {"deploy", "application/octet-stream"}, {"exe", "application/octet-stream"}, {"dll", "application/octet-stream"}, {"deb", "application/octet-stream"}, {"dmg", "application/octet-stream"}, {"iso", "application/octet-stream"}, {"img", "application/octet-stream"}, {"msi", "application/octet-stream"}, {"msp", "application/octet-stream"}, {"msm", "application/octet-stream"}, {"buffer", "application/octet-stream"}, {"oda", "application/oda"}, {"opf", "application/oebps-package+xml"}, {"ogx", "application/ogg"}, {"omdoc", "application/omdoc+xml"}, {"onetoc", "application/onenote"}, {"onetoc2", "application/onenote"}, {"onetmp", "application/onenote"}, {"onepkg", "application/onenote"}, {"oxps", "application/oxps"}, {"xer", "application/patch-ops-error+xml"}, {"pdf", "application/pdf"}, {"pgp", "application/pgp-encrypted"}, {"asc", "application/pgp-signature"}, {"sig", "application/pgp-signature"}, {"prf", "application/pics-rules"}, {"p10", "application/pkcs10"}, {"p7m", "application/pkcs7-mime"}, {"p7c", "application/pkcs7-mime"}, {"p7s", "application/pkcs7-signature"}, {"p8", "application/pkcs8"}, {"ac", "application/pkix-attr-cert"}, {"cer", "application/pkix-cert"}, {"crl", "application/pkix-crl"}, {"pkipath", "application/pkix-pkipath"}, {"pki", "application/pkixcmp"}, {"pls", "application/pls+xml"}, {"ai", "application/postscript"}, {"eps", "application/postscript"}, {"ps", "application/postscript"}, {"pskcxml", "application/pskc+xml"}, {"raml", "application/raml+yaml"}, {"rdf", "application/rdf+xml"}, {"rif", "application/reginfo+xml"}, {"rnc", "application/relax-ng-compact-syntax"}, {"rl", "application/resource-lists+xml"}, {"rld", "application/resource-lists-diff+xml"}, {"rs", "application/rls-services+xml"}, {"gbr", "application/rpki-ghostbusters"}, {"mft", "application/rpki-manifest"}, {"roa", "application/rpki-roa"}, {"rsd", "application/rsd+xml"}, {"rss", "application/rss+xml"}, {"rtf", "application/rtf"}, {"sbml", "application/sbml+xml"}, {"scq", "application/scvp-cv-request"}, {"scs", "application/scvp-cv-response"}, {"spq", "application/scvp-vp-request"}, {"spp", "application/scvp-vp-response"}, {"sdp", "application/sdp"}, {"setpay", "application/set-payment-initiation"}, {"setreg", "application/set-registration-initiation"}, {"shf", "application/shf+xml"}, {"smi", "application/smil+xml"}, {"smil", "application/smil+xml"}, {"rq", "application/sparql-query"}, {"srx", "application/sparql-results+xml"}, {"gram", "application/srgs"}, {"grxml", "application/srgs+xml"}, {"sru", "application/sru+xml"}, {"ssdl", "application/ssdl+xml"}, {"ssml", "application/ssml+xml"}, {"tei", "application/tei+xml"}, {"teicorpus", "application/tei+xml"}, {"tfi", "application/thraud+xml"}, {"tsd", "application/timestamped-data"}, {"vxml", "application/voicexml+xml"}, {"wasm", "application/wasm"}, {"wgt", "application/widget"}, {"hlp", "application/winhlp"}, {"wsdl", "application/wsdl+xml"}, {"wspolicy", "application/wspolicy+xml"}, {"xaml", "application/xaml+xml"}, {"xdf", "application/xcap-diff+xml"}, {"xenc", "application/xenc+xml"}, {"xhtml", "application/xhtml+xml"}, {"xht", "application/xhtml+xml"}, {"xml", "application/xml"}, {"xsl", "application/xml"}, {"xsd", "application/xml"}, {"rng", "application/xml"}, {"dtd", "application/xml-dtd"}, {"xop", "application/xop+xml"}, {"xpl", "application/xproc+xml"}, {"xslt", "application/xslt+xml"}, {"xspf", "application/xspf+xml"}, {"mxml", "application/xv+xml"}, {"xhvml", "application/xv+xml"}, {"xvml", "application/xv+xml"}, {"xvm", "application/xv+xml"}, {"yang", "application/yang"}, {"yin", "application/yin+xml"}, {"zip", "application/zip"}, {"*3gpp", "audio/3gpp"}, {"adp", "audio/adpcm"}, {"au", "audio/basic"}, {"snd", "audio/basic"}, {"mid", "audio/midi"}, {"midi", "audio/midi"}, {"kar", "audio/midi"}, {"rmi", "audio/midi"}, {"*mp3", "audio/mp3"}, {"m4a", "audio/mp4"}, {"mp4a", "audio/mp4"}, {"mpga", "audio/mpeg"}, {"mp2", "audio/mpeg"}, {"mp2a", "audio/mpeg"}, {"mp3", "audio/mpeg"}, {"m2a", "audio/mpeg"}, {"m3a", "audio/mpeg"}, {"oga", "audio/ogg"}, {"ogg", "audio/ogg"}, {"spx", "audio/ogg"}, {"s3m", "audio/s3m"}, {"sil", "audio/silk"}, {"wav", "audio/wav"}, {"*wav", "audio/wave"}, {"weba", "audio/webm"}, {"xm", "audio/xm"}, {"ttc", "font/collection"}, {"otf", "font/otf"}, {"ttf", "font/ttf"}, {"*woff", "font/woff"}, {"woff2", "font/woff2"}, {"apng", "image/apng"}, {"bmp", "image/bmp"}, {"cgm", "image/cgm"}, {"g3", "image/g3fax"}, {"gif", "image/gif"}, {"ief", "image/ief"}, {"jp2", "image/jp2"}, {"jpg2", "image/jp2"}, {"jpeg", "image/jpeg"}, {"jpg", "image/jpeg"}, {"jpe", "image/jpeg"}, {"jpm", "image/jpm"}, {"jpx", "image/jpx"}, {"jpf", "image/jpx"}, {"ktx", "image/ktx"}, {"png", "image/png"}, {"sgi", "image/sgi"}, {"svg", "image/svg+xml"}, {"svgz", "image/svg+xml"}, {"tiff", "image/tiff"}, {"tif", "image/tiff"}, {"webp", "image/webp"}, {"disposition-notification", "message/disposition-notification"}, {"u8msg", "message/global"}, {"u8dsn", "message/global-delivery-status"}, {"u8mdn", "message/global-disposition-notification"}, {"u8hdr", "message/global-headers"}, {"eml", "message/rfc822"}, {"mime", "message/rfc822"}, {"gltf", "model/gltf+json"}, {"glb", "model/gltf-binary"}, {"igs", "model/iges"}, {"iges", "model/iges"}, {"msh", "model/mesh"}, {"mesh", "model/mesh"}, {"silo", "model/mesh"}, {"wrl", "model/vrml"}, {"vrml", "model/vrml"}, {"x3db", "model/x3d+binary"}, {"x3dbz", "model/x3d+binary"}, {"x3dv", "model/x3d+vrml"}, {"x3dvz", "model/x3d+vrml"}, {"x3d", "model/x3d+xml"}, {"x3dz", "model/x3d+xml"}, {"appcache", "text/cache-manifest"}, {"manifest", "text/cache-manifest"}, {"ics", "text/calendar"}, {"ifb", "text/calendar"}, {"coffee", "text/coffeescript"}, {"litcoffee", "text/coffeescript"}, {"css", "text/css"}, {"csv", "text/csv"}, {"html", "text/html"}, {"htm", "text/html"}, {"shtml", "text/html"}, {"jade", "text/jade"}, {"jsx", "text/jsx"}, {"less", "text/less"}, {"markdown", "text/markdown"}, {"md", "text/markdown"}, {"mml", "text/mathml"}, {"n3", "text/n3"}, {"txt", "text/plain"}, {"text", "text/plain"}, {"conf", "text/plain"}, {"def", "text/plain"}, {"list", "text/plain"}, {"log", "text/plain"}, {"in", "text/plain"}, {"ini", "text/plain"}, {"rtx", "text/richtext"}, {"*rtf", "text/rtf"}, {"sgml", "text/sgml"}, {"sgm", "text/sgml"}, {"shex", "text/shex"}, {"slim", "text/slim"}, {"slm", "text/slim"}, {"stylus", "text/stylus"}, {"styl", "text/stylus"}, {"tsv", "text/tab-separated-values"}, {"t", "text/troff"}, {"tr", "text/troff"}, {"roff", "text/troff"}, {"man", "text/troff"}, {"me", "text/troff"}, {"ms", "text/troff"}, {"ttl", "text/turtle"}, {"uri", "text/uri-list"}, {"uris", "text/uri-list"}, {"urls", "text/uri-list"}, {"vcard", "text/vcard"}, {"vtt", "text/vtt"}, {"*xml", "text/xml"}, {"yaml", "text/yaml"}, {"yml", "text/yaml"}, {"3gp", "video/3gpp"}, {"3gpp", "video/3gpp"}, {"3g2", "video/3gpp2"}, {"h261", "video/h261"}, {"h263", "video/h263"}, {"h264", "video/h264"}, {"jpgv", "video/jpeg"}, {"*jpm", "video/jpm"}, {"jpgm", "video/jpm"}, {"mj2", "video/mj2"}, {"mjp2", "video/mj2"}, {"ts", "video/mp2t"}, {"mp4", "video/mp4"}, {"mp4v", "video/mp4"}, {"mpg4", "video/mp4"}, {"mpeg", "video/mpeg"}, {"mpg", "video/mpeg"}, {"mpe", "video/mpeg"}, {"m1v", "video/mpeg"}, {"m2v", "video/mpeg"}, {"ogv", "video/ogg"}, {"qt", "video/quicktime"}, {"mov", "video/quicktime"}, {"webm", "video/webm"}, // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types {"aac", "audio/aac"}, {"abw", "application/x-abiword"}, {"arc", "application/octet-stream"}, {"avi", "video/x-msvideo"}, {"azw", "application/vnd.amazon.ebook"}, {"bz", "application/x-bzip"}, {"bz2", "application/x-bzip2"}, {"csh", "application/x-csh"}, {"eot", "application/vnd.ms-fontobject"}, {"es", "application/ecmascript"}, {"ico", "image/x-icon"}, {"mpkg", "application/vnd.apple.installer+xml"}, {"odp", "application/vnd.oasis.opendocument.presentation"}, {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, {"odt", "application/vnd.oasis.opendocument.text"}, {"ppt", "application/vnd.ms-powerpoint"}, {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {"rar", "application/x-rar-compressed"}, {"sh", "application/x-sh"}, {"swf", "application/x-shockwave-flash"}, {"tar", "application/x-tar"}, {"vsd", "application/vnd.visio"}, {"xls", "application/vnd.ms-excel"}, {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"xul", "application/vnd.mozilla.xul+xml"}, {"7z", "application/x-7z-compressed"}}); static const std::string octet_stream("application/octet-stream"); static std::string get_suffix(const std::string &filename) { std::string suffix = std::string(filename).substr(filename.find_last_of('.') + 1); std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); return suffix; } const std::unordered_map &list() { return map_; } const std::string &get(const std::string &filename) { std::string suffix = get_suffix(filename); auto i = map_.find(suffix); if (i != map_.end()) { return i->second; } return octet_stream; } bool add(const std::string &suffix, const std::string &mime_type) { if (map_.find(suffix) == map_.end()) { map_[suffix] = mime_type; return true; } else { return false; } } void set(const std::string &suffix, const std::string &mime_type) { map_[suffix] = mime_type; } bool del(const std::string &suffix) { if (map_.find(suffix) == map_.end()) { return false; } else { map_.erase(suffix); return true; } } bool exists(const std::string &filename) { std::string suffix = get_suffix(filename); return map_.find(suffix) != map_.end(); } } // namespace mime_type } // namespace swoole ================================================ FILE: src/protocol/mqtt.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | | Author: Xinhua Guo | +----------------------------------------------------------------------+ */ #include "swoole_mqtt.h" #include "swoole_protocol.h" using swoole::network::Socket; namespace swoole { namespace mqtt { void print_package(Packet *pkg) { printf("type=%d, length=%d\n", pkg->type, pkg->length); } void set_protocol(Protocol *protocol) { protocol->package_length_size = SW_MQTT_MAX_LENGTH_SIZE; protocol->package_length_offset = 1; protocol->package_body_offset = 0; protocol->get_package_length = get_package_length; } // recv variable_header packet twice may cause that the '*data' contain the payload data, // but there's no chance to read the next mqtt request ,because MQTT client will recv ACK blocking #define SW_MQTT_RECV_LEN_AGAIN 0 ssize_t get_package_length(const Protocol *protocol, Socket *conn, PacketLength *pl) { //-1 cause the arg 'size' contain length_offset(1 byte len) uint32_t recv_variable_header_size = (pl->buf_size - 1); if (recv_variable_header_size < SW_MQTT_MIN_LENGTH_SIZE) { // recv continue return SW_MQTT_RECV_LEN_AGAIN; } uint8_t byte; int mul = 1; ssize_t length = 0; ssize_t variable_header_byte_count = 0; while (true) { variable_header_byte_count++; byte = pl->buf[variable_header_byte_count]; length += (byte & 127) * mul; mul *= 128; if ((byte & 128) == 0) { // done! there is no surplus length byte break; } if (variable_header_byte_count >= SW_MQTT_MAX_LENGTH_SIZE) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "bad request, the variable header size is larger than %d", SW_MQTT_MAX_LENGTH_SIZE); return SW_ERR; } if (variable_header_byte_count >= recv_variable_header_size) { // length not enough return SW_MQTT_RECV_LEN_AGAIN; } } // payload_length + variable_header_byte_count + length_offset(1) return length + variable_header_byte_count + 1; } } // namespace mqtt } // namespace swoole ================================================ FILE: src/protocol/redis.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" #include "swoole_server.h" #include "swoole_redis.h" namespace swoole { namespace redis { struct Request { uint8_t state; int n_lines_total; int n_lines_received; int n_bytes_total; int n_bytes_received; int offset; }; const char *get_number(const char *p, int *_ret) { char *endptr; p++; int ret = strtol(p, &endptr, 10); if (strncmp(SW_CRLF, endptr, SW_CRLF_LEN) == 0) { p += (endptr - p) + SW_CRLF_LEN; *_ret = ret; return p; } else { return nullptr; } } int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { const char *p, *pe; int ret; char *buf_ptr; size_t buf_size; RecvData rdata{}; Request *request; network::Socket *socket = conn->socket; if (conn->object == nullptr) { request = static_cast(sw_malloc(sizeof(Request))); if (!request) { swoole_warning("malloc(%ld) failed", sizeof(Request)); return SW_ERR; } sw_memset_zero(request, sizeof(Request)); conn->object = request; } else { request = static_cast(conn->object); } _recv_data: buf_ptr = buffer->str + buffer->length; buf_size = buffer->size - buffer->length; int n = socket->recv(buf_ptr, buf_size, 0); if (n < 0) { switch (socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("recv from socket#%d failed", conn->fd); return SW_OK; case SW_CLOSE: return SW_ERR; default: return SW_OK; } } else if (n == 0) { return SW_ERR; } else { buffer->length += n; if (strncmp(buffer->str + buffer->length - SW_CRLF_LEN, SW_CRLF, SW_CRLF_LEN) != 0) { if (buffer->size < protocol->package_max_length) { uint32_t extend_size = swoole_size_align(buffer->size * 2, swoole_pagesize()); if (extend_size > protocol->package_max_length) { extend_size = protocol->package_max_length; } buffer->extend(extend_size); } else if (buffer->length == buffer->size) { _package_too_big: swoole_warning("Package is too big. package_length=%ld", buffer->length); return SW_ERR; } goto _recv_data; } p = buffer->str; pe = p + buffer->length; do { switch (request->state) { case STATE_RECEIVE_TOTAL_LINE: if (*p == '*') { if ((p = get_number(p, &ret)) == nullptr) { goto _failed; } request->n_lines_total = ret; request->state = STATE_RECEIVE_LENGTH; break; } /* no break */ case STATE_RECEIVE_LENGTH: if (*p == '$') { if ((p = get_number(p, &ret)) == nullptr) { goto _failed; } if (ret < 0) { break; } if (ret + (p - buffer->str) > protocol->package_max_length) { goto _package_too_big; } request->n_bytes_total = ret; request->state = STATE_RECEIVE_STRING; break; } // integer else if (*p == ':') { if ((p = get_number(p, &ret)) == nullptr) { goto _failed; } break; } /* no break */ case STATE_RECEIVE_STRING: if (pe - p < request->n_bytes_total - request->n_bytes_received) { request->n_bytes_received += pe - p; return SW_OK; } else { p += request->n_bytes_total + SW_CRLF_LEN; request->n_bytes_total = 0; request->n_lines_received++; request->state = STATE_RECEIVE_LENGTH; buffer->offset = buffer->length; if (request->n_lines_received == request->n_lines_total) { rdata.info.len = buffer->length; rdata.data = buffer->str; if (protocol->onPackage(protocol, socket, &rdata) < 0) { return SW_ERR; } if (socket->removed) { return SW_OK; } buffer->clear(); sw_memset_zero(request, sizeof(Request)); return SW_OK; } } break; default: goto _failed; } } while (p < pe); } _failed: swoole_warning("redis protocol error"); return SW_ERR; } void format_nil(String *buf) { buf->append(SW_STRL(SW_REDIS_RETURN_NIL)); } void format(String *buf, ReplyType type, const std::string &value) { if (type == REPLY_STATUS) { if (value.empty()) { buf->append(SW_STRL("+OK\r\n")); } else { buf->format("+%.*s\r\n", value.length(), value.c_str()); } } else if (type == REPLY_ERROR) { if (value.empty()) { buf->append(SW_STRL("-ERR\r\n")); } else { buf->format("-%.*s\r\n", value.length(), value.c_str()); } } else if (type == REPLY_STRING) { if (!value.empty()) { buf->format("$%zu\r\n", value.length()); buf->append(value); buf->append(SW_CRLF, SW_CRLF_LEN); } } } void format(String *buf, ReplyType type, long value) { buf->format(":%" PRId64 "\r\n", value); } std::vector parse(const char *data, size_t len) { int state = STATE_RECEIVE_TOTAL_LINE; const char *p = data; const char *pe = p + len; int ret; int length = 0; std::vector result; do { switch (state) { case STATE_RECEIVE_TOTAL_LINE: if (*p == '*' && ((p = get_number(p, &ret)))) { state = STATE_RECEIVE_LENGTH; break; } /* no break */ case STATE_RECEIVE_LENGTH: if (*p == '$' && ((p = get_number(p, &ret)))) { if (ret == -1) { break; } length = ret; state = STATE_RECEIVE_STRING; break; } // integer else if (*p == ':' && ((p = get_number(p, &ret)))) { result.push_back(std::to_string(ret)); break; } /* no break */ case STATE_RECEIVE_STRING: result.emplace_back(p, length); p += length + SW_CRLF_LEN; state = STATE_RECEIVE_LENGTH; break; default: break; } } while (p < pe); return result; } } // namespace redis } // namespace swoole ================================================ FILE: src/protocol/socks5.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2017 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_proxy.h" #include "swoole_socket.h" namespace swoole { const char *Socks5Proxy::strerror(int code) { switch (code) { case 0x01: return "General failure"; case 0x02: return "Connection not allowed by ruleset"; case 0x03: return "Network unreachable"; case 0x04: return "Host unreachable"; case 0x05: return "Connection refused by destination host"; case 0x06: return "TTL expired"; case 0x07: return "command not supported / protocol error"; case 0x08: return "address type not supported"; default: return "Unknown error"; } } Socks5Proxy *Socks5Proxy::create( int socket_type, const std::string &host, int port, const std::string &user, const std::string &pwd) { if (user.length() > 250 || pwd.length() > 250) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS5 username or password is too long, max length is 250 bytes"); return nullptr; } auto socks5_proxy = new Socks5Proxy(); socks5_proxy->host = host; socks5_proxy->port = port; socks5_proxy->dns_tunnel = 1; socks5_proxy->socket_type = socket_type; if (!user.empty() && !pwd.empty()) { socks5_proxy->username = user; socks5_proxy->password = pwd; } return socks5_proxy; } ssize_t Socks5Proxy::pack_negotiate_request() { char *p = buf; p[0] = SW_SOCKS5_VERSION_CODE; // Version p[1] = 0x01; method = username.empty() ? SW_SOCKS5_METHOD_NO_AUTH : SW_SOCKS5_METHOD_AUTH; p[2] = method; return 3; } ssize_t Socks5Proxy::pack_auth_request() { char *p = buf; // username p[0] = 0x01; p[1] = username.length(); p += 2; if (!username.empty()) { memcpy(p, username.c_str(), username.length()); p += username.length(); } // password p[0] = password.length(); p += 1; if (!password.empty()) { memcpy(p, password.c_str(), password.length()); p += password.length(); } return p - buf; } bool Socks5Proxy::handshake(const char *rbuf, size_t rlen, const std::function &send_fn) { if (rlen < 2) { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, "SOCKS5 handshake failed, data length is too short"); return false; } const uchar resp_version = rbuf[0]; const uchar resp_result = rbuf[1]; if (state == SW_SOCKS5_STATE_HANDSHAKE) { if (resp_version != SW_SOCKS5_VERSION_CODE) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); return false; } if (method != resp_result) { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_METHOD, "SOCKS authentication method is not supported"); return false; } // authenticate request if (method == SW_SOCKS5_METHOD_AUTH) { const auto len = pack_auth_request(); state = SW_SOCKS5_STATE_AUTH; return send_fn(buf, len) == len; } // send connect request else { _send_connect_request: state = SW_SOCKS5_STATE_CONNECT; const auto len = pack_connect_request(); if (len < 0) { return false; } return send_fn(buf, len) == len; } } else if (state == SW_SOCKS5_STATE_AUTH) { if (resp_version != 0x01) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); return false; } if (resp_result != 0) { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS username/password authentication failed"); return false; } goto _send_connect_request; } else if (state == SW_SOCKS5_STATE_CONNECT) { if (resp_version != SW_SOCKS5_VERSION_CODE) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); return false; } #if 0 uchar reg = recv_data[2]; uchar type = recv_data[3]; uint32_t ip = *(uint32_t *) (recv_data + 4); uint16_t port = *(uint16_t *) (recv_data + 8); #endif if (resp_result == 0) { state = SW_SOCKS5_STATE_READY; return true; } else { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_SERVER_ERROR, "Socks5 server error, reason :%s", Socks5Proxy::strerror(resp_result)); return false; } } return true; } ssize_t Socks5Proxy::pack_connect_request() { char *p = buf; p[0] = SW_SOCKS5_VERSION_CODE; p[1] = 0x01; // CONNECT command p[2] = 0x00; // Reserved byte p += 3; if (dns_tunnel) { if (host.length() > 480) { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS5 host is too long, max length is 480 bytes"); return -1; } p[0] = 0x03; p[1] = target_host.length(); p += 2; memcpy(p, target_host.c_str(), target_host.length()); p += target_host.length(); } else { network::Address target_addr; if (!target_addr.assign(static_cast(socket_type), target_host, target_port, false)) { swoole_error_log( SW_LOG_NOTICE, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, "When disable SOCKS5 proxy DNS tunnel connection, the destination host must be an IP address."); return SW_ERR; } if (network::Socket::is_inet4(static_cast(socket_type))) { p[0] = 0x01; // IPv4 address type p += 1; memcpy(p, &target_addr.addr.inet_v4.sin_addr, sizeof(target_addr.addr.inet_v4.sin_addr)); p += sizeof(target_addr.addr.inet_v4.sin_addr); } else if (network::Socket::is_inet6(static_cast(socket_type))) { p[0] = 0x04; // IPv6 address type p += 1; memcpy(p, &target_addr.addr.inet_v6.sin6_addr, sizeof(target_addr.addr.inet_v6.sin6_addr)); p += sizeof(target_addr.addr.inet_v6.sin6_addr); } else { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, "Unsupported socket type for SOCKS5"); return SW_ERR; } } const auto _target_port = htons(target_port); memcpy(p, &_target_port, sizeof(_target_port)); p += 2; return p - buf; } } // namespace swoole ================================================ FILE: src/protocol/ssl.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_ssl.h" #include "swoole_util.h" using swoole::SSLContext; using swoole::network::Address; using swoole::network::Socket; #if OPENSSL_VERSION_NUMBER < 0x10100000L #error "OpenSSL 1.1.0 or later is required" #endif static bool openssl_init = false; static int ssl_connection_index = 0; static int ssl_port_index = 0; static int swoole_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); #if OPENSSL_VERSION_NUMBER < 0x10100000L static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context); #endif #ifdef SW_SUPPORT_DTLS static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len); static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len); #endif std::string swoole_ssl_get_version_message() { return swoole::std_string::format("OPENSSL_VERSION: %s\n", OPENSSL_VERSION_TEXT); } void swoole_ssl_init() { if (openssl_init) { return; } if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr)) { swoole_error("OPENSSL_init_ssl() failed"); return; } ssl_connection_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); if (ssl_connection_index < 0) { swoole_error("SSL_get_ex_new_index() failed"); return; } ssl_port_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); if (ssl_port_index < 0) { swoole_error("SSL_get_ex_new_index() failed"); return; } openssl_init = true; } int swoole_ssl_get_ex_connection_index() { return ssl_connection_index; } int swoole_ssl_get_ex_port_index() { return ssl_port_index; } void swoole_ssl_destroy() { if (!openssl_init) { return; } openssl_init = false; } static int ssl_error_cb(const char *str, size_t len, void *buf) { auto s = static_cast(buf); memcpy(s->str, str, len); s->length = len; s->set_null_terminated(); return 0; } const char *swoole_ssl_get_error() { sw_tg_buffer()->clear(); sw_tg_buffer()->set_null_terminated(); ERR_print_errors_cb(ssl_error_cb, sw_tg_buffer()); return sw_tg_buffer()->str; } static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) { Socket *sock; if (where & SSL_CB_HANDSHAKE_START) { sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index); if (sock->ssl_state == SW_SSL_STATE_READY) { sock->ssl_renegotiation = 1; swoole_debug("SSL renegotiation"); } } if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) { sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index); if (!sock->ssl_handshake_buffer_set) { /* * By default, OpenSSL uses 4k buffer during a handshake, * which is too low for long certificate chains and might * result in extra round-trips. * * To adjust a buffer size we detect that buffering was added * to write side of the connection by comparing rbio and wbio. * If they are different, we assume that it's due to buffering * added to wbio, and set buffer size. */ BIO *rbio = SSL_get_rbio(ssl); BIO *wbio = SSL_get_wbio(ssl); if (rbio != wbio) { (void) BIO_set_write_buffer_size(wbio, SW_SSL_BUFFER_SIZE); sock->ssl_handshake_buffer_set = 1; } } } } namespace swoole { #define HTTP2_H2_ALPN "\x02h2" #define HTTP2_H2_16_ALPN "\x05h2-16" #define HTTP2_H2_14_ALPN "\x05h2-14" #define HTTP1_NPN "\x08http/1.1" #define ssl_error(_fmt, ...) \ long _ssl_error = ERR_get_error(); \ swoole_warning(_fmt ", Error: %s[%ld]", ##__VA_ARGS__, ERR_reason_error_string(_ssl_error), _ssl_error); #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation static int ssl_alpn_advertised(SSL *ssl, const uchar **out, uchar *outlen, const uchar *in, uint32_t inlen, void *arg) { unsigned int protos_len; const char *protos; auto *cfg = (SSLContext *) arg; if (cfg->http_v2) { protos = HTTP2_H2_ALPN HTTP1_NPN; protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1; } else { protos = HTTP1_NPN; protos_len = sizeof(HTTP1_NPN) - 1; } if (SSL_select_next_proto((uchar **) out, outlen, (const uchar *) protos, protos_len, in, inlen) != OPENSSL_NPN_NEGOTIATED) { return SSL_TLSEXT_ERR_NOACK; } return SSL_TLSEXT_ERR_OK; } #endif static int ssl_passwd_callback(char *buf, int num, int verify, void *data) { const auto *ctx = static_cast(data); if (!ctx->passphrase.empty()) { const int len = static_cast(ctx->passphrase.length()); if (len < num - 1) { memcpy(buf, ctx->passphrase.c_str(), len); buf[len] = '\0'; return len; } } return 0; } bool SSLContext::create() { if (!openssl_init) { swoole_ssl_init(); } const SSL_METHOD *method; #ifdef SW_SUPPORT_DTLS if (protocols & SW_SSL_DTLS) { method = DTLS_method(); } else #endif { method = SSLv23_method(); } if (protocols == 0) { protocols = SW_SSL_ALL; } context = SSL_CTX_new(method); if (context == nullptr) { ssl_error("SSL_CTX_new() failed"); return false; } #ifdef SSL_OP_MICROSOFT_SESS_ID_BUG SSL_CTX_set_options(context, SSL_OP_MICROSOFT_SESS_ID_BUG); #endif #ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG SSL_CTX_set_options(context, SSL_OP_NETSCAPE_CHALLENGE_BUG); #endif /* server side options */ #ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG SSL_CTX_set_options(context, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG); #endif #ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER SSL_CTX_set_options(context, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER); #endif #ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */ SSL_CTX_set_options(context, SSL_OP_MSIE_SSLV2_RSA_PADDING); #endif #ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG SSL_CTX_set_options(context, SSL_OP_SSLEAY_080_CLIENT_DH_BUG); #endif #ifdef SSL_OP_TLS_D5_BUG SSL_CTX_set_options(context, SSL_OP_TLS_D5_BUG); #endif #ifdef SSL_OP_TLS_BLOCK_PADDING_BUG SSL_CTX_set_options(context, SSL_OP_TLS_BLOCK_PADDING_BUG); #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); #endif #if OPENSSL_VERSION_NUMBER >= 0x009080dfL /* only in 0.9.8m+ */ SSL_CTX_clear_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); #endif #ifdef SSL_OP_NO_SSLv2 if (!(protocols & SW_SSL_SSLv2)) { SSL_CTX_set_options(context, SSL_OP_NO_SSLv2); } #endif #ifdef SSL_OP_NO_SSLv3 if (!(protocols & SW_SSL_SSLv3)) { SSL_CTX_set_options(context, SSL_OP_NO_SSLv3); } #endif #ifdef SSL_OP_NO_TLSv1 if (!(protocols & SW_SSL_TLSv1)) { SSL_CTX_set_options(context, SSL_OP_NO_TLSv1); } #endif #ifdef SSL_OP_NO_TLSv1_1 SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_1); if (!(protocols & SW_SSL_TLSv1_1)) { SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_1); } #endif #ifdef SSL_OP_NO_TLSv1_2 SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_2); if (!(protocols & SW_SSL_TLSv1_2) && !(protocols & SW_SSL_DTLS)) { SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_2); } #endif #ifdef SSL_OP_NO_TLSv1_3 SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_3); if (!(protocols & SW_SSL_TLSv1_3)) { SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_3); } #endif #ifdef SSL_OP_NO_COMPRESSION if (disable_compress) { SSL_CTX_set_options(context, SSL_OP_NO_COMPRESSION); } #endif #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(context, SSL_MODE_RELEASE_BUFFERS); #endif #ifdef SSL_MODE_NO_AUTO_CHAIN SSL_CTX_set_mode(context, SSL_MODE_NO_AUTO_CHAIN); #endif SSL_CTX_set_read_ahead(context, 1); SSL_CTX_set_info_callback(context, swoole_ssl_info_callback); if (!passphrase.empty()) { SSL_CTX_set_default_passwd_cb_userdata(context, this); SSL_CTX_set_default_passwd_cb(context, ssl_passwd_callback); } if (!cert_file.empty()) { /* * set the local certificate from CertFile */ if (SSL_CTX_use_certificate_file(context, cert_file.c_str(), SSL_FILETYPE_PEM) <= 0) { ssl_error("SSL_CTX_use_certificate_file(%s) failed", cert_file.c_str()); return true; } /* * if the crt file have many certificate entry ,means certificate chain * we need call this function */ if (SSL_CTX_use_certificate_chain_file(context, cert_file.c_str()) <= 0) { ssl_error("SSL_CTX_use_certificate_chain_file(%s) failed", cert_file.c_str()); return false; } } if (!key_file.empty()) { /* * set the private key from KeyFile (maybe the same as CertFile) */ if (SSL_CTX_use_PrivateKey_file(context, key_file.c_str(), SSL_FILETYPE_PEM) <= 0) { ssl_error("SSL_CTX_use_PrivateKey_file(%s) failed", key_file.c_str()); return false; } /* * verify private key */ if (!SSL_CTX_check_private_key(context)) { ssl_error("SSL_CTX_check_private_key() failed"); return false; } } #ifdef SW_SUPPORT_DTLS if (protocols & SW_SSL_DTLS) { #ifndef OPENSSL_IS_BORINGSSL SSL_CTX_set_cookie_generate_cb(context, swoole_ssl_generate_cookie); SSL_CTX_set_cookie_verify_cb(context, swoole_ssl_verify_cookie); #endif } #endif auto rs = set_capath(); if (verify_peer) { if (!rs) { return false; } } else { SSL_CTX_set_verify(context, SSL_VERIFY_NONE, nullptr); } if (http || http_v2) { unsigned int protos_len; const char *protos; if (http_v2) { protos = HTTP2_H2_ALPN HTTP1_NPN; protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1; } else { protos = HTTP1_NPN; protos_len = sizeof(HTTP1_NPN) - 1; } if (SSL_CTX_set_alpn_protos(context, (const uchar *) protos, protos_len) < 0) { ssl_error("SSL_CTX_set_alpn_protos(%s) failed", protos); return false; } #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation SSL_CTX_set_alpn_select_cb(context, ssl_alpn_advertised, (void *) this); #endif SSL_CTX_set_session_id_context(context, (const uchar *) "HTTP", sizeof("HTTP") - 1); SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_SERVER); } #ifdef OPENSSL_IS_BORINGSSL SSL_CTX_set_grease_enabled(context, grease); #endif if (!client_cert_file.empty() && !set_client_certificate()) { return false; } if (!set_ciphers()) { swoole_warning("set_cipher() error"); return false; } return true; } bool SSLContext::set_capath() const { if (!cafile.empty() || !capath.empty()) { const char *_cafile = cafile.empty() ? nullptr : cafile.c_str(); const char *_capath = capath.empty() ? nullptr : capath.c_str(); if (!SSL_CTX_load_verify_locations(context, _cafile, _capath)) { return false; } } else { if (!SSL_CTX_set_default_verify_paths(context)) { ssl_error("SSL_CTX_set_default_verify_paths() failed"); return false; } } if (verify_depth > 0) { SSL_CTX_set_verify_depth(context, verify_depth); } return true; } bool SSLContext::set_ciphers() const { #ifndef TLS1_2_VERSION return true; #endif if (!ciphers.empty()) { if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) == 0) { ssl_error("SSL_CTX_set_cipher_list(\"%s\") failed", ciphers.c_str()); return false; } if (prefer_server_ciphers && !SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE)) { ssl_error("SSL_CTX_set_options(SSL_OP_CIPHER_SERVER_PREFERENCE) failed"); return false; } } if (!dhparam.empty() && !set_dhparam()) { return false; } #if OPENSSL_VERSION_NUMBER < 0x10100000L else { swoole_ssl_set_default_dhparam(context); } #endif if (!ecdh_curve.empty() && !set_ecdh_curve()) { return false; } return true; } bool SSLContext::set_client_certificate() const { const char *_cert_file = client_cert_file.c_str(); int depth = verify_depth; SSL_CTX_set_verify(context, SSL_VERIFY_PEER, swoole_ssl_verify_callback); SSL_CTX_set_verify_depth(context, depth); if (SSL_CTX_load_verify_locations(context, _cert_file, nullptr) == 0) { ssl_error("SSL_CTX_load_verify_locations(\"%s\") failed", _cert_file); return false; } ERR_clear_error(); STACK_OF(X509_NAME) *list = SSL_load_client_CA_file(_cert_file); if (list == nullptr) { ssl_error("SSL_load_client_CA_file(\"%s\") failed", _cert_file); return false; } ERR_clear_error(); SSL_CTX_set_client_CA_list(context, list); return true; } bool SSLContext::set_ecdh_curve() const { #ifndef OPENSSL_NO_ECDH /* * Elliptic-Curve Diffie-Hellman parameters are either "named curves" * from RFC 4492 section 5.1.1, or explicitly described curves over * binary fields. OpenSSL only supports the "named curves", which provide * maximum interoperability. */ #if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST) /* * OpenSSL 1.0.2+ allows configuring a curve list instead of a single * curve previously supported. By default, an internal list is used, * with prime256v1 being preferred by server in OpenSSL 1.0.2b+ * and X25519 in OpenSSL 1.1.0+. * * By default, a curve preferred by the client will be used for * key exchange. The SSL_OP_CIPHER_SERVER_PREFERENCE option can * be used to prefer server curves instead, similar to what it * does for ciphers. */ SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE); #if SSL_CTRL_SET_ECDH_AUTO /* not needed in OpenSSL 1.1.0+ */ SSL_CTX_set_ecdh_auto(context, 1); #endif if (strcmp(ecdh_curve.c_str(), "auto") == 0) { return true; } if (SSL_CTX_set1_curves_list(context, ecdh_curve.c_str()) == 0) { swoole_warning("SSL_CTX_set1_curves_list(\"%s\") failed", ecdh_curve.c_str()); return false; } #else EC_KEY *ecdh; /* * Elliptic-Curve Diffie-Hellman parameters are either "named curves" * from RFC 4492 section 5.1.1, or explicitly described curves over * binary fields. OpenSSL only supports the "named curves", which provide * maximum interoperability. */ int nid = OBJ_sn2nid(ecdh_curve.c_str()); if (nid == 0) { swoole_warning("Unknown curve name \"%s\"", ecdh_curve.c_str()); return false; } ecdh = EC_KEY_new_by_curve_name(nid); if (ecdh == nullptr) { swoole_warning("Unable to create curve \"%s\"", ecdh_curve.c_str()); return false; } SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_tmp_ecdh(context, ecdh); EC_KEY_free(ecdh); #endif #endif return true; } bool SSLContext::set_dhparam() const { const char *file = dhparam.c_str(); BIO *bio = BIO_new_file(file, "r"); if (bio == nullptr) { ssl_error("BIO_new_file(%s) failed", file); return false; } #if OPENSSL_VERSION_MAJOR >= 3 EVP_PKEY *pkey = PEM_read_bio_Parameters(bio, nullptr); if (pkey == nullptr) { ssl_error("PEM_read_bio_Parameters('%s') failed", file); BIO_free(bio); return false; } if (SSL_CTX_set0_tmp_dh_pkey(context, pkey) != 1) { ssl_error("SSL_CTX_set0_tmp_dh_pkey('%s') failed", file); EVP_PKEY_free(pkey); BIO_free(bio); return false; } #else DH *dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); if (dh == nullptr) { ssl_error("PEM_read_bio_DHparams(%s) failed", file); BIO_free(bio); return false; } SSL_CTX_set_tmp_dh(context, dh); DH_free(dh); #endif BIO_free(bio); return true; } SSLContext::~SSLContext() { SSL_CTX_free(context); } } // namespace swoole static int swoole_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) { #if 0 char *subject, *issuer; int err, depth; X509 *cert; X509_NAME *sname, *iname; X509_STORE_CTX_get_ex_data(x509_store, SSL_get_ex_data_X509_STORE_CTX_idx()); cert = X509_STORE_CTX_get_current_cert(x509_store); err = X509_STORE_CTX_get_error(x509_store); depth = X509_STORE_CTX_get_error_depth(x509_store); sname = X509_get_subject_name(cert); subject = sname ? X509_NAME_oneline(sname, nullptr, 0) : "(none)"; iname = X509_get_issuer_name(cert); issuer = iname ? X509_NAME_oneline(iname, nullptr, 0) : "(none)"; swoole_warning("verify:%d, error:%d, depth:%d, subject:\"%s\", issuer:\"%s\"", ok, err, depth, subject, issuer); if (sname) { OPENSSL_free(subject); } if (iname) { OPENSSL_free(issuer); } #endif return 1; } #ifdef SW_SUPPORT_DTLS #define COOKIE_SECRET_LENGTH (32) static void calculate_cookie(SSL *ssl, uchar *cookie_secret, uint cookie_length) { long rv = (long) ssl; long inum = (cookie_length - (((long) cookie_secret) % sizeof(long))) / sizeof(long); long i = 0; long *ip = (long *) cookie_secret; for (i = 0; i < inum; ++i, ++ip) { *ip = rv; } } static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) { uchar result[EVP_MAX_MD_SIZE]; uint length = 0, result_len; Address sa{}; uchar cookie_secret[COOKIE_SECRET_LENGTH]; calculate_cookie(ssl, cookie_secret, sizeof(cookie_secret)); /* Read peer information */ (void) BIO_dgram_get_peer(SSL_get_wbio(ssl), &sa); length = 0; switch (sa.addr.ss.sa_family) { case AF_INET: length += sizeof(struct in_addr); break; case AF_INET6: length += sizeof(struct in6_addr); break; default: OPENSSL_assert(0); break; } length += sizeof(in_port_t); auto *buffer = (uchar *) OPENSSL_malloc(length); if (buffer == nullptr) { swoole_sys_warning("out of memory"); return 0; } switch (sa.addr.ss.sa_family) { case AF_INET: memcpy(buffer, &sa.addr.inet_v4.sin_port, sizeof(in_port_t)); memcpy(buffer + sizeof(sa.addr.inet_v4.sin_port), &sa.addr.inet_v4.sin_addr, sizeof(struct in_addr)); break; case AF_INET6: memcpy(buffer, &sa.addr.inet_v6.sin6_port, sizeof(in_port_t)); memcpy(buffer + sizeof(in_port_t), &sa.addr.inet_v6.sin6_addr, sizeof(struct in6_addr)); break; default: OPENSSL_assert(0); break; } HMAC(EVP_sha1(), (const void *) cookie_secret, COOKIE_SECRET_LENGTH, buffer, length, result, &result_len); OPENSSL_free(buffer); memcpy(cookie, result, result_len); *cookie_len = result_len; return 1; } static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len) { uint result_len = 0; uchar result[COOKIE_SECRET_LENGTH]; swoole_ssl_generate_cookie(ssl, result, &result_len); return cookie_len == result_len && memcmp(result, cookie, result_len) == 0; } #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context) { DH *dh; static unsigned char dh1024_p[] = { 0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5, 0x80, 0xE9, 0xCF, 0xDB, 0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B, 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C, 0x6C, 0x03, 0x02, 0x76, 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5, 0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04, 0x9B, 0xE6, 0x77, 0x80, 0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04, 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8, 0xF2, 0x9B, 0xA5, 0xDF, 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50, 0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E, 0x58, 0xFA, 0xFD, 0x49, 0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA, 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B}; static unsigned char dh1024_g[] = {0x02}; dh = DH_new(); if (dh == nullptr) { swoole_warning("DH_new() failed"); return SW_ERR; } dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), nullptr); dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), nullptr); if (dh->p == nullptr || dh->g == nullptr) { DH_free(dh); } SSL_CTX_set_tmp_dh(ssl_context, dh); DH_free(dh); return SW_OK; } #endif ================================================ FILE: src/protocol/websocket.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_websocket.h" using swoole::Connection; using swoole::Protocol; using swoole::Server; using swoole::String; using swoole::network::Socket; namespace swoole { namespace websocket { /* The following is websocket data frame: +-+-+-+-+-------+-+-------------+-------------------------------+ 0 1 2 3 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ */ static ssize_t get_package_length_impl(PacketLength *pl) { // need more data if (pl->buf_size < SW_WEBSOCKET_HEADER_LEN) { return 0; } const char *buf = pl->buf; char mask = (buf[1] >> 7) & 0x1; // 0-125 uint64_t payload_length = buf[1] & 0x7f; pl->header_len = SW_WEBSOCKET_HEADER_LEN; buf += SW_WEBSOCKET_HEADER_LEN; // uint16_t, 2byte if (payload_length == SW_WEBSOCKET_EXT16_LENGTH) { pl->header_len += sizeof(uint16_t); if (pl->buf_size < pl->header_len) { return 0; } payload_length = ntohs(*((uint16_t *) buf)); } // uint64_t, 8byte else if (payload_length == SW_WEBSOCKET_EXT64_LENGTH) { pl->header_len += sizeof(uint64_t); if (pl->buf_size < pl->header_len) { return 0; } payload_length = swoole_ntoh64(*((uint64_t *) buf)); } if (mask) { pl->header_len += SW_WEBSOCKET_MASK_LEN; if (pl->buf_size < pl->header_len) { return 0; } } if ((ssize_t) payload_length < 0) { return -1; } swoole_trace_log( SW_TRACE_LENGTH_PROTOCOL, "header_length=%u, payload_length=%" PRIu64, pl->header_len, payload_length); return (ssize_t) pl->header_len + (ssize_t) payload_length; } ssize_t get_package_length(const Protocol *protocol, Socket *conn, PacketLength *pl) { return get_package_length_impl(pl); } void mask(char *data, size_t len, const char *mask_key) { size_t n = len / 8; uint64_t mask_key64 = ((uint64_t) (*((uint32_t *) mask_key)) << 32) | *((uint32_t *) mask_key); size_t i; for (i = 0; i < n; i++) { ((uint64_t *) data)[i] ^= mask_key64; } for (i = n * 8; i < len; i++) { data[i] ^= mask_key[i % SW_WEBSOCKET_MASK_LEN]; } } bool encode(String *buffer, const char *data, size_t length, uint8_t opcode, uint8_t _flags) { int pos = 0; char frame_header[16]; auto *header = (Header *) frame_header; header->FIN = !!(_flags & FLAG_FIN); header->OPCODE = opcode; header->RSV1 = !!(_flags & FLAG_RSV1); header->RSV2 = 0; header->RSV3 = 0; header->MASK = !!(_flags & FLAG_MASK); pos = 2; if (length < SW_WEBSOCKET_EXT16_LENGTH) { header->LENGTH = length; } else if (length <= SW_WEBSOCKET_EXT16_MAX_LEN) { header->LENGTH = SW_WEBSOCKET_EXT16_LENGTH; auto *length_ptr = (uint16_t *) (frame_header + pos); *length_ptr = htons(length); pos += sizeof(*length_ptr); } else { header->LENGTH = SW_WEBSOCKET_EXT64_LENGTH; auto *length_ptr = (uint64_t *) (frame_header + pos); *length_ptr = swoole_hton64(length); pos += sizeof(*length_ptr); } buffer->append(frame_header, pos); /** * frame body */ if (header->MASK) { buffer->append(SW_WEBSOCKET_MASK_DATA, SW_WEBSOCKET_MASK_LEN); if (_flags & FLAG_ENCODE_HEADER_ONLY) { return false; } if (length > 0) { size_t offset = buffer->length; // Warn: buffer may be extended, string pointer will change buffer->append(data, length); mask(buffer->str + offset, length, SW_WEBSOCKET_MASK_DATA); } } else { if (length > 0 and !(_flags & FLAG_ENCODE_HEADER_ONLY)) { buffer->append(data, length); } } return true; } bool decode(Frame *frame, char *data, size_t length) { frame->header.OPCODE = data[0] & 0xf; frame->header.RSV1 = (data[0] >> 6) & 0x1; frame->header.RSV2 = (data[0] >> 5) & 0x1; frame->header.RSV3 = (data[0] >> 4) & 0x1; frame->header.FIN = (data[0] >> 7) & 0x1; frame->header.MASK = (data[1] >> 7) & 0x1; frame->header.LENGTH = data[1] & 0x7f; PacketLength pl{data, (uint32_t) length, 0}; ssize_t total_length = get_package_length_impl(&pl); if (total_length <= 0 || length < (size_t) total_length) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_WEBSOCKET_INCOMPLETE_PACKET, "incomplete packet, expected length is %zu, actual length is %zu", total_length, length); return false; } frame->payload_length = total_length - pl.header_len; frame->header_length = pl.header_len; swoole_trace_log(SW_TRACE_WEBSOCKET, "decode frame, payload_length=%ld, mask=%d, opcode=%d", frame->payload_length, frame->header.MASK, frame->header.OPCODE); if (frame->payload_length == 0) { frame->payload = nullptr; } else { frame->payload = data + frame->header_length; if (frame->header.MASK) { memcpy(frame->mask_key, frame->payload - SW_WEBSOCKET_MASK_LEN, SW_WEBSOCKET_MASK_LEN); mask(frame->payload, frame->payload_length, frame->mask_key); } } return true; } bool pack_close_frame(String *buffer, int code, const char *reason, size_t length, uint8_t flags) { if (sw_unlikely(length > SW_WEBSOCKET_CLOSE_REASON_MAX_LEN)) { swoole_warning("the max length of close reason is %d", SW_WEBSOCKET_CLOSE_REASON_MAX_LEN); return false; } char payload[SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_CLOSE_CODE_LEN + SW_WEBSOCKET_CLOSE_REASON_MAX_LEN]; payload[0] = (char) ((code >> 8 & 0xFF)); payload[1] = (char) ((code & 0xFF)); if (length > 0) { memcpy(payload + SW_WEBSOCKET_CLOSE_CODE_LEN, reason, length); } flags |= FLAG_FIN; if (!encode(buffer, payload, SW_WEBSOCKET_CLOSE_CODE_LEN + length, OPCODE_CLOSE, flags)) { return false; } return true; } void print_frame(const Frame *frame) { sw_printf("FIN: %x, RSV1: %d, RSV2: %d, RSV3: %d, opcode: %d, MASK: %d, length: %ld\n", frame->header.FIN, frame->header.RSV1, frame->header.RSV2, frame->header.RSV3, frame->header.OPCODE, frame->header.MASK, frame->payload_length); if (frame->payload_length) { sw_printf("payload: %.*s\n", (int) frame->payload_length, frame->payload); } } int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata) { auto *serv = (Server *) proto->private_data_2; auto *conn = (Connection *) _socket->object; RecvData dispatch_data{}; String send_frame{}; const char *data = rdata->data; const uint32_t length = rdata->info.len; char buf[SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_CLOSE_CODE_LEN + SW_WEBSOCKET_CLOSE_REASON_MAX_LEN]; send_frame.str = buf; send_frame.size = sizeof(buf); Frame ws; if (!decode(&ws, const_cast(data), length)) { return SW_ERR; } String *frame_buffer; int frame_length; ListenPort *port; size_t offset; switch (ws.header.OPCODE) { case OPCODE_CONTINUATION: frame_buffer = conn->websocket_buffer; if (frame_buffer == nullptr) { swoole_warning("bad frame[opcode=0]. remote_addr=%s:%d", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } offset = length - ws.payload_length; frame_length = length - offset; port = serv->get_port_by_fd(conn->fd); // frame data overflow if (frame_buffer->length + frame_length > port->protocol.package_max_length) { swoole_warning( "websocket frame is too big, remote_addr=%s:%d", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } // merge incomplete data frame_buffer->append(data + offset, frame_length); // frame is finished, do dispatch if (ws.header.FIN) { dispatch_data.info.ext_flags = conn->websocket_buffer->offset | FLAG_FIN; dispatch_data.info.len = frame_buffer->length; dispatch_data.data = frame_buffer->str; Server::dispatch_task(proto, _socket, &dispatch_data); delete frame_buffer; conn->websocket_buffer = nullptr; } break; case OPCODE_TEXT: case OPCODE_BINARY: { offset = length - ws.payload_length; uint16_t ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); if (!ws.header.FIN) { if (conn->websocket_buffer) { swoole_warning("merging incomplete frame, bad request. remote_addr=%s:%d", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } conn->websocket_buffer = new String(data + offset, length - offset); conn->websocket_buffer->offset = ext_flags; } else { dispatch_data.info.ext_flags = ext_flags; dispatch_data.info.len = length - offset; dispatch_data.data = data + offset; Server::dispatch_task(proto, _socket, &dispatch_data); } break; } case OPCODE_PING: case OPCODE_PONG: if (length >= (sizeof(buf) - SW_WEBSOCKET_HEADER_LEN)) { swoole_warning("%s frame application data is too big. remote_addr=%s:%d", ws.header.OPCODE == OPCODE_PING ? "ping" : "pong", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } else if (length == SW_WEBSOCKET_HEADER_LEN) { dispatch_data.data = nullptr; dispatch_data.info.len = 0; } else { offset = ws.header.MASK ? SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_MASK_LEN : SW_WEBSOCKET_HEADER_LEN; dispatch_data.info.len = length - offset; dispatch_data.data = dispatch_data.info.len == 0 ? nullptr : data + offset; } dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); Server::dispatch_task(proto, _socket, &dispatch_data); break; case OPCODE_CLOSE: if ((length - SW_WEBSOCKET_HEADER_LEN) > SW_WEBSOCKET_CLOSE_REASON_MAX_LEN) { return SW_ERR; } if (conn->websocket_status != STATUS_CLOSING) { // Dispatch the frame with the same format of message frame offset = length - ws.payload_length; dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); dispatch_data.info.len = length - offset; dispatch_data.data = data + offset; Server::dispatch_task(proto, _socket, &dispatch_data); // Client attempt to close send_frame.str[0] = 0x88; // FIN | OPCODE: WEBSOCKET_OPCODE_CLOSE send_frame.str[1] = ws.payload_length; // Get payload and return it as it is memcpy(send_frame.str + SW_WEBSOCKET_HEADER_LEN, data + length - ws.payload_length, ws.payload_length); send_frame.length = SW_WEBSOCKET_HEADER_LEN + ws.payload_length; _socket->send(send_frame.str, send_frame.length, 0); } else { // Server attempt to close, frame sent by swoole_websocket_server->disconnect() conn->websocket_status = 0; } return SW_ERR; default: swoole_warning("unknown opcode [%d]", ws.header.OPCODE); break; } return SW_OK; } } // namespace websocket } // namespace swoole ================================================ FILE: src/reactor/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_signal.h" #include "swoole_reactor.h" #include "swoole_timer.h" #include "swoole_api.h" namespace swoole { using network::Socket; #ifdef SW_USE_MALLOC_TRIM #ifdef __APPLE__ #include #else #include #endif #endif #ifdef HAVE_EPOLL ReactorImpl *make_reactor_epoll(Reactor *_reactor, int max_events); #endif #ifdef HAVE_KQUEUE ReactorImpl *make_reactor_kqueue(Reactor *_reactor, int max_events); #endif ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events); void ReactorImpl::after_removal_failure(const Socket *_socket) const { if (!_socket->silent_remove) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "failed to delete events[fd=%d#%d, type=%d, events=%d, errno=%d]", _socket->fd, reactor_->id, _socket->fd_type, _socket->events, errno); swoole_print_backtrace_on_error(); } } Reactor::Reactor(int max_event, Type _type) { if (_type == TYPE_AUTO) { #ifdef HAVE_EPOLL type_ = TYPE_EPOLL; #else type_ = TYPE_POLL; #ifdef HAVE_KQUEUE /** * When kqueue is enabled, the Process mode of the Server module and functionalities such as Task operations, * pipe messaging, and inter-process message forwarding that rely on pipe communication will be unavailable. */ if (SwooleG.enable_kqueue) { type_ = TYPE_KQUEUE; } #endif #endif } else { type_ = _type; } switch (type_) { #ifdef HAVE_EPOLL case TYPE_EPOLL: impl = make_reactor_epoll(this, max_event); break; #endif #ifdef HAVE_KQUEUE case TYPE_KQUEUE: impl = make_reactor_kqueue(this, max_event); break; #endif default: impl = make_reactor_poll(this, max_event); break; } ready_ = impl->ready(); if (!ready_) { return; } timeout_msec = -1; idle_task = {}; future_task = {}; write = _write; writev = _writev; close = _close; default_write_handler = _writable_callback; if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_REACTOR_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_ON_REACTOR_CREATE, this); } set_end_callback(PRIORITY_DEFER_TASK, [](Reactor *reactor) { CallbackManager *cm = reactor->defer_tasks; if (cm) { reactor->defer_tasks = nullptr; cm->execute(); delete cm; } }); set_exit_condition(EXIT_CONDITION_DEFER_TASK, [](Reactor *reactor, size_t &event_num) -> bool { return reactor->defer_tasks == nullptr; }); set_end_callback(PRIORITY_IDLE_TASK, [](Reactor *reactor) { if (reactor->idle_task.callback) { reactor->idle_task.callback(reactor->idle_task.data); } }); if (swoole_is_main_thread()) { set_end_callback(PRIORITY_SIGNAL_CALLBACK, [](Reactor *) { swoole_signal_dispatch(); }); } set_end_callback(PRIORITY_TRY_EXIT, [](Reactor *reactor) { if (reactor->wait_exit && reactor->if_exit()) { reactor->running = false; } }); #ifdef SW_USE_MALLOC_TRIM if (swoole_is_main_thread()) { set_end_callback(PRIORITY_MALLOC_TRIM, [](Reactor *reactor) { time_t now = ::time(nullptr); if (reactor->last_malloc_trim_time < now - SW_MALLOC_TRIM_INTERVAL) { malloc_trim(SW_MALLOC_TRIM_PAD); reactor->last_malloc_trim_time = now; } }); } #endif set_exit_condition(EXIT_CONDITION_DEFAULT, [](Reactor *, size_t &event_num) -> bool { return event_num == 0; }); } void Reactor::set_handler(const int fd_type, const int event, const ReactorHandler handler) { if (isset_read_event(event)) { read_handler[fd_type] = handler; } else if (isset_write_event(event)) { write_handler[fd_type] = handler; } else if (isset_error_event(event)) { error_handler[fd_type] = handler; } else { assert(0); } } bool Reactor::isset_handler(const int fd_type, const int event) const { if (isset_read_event(event)) { return read_handler[fd_type] != nullptr; } else if (isset_write_event(event)) { return write_handler[fd_type] != nullptr; } else if (isset_error_event(event)) { return error_handler[fd_type] != nullptr; } else { return false; } } bool Reactor::if_exit() { size_t _event_num = get_event_num(); for (auto &kv : exit_conditions) { if (kv.second(this, _event_num) == false) { return false; } } return true; } int Reactor::_close(Reactor *reactor, Socket *socket) { swoole_trace_log(SW_TRACE_CLOSE, "fd=%d", socket->fd); socket->free(); return SW_OK; } ssize_t Reactor::write_func(const Reactor *reactor, Socket *socket, const size_t _len, const std::function &send_fn, const std::function &append_fn) { Buffer *buffer = socket->out_buffer; if (socket->buffer_size == 0) { socket->set_memory_buffer_size(Socket::default_buffer_size); } if (socket->nonblock == 0) { socket->set_fd_option(1, -1); } if ((uint32_t) _len > socket->buffer_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "data packet is too large, cannot exceed the socket buffer size"); return SW_ERR; } if (Buffer::empty(buffer)) { ssize_t retval; if (socket->ssl_send_) { goto _alloc_buffer; } _do_send: retval = send_fn(); if (retval > 0) { if ((ssize_t) _len == retval) { return retval; } else { goto _alloc_buffer; } } else if (socket->catch_write_error(errno) == SW_WAIT) { _alloc_buffer: if (!socket->out_buffer) { buffer = new Buffer(socket->chunk_size); socket->out_buffer = buffer; } if (!socket->isset_writable_event()) { reactor->add_write_event(socket); } /** * Part of the data has been successfully written to the kernel's socket buffer, * and at this point, writing to the memory queue is permitted under any circumstances. * Ensure that the async write operation either succeeds completely or fails entirely. */ goto _append_buffer; } else if (errno == EINTR) { goto _do_send; } else { swoole_set_last_error(errno); return SW_ERR; } } else { if (buffer->length() + _len > socket->buffer_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "socket#%d output buffer overflow: (%u/%u)", socket->get_fd(), buffer->length(), socket->buffer_size); return SW_ERR; } _append_buffer: append_fn(buffer); } return _len; } ssize_t Reactor::_write(Reactor *reactor, Socket *socket, const void *buf, size_t n) { ssize_t send_bytes = 0; auto send_fn = [&send_bytes, socket, buf, n]() -> ssize_t { send_bytes = socket->send(buf, n, 0); return send_bytes; }; auto append_fn = [&send_bytes, buf, n](Buffer *buffer) { ssize_t offset = send_bytes > 0 ? send_bytes : 0; buffer->append(static_cast(buf) + offset, n - offset); }; return write_func(reactor, socket, n, send_fn, append_fn); } ssize_t Reactor::_writev(Reactor *reactor, Socket *socket, const iovec *iov, size_t iovcnt) { if (socket->ssl) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "does not support SSL"); return SW_ERR; } ssize_t send_bytes = 0; size_t n = 0; SW_LOOP_N(iovcnt) { n += iov[i].iov_len; } auto send_fn = [&send_bytes, socket, iov, iovcnt]() -> ssize_t { send_bytes = socket->writev(iov, iovcnt); return send_bytes; }; auto append_fn = [&send_bytes, iov, iovcnt](Buffer *buffer) { ssize_t offset = send_bytes > 0 ? send_bytes : 0; buffer->append(iov, iovcnt, offset); }; return write_func(reactor, socket, n, send_fn, append_fn); } int Reactor::_writable_callback(Reactor *reactor, Event *ev) { int ret; Socket *socket = ev->socket; Buffer *buffer = socket->out_buffer; while (!Buffer::empty(buffer)) { BufferChunk *chunk = buffer->front(); if (chunk->type == BufferChunk::TYPE_CLOSE) { return reactor->close(reactor, ev->socket); } else if (chunk->type == BufferChunk::TYPE_SENDFILE) { ret = socket->handle_sendfile(); } else { ret = socket->handle_send(); } if (ret < 0) { if (socket->close_wait) { return reactor->trigger_close_event(ev); } else if (socket->send_wait) { return SW_OK; } } } if (socket->send_timer) { swoole_timer_del(socket->send_timer); socket->send_timer = nullptr; } // remove EPOLLOUT event if (Buffer::empty(buffer)) { reactor->remove_write_event(ev->socket); } return SW_OK; } void Reactor::drain_write_buffer(Socket *socket) { Event event = {}; event.socket = socket; event.fd = socket->fd; while (!Buffer::empty(socket->out_buffer)) { if (socket->wait_event(static_cast(sec2msec(Socket::default_write_timeout)), SW_EVENT_WRITE) == SW_ERR) { break; } _writable_callback(this, &event); if (socket->close_wait || socket->removed) { break; } } } void Reactor::add_destroy_callback(const Callback &cb, void *data) { destroy_callbacks.append(cb, data); } void Reactor::set_end_callback(const EndCallback _id, const std::function &fn) { end_callbacks[_id] = fn; } void Reactor::erase_end_callback(const EndCallback _id) { end_callbacks.erase(_id); } /** * Returns false, the reactor cannot be exited, the next condition is skipped * Returns true, the reactor can exit and will continue to execute the next conditional function */ void Reactor::set_exit_condition(const ExitCondition _id, const std::function &fn) { exit_conditions[_id] = fn; } void Reactor::defer(const Callback &cb, void *data) { if (defer_tasks == nullptr) { defer_tasks = new CallbackManager; } defer_tasks->append(cb, data); } void Reactor::execute_end_callbacks(bool _timed_out) { timed_out = _timed_out; for (auto &kv : end_callbacks) { kv.second(this); } } void Reactor::execute_begin_callback() const { if (future_task.callback) { future_task.callback(future_task.data); } } Reactor::~Reactor() { destroyed = true; destroy_callbacks.execute(); delete impl; if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_REACTOR_DESTROY)) { swoole_call_hook(SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, this); } } } // namespace swoole ================================================ FILE: src/reactor/epoll.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_reactor.h" #ifdef HAVE_EPOLL #include #ifndef EPOLLRDHUP #error "require linux kernel version 2.6.32 or later" #endif #ifndef EPOLLONESHOT #error "require linux kernel version 2.6.32 or later" #endif namespace swoole { using network::Socket; class ReactorEpoll final : public ReactorImpl { int epfd_; epoll_event *events_ = nullptr; public: ReactorEpoll(Reactor *_reactor, int max_events); ~ReactorEpoll() override; bool ready() override; int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; int del(Socket *_socket) override; int wait() override; static int get_events(const int fd_type) { int events = 0; if (Reactor::isset_read_event(fd_type)) { events |= EPOLLIN; } if (Reactor::isset_write_event(fd_type)) { events |= EPOLLOUT; } if (fd_type & SW_EVENT_ONCE) { events |= EPOLLONESHOT; } if (Reactor::isset_error_event(fd_type)) { events |= (EPOLLRDHUP | EPOLLHUP | EPOLLERR); } return events; } }; ReactorImpl *make_reactor_epoll(Reactor *_reactor, int max_events) { return new ReactorEpoll(_reactor, max_events); } ReactorEpoll::ReactorEpoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) { epfd_ = epoll_create(512); if (!ReactorEpoll::ready()) { swoole_sys_warning("epoll_create() failed"); return; } events_ = new epoll_event[max_events]; reactor_->max_event_num = max_events; reactor_->native_handle = epfd_; } bool ReactorEpoll::ready() { return epfd_ >= 0; } ReactorEpoll::~ReactorEpoll() { if (epfd_ >= 0) { close(epfd_); } delete[] events_; } int ReactorEpoll::add(Socket *socket, int events) { epoll_event e; e.events = get_events(events); e.data.ptr = socket; if (epoll_ctl(epfd_, EPOLL_CTL_ADD, socket->fd, &e) < 0) { swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_ADD, fd=%d, fd_type=%d, events=%d) failed", reactor_->id, epfd_, socket->fd, socket->fd_type, events); swoole_print_backtrace_on_error(); return SW_ERR; } reactor_->_add(socket, events); swoole_trace_log( SW_TRACE_EVENT, "add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events); return SW_OK; } int ReactorEpoll::del(Socket *_socket) { if (_socket->removed) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], this socket has already been removed", reactor_->id, _socket->fd, _socket->fd_type); swoole_print_backtrace_on_error(); return SW_ERR; } if (epoll_ctl(epfd_, EPOLL_CTL_DEL, _socket->fd, nullptr) < 0) { after_removal_failure(_socket); /** * Before removing it from the epoll event loop, the close operation has be executed, * must cleanup related resources with this socket. */ if (errno != EBADF && errno != ENOENT) { swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_DEL, fd=%d, fd_type=%d) failed", reactor_->id, epfd_, _socket->fd, _socket->fd_type); swoole_print_backtrace_on_error(); return SW_ERR; } } swoole_trace_log( SW_TRACE_REACTOR, "remove event[reactor_id=%d|fd=%d|type=%d]", reactor_->id, _socket->fd, _socket->fd_type); reactor_->_del(_socket); return SW_OK; } int ReactorEpoll::set(Socket *socket, int events) { epoll_event e; e.events = get_events(events); e.data.ptr = socket; int ret = epoll_ctl(epfd_, EPOLL_CTL_MOD, socket->fd, &e); if (ret < 0) { swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_MOD, fd=%d, fd_type=%d, events=%d) failed", reactor_->id, epfd_, socket->fd, socket->fd_type, events); swoole_print_backtrace_on_error(); return SW_ERR; } swoole_trace_log(SW_TRACE_EVENT, "set event[reactor_id=%d, fd=%d, events=%d]", reactor_->id, socket->fd, events); reactor_->_set(socket, events); return SW_OK; } int ReactorEpoll::wait() { Event event; ReactorHandler handler; int n, ret; reactor_->before_wait(); while (reactor_->running) { reactor_->execute_begin_callback(); n = epoll_wait(epfd_, events_, reactor_->max_event_num, reactor_->get_timeout_msec()); if (n < 0) { if (!reactor_->catch_error()) { swoole_sys_warning("[Reactor#%d] epoll_wait(epfd=%d, max_events=%d, timeout=%d) failed", reactor_->id, epfd_, reactor_->max_event_num, reactor_->get_timeout_msec()); return SW_ERR; } else { #ifdef SW_USE_IOURING if (sw_likely(errno == EINTR && reactor_->iouring_interrupt_handler)) { reactor_->iouring_interrupt_handler(reactor_); } #endif goto _continue; } } else if (n == 0) { reactor_->execute_end_callbacks(true); SW_REACTOR_CONTINUE; } for (int i = 0; i < n; i++) { event.reactor_id = reactor_->id; event.socket = static_cast(events_[i].data.ptr); event.type = event.socket->fd_type; event.fd = event.socket->fd; if (events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) { event.socket->event_hup = 1; } // read if ((events_[i].events & EPOLLIN) && !event.socket->removed) { handler = reactor_->get_handler(event.type, SW_EVENT_READ); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("EPOLLIN handle failed [fd=%d, type=%d]", event.fd, event.type); } } // write if ((events_[i].events & EPOLLOUT) && !event.socket->removed) { handler = reactor_->get_handler(event.type, SW_EVENT_WRITE); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("EPOLLOUT handle failed [fd=%d, type=%d]", event.fd, event.type); } } // error if ((events_[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) && !event.socket->removed) { // ignore ERR and HUP, because event is already processed at IN and OUT handler. if ((events_[i].events & EPOLLIN) || (events_[i].events & EPOLLOUT)) { continue; } handler = reactor_->get_error_handler(event.type); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("EPOLLERR handle failed [fd=%d, type=%d]", event.fd, event.type); } } if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) { reactor_->_del(event.socket); } } _continue: reactor_->execute_end_callbacks(false); SW_REACTOR_CONTINUE; } return 0; } } // namespace swoole #endif ================================================ FILE: src/reactor/kqueue.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_signal.h" #ifdef HAVE_KQUEUE #ifdef USE_KQUEUE_IDE_HELPER #include "helper/kqueue.h" #else #include #endif #ifdef __NetBSD__ #include #endif namespace swoole { using network::Socket; class ReactorKqueue : public ReactorImpl { int epfd_; int event_max_; struct kevent *events_; bool fetch_event(Event *event, void *udata) { event->socket = (Socket *) udata; event->fd = event->socket->fd; event->type = event->socket->fd_type; event->reactor_id = reactor_->id; if (event->socket->removed) { return false; } return true; } void del_once_socket(Socket *socket) { if ((socket->events & SW_EVENT_ONCE) && !socket->removed) { del(socket); } } public: ReactorKqueue(Reactor *reactor, int max_events); ~ReactorKqueue(); bool ready() override; int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; int del(Socket *socket) override; int wait() override; }; ReactorImpl *make_reactor_kqueue(Reactor *_reactor, int max_events) { return new ReactorKqueue(_reactor, max_events); } ReactorKqueue::ReactorKqueue(Reactor *reactor, int max_events) : ReactorImpl(reactor) { epfd_ = kqueue(); if (epfd_ < 0) { swoole_sys_warning("kqueue() failed"); return; } reactor_->max_event_num = max_events; reactor_->native_handle = epfd_; event_max_ = max_events; events_ = new struct kevent[max_events]; } bool ReactorKqueue::ready() { return epfd_ >= 0; } ReactorKqueue::~ReactorKqueue() { if (epfd_ >= 0) { close(epfd_); } delete[] events_; } int ReactorKqueue::add(Socket *socket, int events) { struct kevent e; int ret; int fd = socket->fd; int fflags = 0; #if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); #endif if (Reactor::isset_read_event(events)) { #ifdef NOTE_EOF fflags = NOTE_EOF; #endif EV_SET(&e, fd, EVFILT_READ, EV_ADD, fflags, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_ADD_FAILED, "add events[fd=%d, reactor_id=%d, type=%d, events=read] failed", fd, reactor_->id, socket->fd_type); swoole_print_backtrace_on_error(); return SW_ERR; } } if (Reactor::isset_write_event(events)) { EV_SET(&e, fd, EVFILT_WRITE, EV_ADD, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_ADD_FAILED, "add events[fd=%d, reactor_id=%d, type=%d, events=write] failed", fd, reactor_->id, socket->fd_type); swoole_print_backtrace_on_error(); return SW_ERR; } } reactor_->_add(socket, events); swoole_trace_log(SW_TRACE_EVENT, "[THREAD #%d]epfd=%d, fd=%d, events=%d", SwooleTG.id, epfd_, fd, socket->events); return SW_OK; } int ReactorKqueue::set(Socket *socket, int events) { struct kevent e; int ret; int fd = socket->fd; int fflags = 0; #if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); #endif if (Reactor::isset_read_event(events)) { #ifdef NOTE_EOF fflags = NOTE_EOF; #endif EV_SET(&e, fd, EVFILT_READ, EV_ADD, fflags, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_UPDATE_FAILED, "kqueue->set(%d, SW_EVENT_READ) failed", fd); swoole_print_backtrace_on_error(); return SW_ERR; } } else { EV_SET(&e, fd, EVFILT_READ, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "kqueue->del(%d, SW_EVENT_READ) failed", fd); swoole_print_backtrace_on_error(); return SW_ERR; } } if (Reactor::isset_write_event(events)) { EV_SET(&e, fd, EVFILT_WRITE, EV_ADD, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_UPDATE_FAILED, "kqueue->set(%d, SW_EVENT_WRITE) failed", fd); swoole_print_backtrace_on_error(); return SW_ERR; } } else { EV_SET(&e, fd, EVFILT_WRITE, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "kqueue->del(%d, SW_EVENT_WRITE) failed", fd); swoole_print_backtrace_on_error(); return SW_ERR; } } reactor_->_set(socket, events); swoole_trace_log(SW_TRACE_EVENT, "[THREAD #%d]epfd=%d, fd=%d, events=%d", SwooleTG.id, epfd_, fd, socket->events); return SW_OK; } int ReactorKqueue::del(Socket *socket) { struct kevent e; int ret; int fd = socket->fd; #if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); #endif if (socket->removed) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "failed to delete event[%d], has been removed", socket->fd); swoole_print_backtrace_on_error(); return SW_ERR; } if (socket->events & SW_EVENT_READ) { EV_SET(&e, fd, EVFILT_READ, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { after_removal_failure(socket); if (errno != EBADF && errno != ENOENT) { return SW_ERR; } } } if (socket->events & SW_EVENT_WRITE) { EV_SET(&e, fd, EVFILT_WRITE, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { after_removal_failure(socket); if (errno != EBADF && errno != ENOENT) { return SW_ERR; } } } reactor_->_del(socket); swoole_trace_log(SW_TRACE_EVENT, "[THREAD #%d]epfd=%d, fd=%d", SwooleTG.id, epfd_, fd); return SW_OK; } int ReactorKqueue::wait() { Event event; ReactorHandler handler; int i, n; struct timespec t = {}; struct timespec *t_ptr; reactor_->before_wait(); while (reactor_->running) { reactor_->execute_begin_callback(); if (reactor_->timeout_msec > 0) { t.tv_sec = reactor_->timeout_msec / 1000; t.tv_nsec = (reactor_->timeout_msec - t.tv_sec * 1000) * 1000 * 1000; t_ptr = &t; } else if (reactor_->defer_tasks) { t.tv_sec = 0; t.tv_nsec = 0; t_ptr = &t; } else { t_ptr = nullptr; } n = ::kevent(epfd_, nullptr, 0, events_, event_max_, t_ptr); if (n < 0) { if (!reactor_->catch_error()) { swoole_sys_warning("kevent(epfd=%d) failed ", epfd_); return SW_ERR; } else { goto _continue; } } else if (n == 0) { reactor_->execute_end_callbacks(true); SW_REACTOR_CONTINUE; } swoole_trace_log(SW_TRACE_EVENT, "n %d events", n); for (i = 0; i < n; i++) { struct kevent *kevent = &events_[i]; void *udata = (void *) kevent->udata; if (!udata) { continue; } switch (kevent->filter) { case EVFILT_READ: case EVFILT_WRITE: { if (fetch_event(&event, udata)) { handler = reactor_->get_handler(event.type, kevent->filter == EVFILT_READ ? SW_EVENT_READ : SW_EVENT_WRITE); if (sw_unlikely(handler(reactor_, &event) < 0)) { swoole_sys_warning("kqueue event %s socket#%d handler failed", kevent->filter == EVFILT_READ ? "read" : "write", event.fd); swoole_print_backtrace_on_error(); } del_once_socket(event.socket); } break; } case EVFILT_SIGNAL: { Signal *signal_data = (Signal *) udata; if (signal_data->activated) { if (signal_data->handler) { if (sw_likely(signal_data->handler != SIG_IGN)) { signal_data->handler(signal_data->signo); } } else { swoole_error_log(SW_LOG_WARNING, SW_ERROR_UNREGISTERED_SIGNAL, SW_UNREGISTERED_SIGNAL_FMT, swoole_signal_to_str(signal_data->signo)); } } break; } default: swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_UNKNOWN_DATA, "unknown event filter[%d]", kevent->filter); break; } } _continue: reactor_->execute_end_callbacks(false); SW_REACTOR_CONTINUE; } return 0; } } // namespace swoole #endif ================================================ FILE: src/reactor/poll.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_socket.h" #include "swoole_reactor.h" namespace swoole { using network::Socket; class ReactorPoll final : public ReactorImpl { pollfd *events_; int max_events_; int set_events() const; public: ReactorPoll(Reactor *_reactor, int max_events); ~ReactorPoll() override; bool ready() override { return true; }; int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; int del(Socket *socket) override; int wait() override; }; ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events) { return new ReactorPoll(_reactor, max_events); } ReactorPoll::ReactorPoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) { events_ = new pollfd[max_events]; max_events_ = max_events; reactor_->max_event_num = max_events; } ReactorPoll::~ReactorPoll() { delete[] events_; } int ReactorPoll::set_events() const { const auto sockets = reactor_->get_sockets(); int count = 0; for (const auto pair : sockets) { const auto _socket = pair.second; events_[count].fd = _socket->fd; events_[count].events = translate_events_to_poll(_socket->events); events_[count].revents = 0; count++; } return count; } int ReactorPoll::add(Socket *socket, const int events) { if (reactor_->_exists(socket)) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_ADD_FAILED, "[Reactor#%d] failed to add events[fd=%d, fd_type=%d, events=%d], the socket#%d is already exists", reactor_->id, socket->fd, socket->fd_type, events, socket->fd); swoole_print_backtrace_on_error(); return SW_ERR; } if (reactor_->get_event_num() == static_cast(max_events_)) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_EVENT_ADD_FAILED, "too many sockets, the max events is %d", max_events_); swoole_print_backtrace_on_error(); return SW_ERR; } swoole_trace("fd=%d, events=%d", socket->fd, events); reactor_->_add(socket, events); return SW_OK; } int ReactorPoll::set(Socket *socket, int events) { if (!reactor_->_exists(socket)) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SOCKET_NOT_EXISTS, "[Reactor#%d] failed to set events[fd=%d, fd_type=%d, events=%d], the socket#%d has already been removed", reactor_->id, socket->fd, socket->fd_type, events, socket->fd); swoole_print_backtrace_on_error(); return SW_ERR; } swoole_trace("fd=%d, events=%d", socket->fd, events); reactor_->_set(socket, events); return SW_OK; } int ReactorPoll::del(Socket *socket) { if (socket->removed) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SOCKET_NOT_EXISTS, "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], the socket#%d has already been removed", reactor_->id, socket->fd, socket->fd_type, socket->fd); swoole_print_backtrace_on_error(); return SW_ERR; } if (!reactor_->_exists(socket)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SOCKET_NOT_EXISTS, "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], the socket#%d is not exists", reactor_->id, socket->fd, socket->fd_type, socket->fd); swoole_print_backtrace_on_error(); return SW_ERR; } swoole_trace("fd=%d", socket->fd); reactor_->_del(socket); return SW_OK; } int ReactorPoll::wait() { Event event; ReactorHandler handler; reactor_->before_wait(); while (reactor_->running) { reactor_->execute_begin_callback(); const int event_num = set_events(); int ret = poll(events_, event_num, reactor_->get_timeout_msec()); if (ret < 0) { if (!reactor_->catch_error()) { swoole_sys_warning("[Reactor#%d] poll(nfds=%d, timeout=%d) failed", reactor_->id, event_num, reactor_->get_timeout_msec()); break; } } else if (ret == 0) { reactor_->execute_end_callbacks(true); SW_REACTOR_CONTINUE; } else { for (int i = 0; i < event_num; i++) { /** * A revents value of 0 indicates that no events have occurred on this socket, * so the next file descriptor should be checked; * likewise, if the file descriptor has already been removed, it should be skipped. * It is essential to check whether this file descriptor exists in the reactor, * as the event handler may remove a file descriptor with pending but already triggered readable or * writable events that have not yet been processed. */ if (events_[i].revents == 0 || !reactor_->exists(events_[i].fd)) { continue; } event.socket = reactor_->get_socket(events_[i].fd); event.fd = events_[i].fd; event.reactor_id = reactor_->id; event.type = event.socket->fd_type; if (events_[i].revents & (POLLHUP | POLLERR)) { event.socket->event_hup = 1; } swoole_trace("Event: fd=%d|reactor_id=%d|type=%d", event.fd, reactor_->id, event.type); // in if ((events_[i].revents & POLLIN) && !event.socket->removed) { handler = reactor_->get_handler(event.type, SW_EVENT_READ); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("POLLIN handle failed. fd=%d", event.fd); swoole_print_backtrace_on_error(); } } // out if ((events_[i].revents & POLLOUT) && !event.socket->removed) { handler = reactor_->get_handler(event.type, SW_EVENT_WRITE); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("POLLOUT handle failed. fd=%d", event.fd); swoole_print_backtrace_on_error(); } } // error if ((events_[i].revents & (POLLHUP | POLLERR)) && !event.socket->removed) { // ignore ERR and HUP, because event is already processed at IN and OUT handler. if ((events_[i].revents & POLLIN) || (events_[i].revents & POLLOUT)) { continue; } handler = reactor_->get_error_handler(event.type); ret = handler(reactor_, &event); if (ret < 0) { swoole_sys_warning("POLLERR handle failed. fd=%d", event.fd); swoole_print_backtrace_on_error(); } } if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) { del(event.socket); } } } reactor_->execute_end_callbacks(false); SW_REACTOR_CONTINUE; } return SW_OK; } int16_t translate_events_to_poll(int events) { int16_t poll_events = 0; if (events & SW_EVENT_READ) { poll_events |= POLLIN; } if (events & SW_EVENT_WRITE) { poll_events |= POLLOUT; } return poll_events; } int translate_events_from_poll(int16_t events) { int sw_events = 0; if (events & POLLIN) { sw_events |= SW_EVENT_READ; } if (events & POLLOUT) { sw_events |= SW_EVENT_WRITE; } // ignore ERR and HUP, because event is already processed at IN and OUT handler. if ((events & POLLERR || events & POLLHUP) && !(events & POLLIN || events & POLLOUT)) { sw_events |= SW_EVENT_ERROR; } return sw_events; } } // namespace swoole ================================================ FILE: src/server/base.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" namespace swoole { Factory *Server::create_base_factory() { reactor_num = worker_num; connection_list = static_cast(sw_calloc(max_connection, sizeof(Connection))); if (connection_list == nullptr) { swoole_sys_warning("calloc[2](%zu) failed", max_connection * sizeof(Connection)); return nullptr; } gs->connection_nums = static_cast(sw_shm_calloc(worker_num, sizeof(sw_atomic_t))); if (gs->connection_nums == nullptr) { swoole_sys_warning("sw_shm_calloc(%ld) for gs->connection_nums failed", worker_num * sizeof(sw_atomic_t)); return nullptr; } for (const auto port : ports) { port->gs->connection_nums = static_cast(sw_shm_calloc(worker_num, sizeof(sw_atomic_t))); if (port->gs->connection_nums == nullptr) { swoole_sys_warning("sw_shm_calloc(%ld) for port->connection_nums failed", worker_num * sizeof(sw_atomic_t)); return nullptr; } } return new BaseFactory(this); } void Server::destroy_base_factory() const { sw_free(connection_list); sw_shm_free((void *) gs->connection_nums); for (auto port : ports) { sw_shm_free((void *) port->gs->connection_nums); } gs->connection_nums = nullptr; } BaseFactory::BaseFactory(Server *server) : Factory(server) {} BaseFactory::~BaseFactory() = default; bool BaseFactory::start() { return true; } bool BaseFactory::shutdown() { return true; } bool BaseFactory::dispatch(SendData *task) { Connection *conn = nullptr; if (Server::is_stream_event(task->info.type)) { conn = server_->get_connection(task->info.fd); if (conn == nullptr || conn->active == 0) { swoole_warning("dispatch[type=%d] failed, socket#%ld is not active", task->info.type, task->info.fd); return false; } // server active close, discard data. if (conn->closed) { swoole_warning("dispatch[type=%d] failed, socket#%ld is closed by server", task->info.type, task->info.fd); return false; } // converted fd to session_id task->info.fd = conn->session_id; task->info.server_fd = conn->server_fd; } if (task->info.len > 0) { if (conn && conn->socket->recv_buffer && task->data == conn->socket->recv_buffer->str && conn->socket->recv_buffer->offset > 0 && conn->socket->recv_buffer->length == (size_t) conn->socket->recv_buffer->offset) { task->info.flags |= SW_EVENT_DATA_POP_PTR; } } auto bus = server_->get_worker_message_bus(); bus->pass(task); server_->worker_accept_event(&bus->get_buffer()->info); return true; } /** * only stream fd */ bool BaseFactory::notify(DataHead *info) { const auto conn = server_->get_connection(info->fd); if (conn == nullptr || conn->active == 0) { swoole_warning("dispatch[type=%d] failed, socket#%ld is not active", info->type, info->fd); return false; } // server active close, discard data. if (conn->closed) { swoole_warning("dispatch[type=%d] failed, session#%ld is closed by server", info->type, conn->session_id); return false; } // converted fd to session_id info->fd = conn->session_id; info->server_fd = conn->server_fd; info->flags = SW_EVENT_DATA_NORMAL; server_->worker_accept_event(info); return true; } bool BaseFactory::end(SessionId session_id, int flags) { SendData _send{}; _send.info.fd = session_id; _send.info.len = 0; _send.info.type = SW_SERVER_EVENT_CLOSE; _send.info.reactor_id = swoole_get_worker_id(); Session *session = server_->get_session(session_id); if (!session->fd) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "failed to close connection, session#%ld does not exist", session_id); return false; } if (server_->if_forward_message(session)) { swoole_trace_log(SW_TRACE_SERVER, "session_id=%ld, fd=%d, session->reactor_id=%d", session_id, session->fd, session->reactor_id); _send.info.type = SW_SERVER_EVENT_CLOSE_FORWARD; return forward_message(session, &_send); } Connection *conn = server_->get_connection_verify_no_ssl(session_id); if (conn == nullptr) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); return false; } // Reset send buffer, Immediately close the connection. if (flags & Server::CLOSE_RESET) { conn->close_reset = 1; } // Server is initiative to close the connection if (flags & Server::CLOSE_ACTIVELY) { conn->close_actively = 1; } if (conn->closing) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSING, "session#%ld is closing", session_id); return false; } else if (!(conn->close_force || conn->close_reset) && conn->closed) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_CLOSED, "session#%ld is closed", session_id); return false; } conn->closing = 1; if (server_->onClose != nullptr && !conn->closed) { DataHead info{}; info.fd = session_id; if (conn->close_actively) { info.reactor_id = -1; } else { info.reactor_id = conn->reactor_id; } info.server_fd = conn->server_fd; server_->onClose(server_, &info); } conn->closing = 0; conn->closed = 1; conn->close_errno = 0; network::Socket *_socket = conn->socket; if (_socket == nullptr) { swoole_warning("session#%ld->socket is nullptr", session_id); return false; } if (Buffer::empty(_socket->out_buffer) || (conn->close_reset || conn->peer_closed || conn->close_force)) { Reactor *reactor = SwooleTG.reactor; return Server::close_connection(reactor, _socket) == SW_OK; } else { _socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); conn->close_queued = 1; return true; } } bool BaseFactory::finish(SendData *data) { SessionId session_id = data->info.fd; Session *session = server_->get_session(session_id); if (server_->if_forward_message(session)) { swoole_trace_log(SW_TRACE_SERVER, "session_id=%ld, fd=%d, session->reactor_id=%d", session_id, session->fd, session->reactor_id); if (data->info.type == SW_SERVER_EVENT_SEND_DATA || data->info.type == SW_SERVER_EVENT_SEND_FILE) { return forward_message(session, data); } else { swoole_warning("unknown event type[%d]", data->info.type); return false; } } else { return server_->send_to_connection(data) == SW_OK; } } bool BaseFactory::forward_message(const Session *session, SendData *data) const { const Worker *worker = server_->get_event_worker_pool()->get_worker(session->reactor_id); swoole_trace_log(SW_TRACE_SERVER, "fd=%d, worker_id=%d, type=%d, len=%u", worker->pipe_master->get_fd(), session->reactor_id, data->info.type, data->info.len); auto mb = server_->get_worker_message_bus(); auto sock = server_->is_thread_mode() ? mb->get_pipe_socket(worker->pipe_master) : worker->pipe_master; if (!mb->write(sock, data)) { swoole_sys_warning("failed to send %u bytes to pipe_master", data->info.len); return false; } return true; } } // namespace swoole ================================================ FILE: src/server/manager.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_util.h" #include #include namespace swoole { /** * The functionality of the Manager class is similar to that of the ProcessPool, * but due to the more complex management requirements for processes in the Server module, the ProcessPool falls short. * Therefore, we add a new class, and the Manager class should reuse the ProcessPool code as much as possible. */ struct Manager { bool reload_all_worker; bool reload_task_worker; bool force_kill; Server *server_; std::vector kill_workers; void wait(Server *_server); void terminate_all_worker(); void reopen_logger() const; static void signal_handler(int sig); static void timer_callback(Timer *timer, TimerNode *tnode); }; void Manager::timer_callback(Timer *timer, TimerNode *tnode) { auto *serv = static_cast(tnode->data); if (serv->isset_hook(Server::HOOK_MANAGER_TIMER)) { serv->call_hook(Server::HOOK_MANAGER_TIMER, serv); } } int Server::start_manager_process() { if (!create_event_workers()) { return SW_ERR; } if (get_event_worker_pool()->create_message_box(SW_MESSAGE_BOX_SIZE) == SW_ERR) { return SW_ERR; } if (get_user_worker_num() > 0 && !create_user_workers()) { return SW_ERR; } auto fn = [this]() { gs->manager_pid = getpid(); if (task_worker_num > 0) { if (get_task_worker_pool()->start() == SW_ERR) { swoole_sys_error("failed to start task worker"); return; } } /* * Must be set after ProcessPool:start(), * the default ProcessPool will set type of the main process as SW_MASTER, * while in server mode it should be SW_MANAGER */ swoole_set_worker_type(SW_MANAGER); SW_LOOP_N(worker_num) { Worker *worker = get_worker(i); if (factory_->spawn_event_worker(worker) < 0) { swoole_sys_error("failed to fork event worker"); return; } } if (!user_worker_list.empty()) { for (auto worker : user_worker_list) { if (factory_->spawn_user_worker(worker) < 0) { swoole_sys_error("failed to fork user worker"); return; } } } Manager manager{}; manager.wait(this); }; if (is_base_mode()) { fn(); } else { if (swoole_fork_exec(fn) < 0) { swoole_sys_warning("failed fork manager process"); return SW_ERR; } } return SW_OK; } void Manager::wait(Server *_server) { server_ = _server; server_->manager_ = this; ProcessPool *pool = server_->get_event_worker_pool(); ProcessPool *task_pool = server_->get_task_worker_pool(); pool->onWorkerMessage = Server::read_worker_message; pool->max_wait_time = server_->max_wait_time; _server->gs->manager_pid = pool->master_pid = task_pool->master_pid = getpid(); swoole_event_free(); swoole_signal_set(SIGHUP, nullptr); swoole_signal_set(SIGCHLD, signal_handler); // child process exit swoole_signal_set(SIGTERM, signal_handler); // shutdown swoole_signal_set(SIGUSR1, signal_handler); // reload all workers swoole_signal_set(SIGUSR2, signal_handler); // reload task workers swoole_signal_set(SIGIO, signal_handler); // read message swoole_signal_set(SIGALRM, signal_handler); // timer swoole_signal_set(SIGWINCH, signal_handler); // reopen log file #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, signal_handler); #endif if (_server->is_process_mode()) { swoole_set_process_death_signal(SIGTERM); _server->gs->manager_barrier.wait(); if (_server->reload_async && swoole_is_root_user() && !_server->user_.empty()) { swoole_set_isolation(_server->group_, _server->user_, _server->chroot_); } } if (_server->isset_hook(Server::HOOK_MANAGER_START)) { _server->call_hook(Server::HOOK_MANAGER_START, _server); } if (_server->onManagerStart) { _server->onManagerStart(_server); } if (_server->manager_alarm > 0) { swoole_timer_add(sec2msec(_server->manager_alarm), true, timer_callback, _server); } while (_server->running) { ExitStatus exit_status = wait_process(); const auto wait_error = errno; swoole_signal_dispatch(); if (pool->read_message) { EventData msg; while (pool->pop_message(&msg, sizeof(msg)) > 0) { if (!_server->running) { continue; } if (msg.info.type != SW_WORKER_MESSAGE_STOP && pool->onWorkerMessage) { pool->onWorkerMessage(pool, &msg); continue; } WorkerStopMessage worker_stop_msg; memcpy(&worker_stop_msg, msg.data, sizeof(worker_stop_msg)); if (worker_stop_msg.worker_id >= _server->worker_num) { _server->factory_->spawn_task_worker(_server->get_worker(worker_stop_msg.worker_id)); } else { Worker *worker = _server->get_worker(worker_stop_msg.worker_id); _server->factory_->spawn_event_worker(worker); } } pool->read_message = false; } if (sw_timer()) { sw_timer()->select(); } if (exit_status.get_pid() < 0) { if (!pool->reload_task) { if (wait_error > 0 && wait_error != EINTR) { swoole_sys_warning("wait() failed"); } continue; } } if (_server->running) { if (reload_all_worker) { // reload task & event workers pool->reload_init = reload_all_worker = false; swoole_info("Server is reloading all workers now"); if (_server->onBeforeReload != nullptr) { _server->onBeforeReload(_server); } auto reload_task = pool->reload_task; reload_task->add_workers(_server->workers, _server->worker_num); if (_server->task_worker_num > 0) { reload_task->add_workers(_server->get_task_worker_pool()->workers, _server->task_worker_num); } /** * When the `reload_async` option is enabled, the SIGTERM signal will be sent to all work processes * immediately. When the work process receives the signal, it will remove the event listening of the * work process pipeline, no longer receive data, and then send a process exit message to the management * process. When the management process receives the message, it will immediately create a new worker * process. At this time, there will be two processes at the same time. The old process will exit by * itself after completing the legacy tasks, and the new client request data and tasks will be processed * by the new worker process. */ if (_server->reload_async) { reload_task->kill_all(SIGTERM); } else { goto _kill_worker; } } else if (reload_task_worker) { // only reload task workers pool->reload_init = reload_task_worker = false; if (_server->task_worker_num == 0) { swoole_warning("cannot reload task workers, task workers is not started"); continue; } swoole_info("Server is reloading task workers now"); if (_server->onBeforeReload != nullptr) { _server->onBeforeReload(_server); } auto reload_task = pool->reload_task; reload_task->add_workers(_server->get_task_worker_pool()->workers, _server->task_worker_num); goto _kill_worker; } else if (exit_status.get_pid() < 0) { continue; } // event workers SW_LOOP_N(_server->worker_num) { Worker *worker = _server->get_worker(i); // find worker if (exit_status.get_pid() != worker->pid) { continue; } // check the process return code and signal _server->factory_->check_worker_exit_status(worker, exit_status); do { if (_server->factory_->spawn_event_worker(worker) < 0) { SW_START_SLEEP; continue; } break; } while (true); } // task worker if (task_pool->map_) { auto iter = task_pool->map_->find(exit_status.get_pid()); if (iter != task_pool->map_->end()) { _server->factory_->check_worker_exit_status(iter->second, exit_status); _server->factory_->spawn_task_worker(iter->second); } } // user process if (!_server->user_worker_map.empty()) { Server::wait_other_worker(pool, exit_status); } if (pool->reload_task) { pool->reload_task->remove(exit_status.get_pid()); } } if (pool->reload_task) { // reload finish if (pool->reload_task->is_completed()) { delete pool->reload_task; pool->reload_task = nullptr; if (_server->onAfterReload != nullptr) { _server->onAfterReload(_server); } } else { _kill_worker: pool->reload_task->kill_one(SIGTERM); } } } delete pool->reload_task; if (swoole_timer_is_available()) { swoole_timer_free(); /** * The timer will override the SIGALRM signal handler, * which needs to be reset to the manager's signal handler. */ swoole_signal_set(SIGALRM, signal_handler); } // wait child process if (_server->max_wait_time) { force_kill = true; SW_LOOP_N(_server->worker_num) { kill_workers.push_back(_server->workers[i].pid); } if (_server->task_worker_num > 0) { SW_LOOP_N(task_pool->worker_num) { kill_workers.push_back(task_pool->workers[i].pid); } } if (!_server->user_worker_map.empty()) { for (auto &kv : _server->user_worker_map) { kill_workers.push_back(kv.second->pid); } } /** * Multiply max_wait_time by 2 to prevent conflict with worker */ alarm(_server->max_wait_time * 2); } _server->factory_->kill_event_workers(); _server->factory_->kill_task_workers(); _server->factory_->kill_user_workers(); // force kill if (_server->max_wait_time) { alarm(0); } if (_server->onManagerStop) { _server->onManagerStop(_server); } } void Manager::terminate_all_worker() { alarm(0); swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, "wait timeout, all worker processes will be forcibly terminated"); for (int &kill_worker : kill_workers) { swoole_kill(kill_worker, SIGKILL); } } void Manager::reopen_logger() const { sw_logger()->reopen(); SW_LOOP_N(server_->get_all_worker_num()) { const auto worker = server_->get_worker(i); swoole_kill(worker->pid, SIGWINCH); } } void Manager::signal_handler(int signo) { Server *_server = sw_server(); if (!_server || !_server->manager_) { return; } Manager *manager = _server->manager_; ProcessPool *pool = _server->get_event_worker_pool(); switch (signo) { case SIGTERM: _server->running = false; break; case SIGUSR1: case SIGUSR2: _server->reload(signo == SIGUSR1); break; case SIGIO: pool->read_message = true; break; case SIGALRM: if (manager->force_kill) { manager->terminate_all_worker(); } break; case SIGWINCH: manager->reopen_logger(); break; default: #ifdef SIGRTMIN if (signo == SIGRTMIN) { manager->reopen_logger(); } #endif break; } } /** * @return: success returns pid, failure returns SW_ERR. */ int Server::wait_other_worker(ProcessPool *pool, const ExitStatus &exit_status) { auto serv = static_cast(pool->ptr); Worker *exit_worker = nullptr; int worker_type; do { if (serv->get_task_worker_pool()->map_) { const auto iter = serv->get_task_worker_pool()->map_->find(exit_status.get_pid()); if (iter != serv->get_task_worker_pool()->map_->end()) { worker_type = SW_TASK_WORKER; exit_worker = iter->second; break; } } if (!serv->user_worker_map.empty()) { const auto iter = serv->user_worker_map.find(exit_status.get_pid()); if (iter != serv->user_worker_map.end()) { worker_type = SW_USER_WORKER; exit_worker = iter->second; break; } } return SW_ERR; } while (false); serv->factory_->check_worker_exit_status(exit_worker, exit_status); pid_t new_process_pid = -1; switch (worker_type) { case SW_TASK_WORKER: new_process_pid = serv->factory_->spawn_task_worker(exit_worker); break; case SW_USER_WORKER: new_process_pid = serv->factory_->spawn_user_worker(exit_worker); break; default: /* never here */ abort(); } return new_process_pid; } /** * [manager] */ void Server::read_worker_message(ProcessPool *pool, EventData *msg) { if (msg->info.type != SW_SERVER_EVENT_COMMAND_REQUEST) { swoole_warning("unknown worker message type[%d]", msg->info.type); return; } const auto serv = static_cast(pool->ptr); int command_id = msg->info.server_fd; const auto iter = serv->command_handlers.find(command_id); if (iter == serv->command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[command_id=%d]", command_id); return; } Command::Handler handler = iter->second; const auto result = handler(serv, std::string(msg->data, msg->info.len)); SendData task{}; task.info.fd = msg->info.fd; task.info.reactor_id = 0; task.info.server_fd = -1; task.info.type = SW_SERVER_EVENT_COMMAND_RESPONSE; task.info.len = result.length(); task.data = result.c_str(); serv->message_bus.write(serv->get_command_reply_socket(), &task); } bool Server::reload(bool reload_all_workers) const { if (is_thread_mode()) { return reload_worker_threads(reload_all_workers); } if (gs->manager_pid == 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not supported with single process mode"); return false; } if (getpid() != gs->manager_pid) { return swoole_kill(get_manager_pid(), reload_all_workers ? SIGUSR1 : SIGUSR2) == 0; } if (!get_event_worker_pool()->reload()) { return false; } if (reload_all_workers) { manager_->reload_all_worker = true; } else { manager_->reload_task_worker = true; } return true; } } // namespace swoole ================================================ FILE: src/server/master.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_memory.h" #include "swoole_lock.h" #include "swoole_thread.h" #include "swoole_util.h" #include "swoole_hash.h" #include "swoole_api.h" #include #include #include using swoole::network::Address; using swoole::network::SendfileTask; using swoole::network::Socket; namespace swoole { TimerCallback Server::get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn) const { return [this, port, conn, reactor](Timer *, TimerNode *) { if (conn->protect) { return; } long ms = time(true); swoole_trace_log(SW_TRACE_SERVER, "timeout_callback, last_received_time=%f, last_sent_time=%f", conn->socket->last_received_time, conn->socket->last_sent_time); if (ms - conn->socket->last_received_time < port->max_idle_time && ms - conn->socket->last_sent_time < port->max_idle_time) { return; } if (disable_notify || conn->closed || conn->close_force) { close_connection(reactor, conn->socket); return; } conn->close_force = 1; Event _ev{}; _ev.fd = conn->fd; _ev.socket = conn->socket; reactor->trigger_close_event(&_ev); }; } void Server::disable_accept() { enable_accept_timer = swoole_timer_add( SW_ACCEPT_RETRY_TIME, false, [](Timer *timer, const TimerNode *tnode) { auto *serv = static_cast(tnode->data); for (const auto port : serv->ports) { if (port->is_dgram()) { continue; } swoole_event_add(port->socket, SW_EVENT_READ); } serv->enable_accept_timer = nullptr; }, this); if (enable_accept_timer == nullptr) { return; } for (const auto port : ports) { if (port->is_dgram()) { continue; } swoole_event_del(port->socket); } } void Server::call_command_callback(const int64_t request_id, const std::string &result) { const auto iter = command_callbacks.find(request_id); if (iter == command_callbacks.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Invalid command result[request_id=%" PRId64 "]", request_id); return; } iter->second(this, result); command_callbacks.erase(request_id); } void Server::call_command_handler(MessageBus &mb, uint16_t worker_id, Socket *sock) { const PipeBuffer *buffer = mb.get_buffer(); const int command_id = buffer->info.server_fd; const auto iter = command_handlers.find(command_id); if (iter == command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[command_id=%d]", command_id); return; } const Command::Handler handler = iter->second; const auto packet = mb.get_packet(); const auto result = handler(this, std::string(packet.data, packet.length)); SendData task{}; task.info.fd = buffer->info.fd; task.info.reactor_id = worker_id; task.info.server_fd = -1; task.info.type = SW_SERVER_EVENT_COMMAND_RESPONSE; task.info.len = result.length(); task.data = result.c_str(); mb.write(sock, &task); } std::string Server::call_command_handler_in_master(int command_id, const std::string &msg) { auto iter = command_handlers.find(command_id); if (iter == command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[%d]", command_id); return ""; } Server::Command::Handler handler = iter->second; return handler(this, msg); } int Server::accept_command_result(Reactor *reactor, Event *event) { auto *serv = static_cast(reactor->ptr); if (serv->message_bus.read(event->socket) <= 0) { return SW_OK; } auto packet = serv->message_bus.get_packet(); std::string result(packet.data, packet.length); auto buffer = serv->message_bus.get_buffer(); serv->call_command_callback(buffer->info.fd, result); serv->message_bus.pop(); return SW_OK; } int Server::accept_connection(Reactor *reactor, Event *event) { auto serv = static_cast(reactor->ptr); auto listen_host = static_cast(event->socket->object); for (int i = 0; i < SW_ACCEPT_MAX_COUNT; i++) { Socket *sock = event->socket->accept(); if (sock == nullptr) { switch (errno) { case EAGAIN: return SW_OK; case EINTR: continue; default: if (errno == EMFILE || errno == ENFILE) { serv->disable_accept(); } swoole_sys_warning("accept() failed"); return SW_OK; } } swoole_trace("[Master] Accept new connection. maxfd=%d|minfd=%d|reactor_id=%d|conn=%d", serv->get_maxfd(), serv->get_minfd(), reactor->id, sock->fd); // too many connection if (sock->fd >= (int) serv->max_connection) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SERVER_TOO_MANY_SOCKET, "Too many connections [now: %d]", sock->fd); serv->abort_connection(reactor, listen_host, sock); serv->disable_accept(); return SW_OK; } if (listen_host->ssl) { if (!listen_host->ssl_create(sock)) { serv->abort_connection(reactor, listen_host, sock); return SW_OK; } } else { sock->ssl = nullptr; } // add to connection_list Connection *conn = serv->add_connection(listen_host, sock, event->fd); if (conn == nullptr) { serv->abort_connection(reactor, listen_host, sock); return SW_OK; } sock->chunk_size = SW_SEND_BUFFER_SIZE; if (serv->single_thread) { if (serv->connection_incoming(reactor, conn) < 0) { serv->abort_connection(reactor, listen_host, sock); return SW_OK; } } else { DataHead ev{}; ev.type = SW_SERVER_EVENT_INCOMING; ev.fd = conn->session_id; ev.reactor_id = conn->reactor_id; ev.server_fd = event->fd; if (serv->send_to_reactor_thread(reinterpret_cast(&ev), sizeof(ev), conn->session_id) < 0) { serv->abort_connection(reactor, listen_host, sock); return SW_OK; } } } return SW_OK; } int Server::connection_incoming(Reactor *reactor, Connection *conn) const { ListenPort *port = get_port_by_server_fd(conn->server_fd); if (port->max_idle_time > 0) { const auto timeout_callback = get_timeout_callback(port, reactor, conn); conn->socket->read_timeout = port->max_idle_time; conn->socket->recv_timer = swoole_timer_add(sec2msec(port->max_idle_time), true, timeout_callback); } if (conn->socket->ssl) { return reactor->add(conn->socket, SW_EVENT_READ); } // delay receive, wait resume command if (!enable_delay_receive) { if (reactor->add(conn->socket, SW_EVENT_READ) < 0) { return SW_ERR; } } // notify worker process if (onConnect) { if (!notify(conn, SW_SERVER_EVENT_CONNECT)) { return SW_ERR; } } return SW_OK; } #ifdef SW_SUPPORT_DTLS dtls::Session *Server::accept_dtls_connection(const ListenPort *port, const Address *sa) { dtls::Session *session = nullptr; Connection *conn = nullptr; Socket *sock = make_socket(port->type, SW_FD_SESSION, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); if (!sock) { return nullptr; } sock->set_reuse_addr(); #ifdef HAVE_KQUEUE sock->set_reuse_port(); #endif switch (port->type) { case SW_SOCK_UDP: case SW_SOCK_UDP6: break; default: OPENSSL_assert(0); break; } if (sock->bind(port->host, port->port) < 0) { swoole_sys_warning("bind() failed"); goto _cleanup; } if (sock->is_inet6()) { sock->set_option(IPPROTO_IPV6, IPV6_V6ONLY, 0); } if (sock->connect(sa) < 0) { swoole_sys_warning("connect(%s:%d) failed", sa->get_addr(), sa->get_port()); goto _cleanup; } memcpy(&sock->info, sa, sizeof(*sa)); sock->chunk_size = SW_SSL_BUFFER_SIZE; conn = add_connection(port, sock, port->socket->fd); if (conn == nullptr) { goto _cleanup; } session = port->create_dtls_session(sock); if (session) { return session; } _cleanup: if (conn) { *conn = {}; } sock->free(); return nullptr; } #endif void Server::set_max_connection(uint32_t _max_connection) { if (connection_list != nullptr) { swoole_warning("max_connection must be set before server create"); return; } max_connection = _max_connection; if (max_connection == 0) { max_connection = SW_MIN(SW_MAX_CONNECTION, SwooleG.max_sockets); } else if (max_connection > SW_SESSION_LIST_SIZE) { max_connection = SW_SESSION_LIST_SIZE; swoole_warning("max_connection is exceed the SW_SESSION_LIST_SIZE, it's reset to %u", SW_SESSION_LIST_SIZE); } if (SwooleG.max_sockets > 0 && max_connection > SwooleG.max_sockets) { max_connection = SwooleG.max_sockets; swoole_warning("max_connection is exceed the maximum value, it's reset to %u", SwooleG.max_sockets); } } bool Server::set_document_root(const std::string &path) { if (path.length() > PATH_MAX) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_NAME_TOO_LONG, "The length of document_root must be less than %d", PATH_MAX); return false; } char _realpath[PATH_MAX]; if (!realpath(path.c_str(), _realpath)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_DIR_NOT_EXIST, "document_root[%s] does not exist", path.c_str()); return false; } document_root = std::string(_realpath); return true; } void Server::add_http_compression_type(const std::string &type) { if (http_compression_types == nullptr) { http_compression_types = std::make_shared>(); } http_compression_types->emplace(type); } const char *Server::get_startup_error_message() const { auto error_msg = swoole_get_last_error_msg(); if (strlen(error_msg) == 0 && swoole_get_last_error() > 0) { auto buf = sw_tg_buffer(); buf->clear(); buf->append(swoole_get_last_error()); buf->str[buf->length] = '\0'; error_msg = buf->str; } return error_msg; } int Server::start_check() { assert(is_created()); // disable notice when use SW_DISPATCH_ROUND and SW_DISPATCH_QUEUE if (is_process_mode()) { if (!is_support_unsafe_events()) { if (onConnect) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "cannot set 'onConnect' event when using dispatch_mode=%d", dispatch_mode); onConnect = nullptr; } if (onClose) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "cannot set 'onClose' event when using dispatch_mode=%d", dispatch_mode); onClose = nullptr; } if (onBufferFull) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "cannot set 'onBufferFull' event when using dispatch_mode=%d", dispatch_mode); onBufferFull = nullptr; } if (onBufferEmpty) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "cannot set 'onBufferEmpty' event when using dispatch_mode=%d", dispatch_mode); onBufferEmpty = nullptr; } disable_notify = true; } if (!is_support_send_yield()) { send_yield = false; } } else { max_queued_bytes = 0; } if (task_worker_num > 0) { if (onTask == nullptr) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onTask' callback"); return SW_ERR; } } if (send_timeout > 0 && send_timeout < SW_TIMER_MIN_SEC) { send_timeout = SW_TIMER_MIN_SEC; } if (heartbeat_check_interval > 0) { for (auto port : ports) { if (port->heartbeat_idle_time == 0) { port->heartbeat_idle_time = heartbeat_check_interval * 2; } } } for (auto port : ports) { if (port->protocol.package_max_length < SW_BUFFER_MIN_SIZE) { port->protocol.package_max_length = SW_BUFFER_MIN_SIZE; } if (if_require_receive_callback(port, onReceive != nullptr)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onReceive' callback"); return SW_ERR; } if (if_require_packet_callback(port, onPacket != nullptr)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onPacket' callback"); return SW_ERR; } if (port->heartbeat_idle_time > 0) { int expect_heartbeat_check_interval = port->heartbeat_idle_time > 2 ? port->heartbeat_idle_time / 2 : 1; if (heartbeat_check_interval == 0 || heartbeat_check_interval > expect_heartbeat_check_interval) { heartbeat_check_interval = expect_heartbeat_check_interval; } } if (port->open_websocket_protocol) { port->websocket_settings.compression = websocket_compression; } } return SW_OK; } int Server::start_master_thread(Reactor *reactor) { swoole_set_thread_type(THREAD_MASTER); swoole_set_thread_id(single_thread ? 0 : reactor_num); if (SwooleTG.timer && SwooleTG.timer->get_reactor() == nullptr) { SwooleTG.timer->reinit(); } init_signal_handler(); swoole_set_worker_type(SW_MASTER); if (is_thread_mode()) { swoole_set_worker_pid(swoole_thread_get_native_id()); } else if (is_process_mode()) { swoole_set_worker_pid(getpid()); } reactor->ptr = this; reactor->set_handler(SW_FD_STREAM_SERVER, SW_EVENT_READ, accept_connection); if (pipe_command) { if (!single_thread) { reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, accept_command_result); } reactor->add(pipe_command->get_socket(true), SW_EVENT_READ); } if ((master_timer = swoole_timer_add(1000L, true, Server::timer_callback, this)) == nullptr) { swoole_event_free(); return SW_ERR; } if (!single_thread && !is_thread_mode()) { reactor_thread_barrier.wait(); } if (is_process_mode()) { gs->manager_barrier.wait(); } gs->master_pid = getpid(); if (isset_hook(HOOK_MASTER_START)) { call_hook(HOOK_MASTER_START, this); } if (onStart) { onStart(this); } return swoole_event_wait(); } void Server::store_listen_socket() { for (auto ls : ports) { int sockfd = ls->socket->fd; // save server socket to connection_list connection_list[sockfd].fd = sockfd; connection_list[sockfd].socket = ls->socket; connection_list[sockfd].socket_type = ls->type; connection_list[sockfd].object = ls; connection_list[sockfd].info.assign(ls->type, ls->host, ls->port); ls->socket->object = ls; if (sockfd >= 0) { set_minfd(sockfd); set_maxfd(sockfd); } } } /** * only the memory of the Worker structure is allocated, no process is forked */ bool Server::create_task_workers() { key_t key = 0; swIPCMode ipc_mode; if (task_ipc_mode == TASK_IPC_MSGQUEUE || task_ipc_mode == TASK_IPC_PREEMPTIVE) { key = message_queue_key; ipc_mode = SW_IPC_MSGQUEUE; } else if (task_ipc_mode == TASK_IPC_STREAM) { ipc_mode = SW_IPC_SOCKET; } else { ipc_mode = SW_IPC_UNIXSOCK; } ProcessPool *pool = get_task_worker_pool(); *pool = {}; if (pool->create(task_worker_num, key, ipc_mode) < 0) { swoole_warning("[Master] create task_workers failed"); return false; } pool->set_max_request(task_max_request, task_max_request_grace); pool->set_start_id(worker_num); pool->set_type(SW_TASK_WORKER); if (ipc_mode == SW_IPC_SOCKET) { char sockfile[sizeof(struct sockaddr_un)]; snprintf(sockfile, sizeof(sockfile), "/tmp/swoole.task.%d.sock", gs->master_pid); if (get_task_worker_pool()->listen(sockfile, 2048) < 0) { return false; } } /* * For Server::task_sync(), create notify pipe and result shared memory. */ task_results = static_cast(sw_shm_calloc(worker_num, sizeof(EventData))); if (!task_results) { swoole_warning("sw_shm_calloc(%d, %zu) for task_result failed", worker_num, sizeof(EventData)); return false; } SW_LOOP_N(worker_num) { auto _pipe = new Pipe(true); if (!_pipe->ready()) { sw_shm_free(task_results); delete _pipe; return false; } task_notify_pipes.emplace_back(_pipe); } if (!init_task_workers()) { return false; } return true; } void Server::destroy_task_workers() const { if (task_results) { sw_shm_free(task_results); } get_task_worker_pool()->destroy(); } bool Server::create_event_workers() { SW_LOOP_N(worker_num) { create_worker(get_worker(i)); } return true; } /** * @description: * only the memory of the Worker structure is allocated, no process is fork. * called when the manager process start. * @return: SW_OK|SW_ERR */ bool Server::create_user_workers() { user_workers = static_cast(sw_shm_calloc(get_user_worker_num(), sizeof(Worker))); if (user_workers == nullptr) { swoole_sys_warning("sw_shm_calloc(%lu, %zu) for user_workers failed", get_user_worker_num(), sizeof(Worker)); return false; } int i = 0; for (const auto worker : user_worker_list) { memcpy(&user_workers[i], worker, sizeof(user_workers[i])); create_worker(worker); i++; } return true; } /** * [Master] */ void Server::create_worker(Worker *worker) { worker->lock = new Mutex(true); if (worker->pipe_object) { store_pipe_fd(worker->pipe_object); } } void Server::destroy_worker(Worker *worker) { delete worker->lock; worker->lock = nullptr; } /** * [Worker] */ void Server::init_event_worker(Worker *worker) const { worker->init(); worker->set_max_request(max_request, max_request_grace); } int Server::start() { swoole_clear_last_error(); swoole_clear_last_error_msg(); if (start_check() < 0) { return SW_ERR; } if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_START)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_START, this); } // cannot start 2 servers at the same time, please use process->exec. if (!sw_atomic_cmp_set(&gs->start, 0, 1)) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_ONLY_START_ONE, "can only start one server"); return SW_ERR; } // run as daemon if (daemonize > 0) { // redirect stdout/stderr to log file if (sw_logger()->is_opened()) { sw_logger()->redirect_stdout_and_stderr(true); } // redirect stdout/stderr to /dev/null else { swoole_redirect_stdout("/dev/null"); } if (swoole_daemon(0, 1) < 0) { return SW_ERR; } } gs->start_time = ::time(nullptr); /** * store to ProcessPool object */ auto pool = get_event_worker_pool(); pool->ptr = this; pool->workers = workers; pool->worker_num = worker_num; pool->use_msgqueue = 0; SW_LOOP_N(worker_num) { pool->workers[i].pool = pool; pool->workers[i].id = i; pool->workers[i].type = SW_WORKER; } if (!user_worker_list.empty()) { uint32_t i = 0; for (auto worker : user_worker_list) { worker->id = worker_num + task_worker_num + i; i++; } } running = true; // factory start if (!factory_->start()) { return SW_ERR; } // write PID file if (!pid_file.empty()) { size_t n = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, "%d", getpid()); file_put_contents(pid_file, sw_tg_buffer()->str, n); } int ret; if (is_base_mode()) { ret = start_reactor_processes(); } else if (is_process_mode()) { ret = start_reactor_threads(); } else if (is_thread_mode()) { ret = start_worker_threads(); } else { abort(); return SW_ERR; } // failed to start if (ret < 0) { return SW_ERR; } destroy(); // remove PID file if (!pid_file.empty()) { unlink(pid_file.c_str()); } return SW_OK; } /** * initializing server config, set default */ Server::Server(Mode _mode) { reactor_num = SW_CPU_NUM > SW_REACTOR_MAX_THREAD ? SW_REACTOR_MAX_THREAD : SW_CPU_NUM; worker_num = SW_CPU_NUM; max_connection = SW_MIN(SW_MAX_CONNECTION, SwooleG.max_sockets); mode_ = _mode; // http server http_compression = true; http_compression_level = SW_Z_BEST_SPEED; compression_min_length = SW_COMPRESSION_MIN_LENGTH_DEFAULT; timezone_ = get_timezone(); gs = static_cast(sw_shm_malloc(sizeof(ServerGS))); if (gs == nullptr) { swoole_sys_warning("[Master] Fatal Error: failed to allocate memory for Server->gs"); throw std::bad_alloc(); } gs->pipe_packet_msg_id = 1; gs->max_concurrency = UINT_MAX; msg_id_generator = [this]() { return sw_atomic_fetch_add(&gs->pipe_packet_msg_id, 1); }; message_bus.set_id_generator(msg_id_generator); #ifdef SW_THREAD worker_thread_start = [](std::shared_ptr, const WorkerFn &fn) { fn(); }; #endif SwooleG.server = this; } Server::~Server() { if (!is_shutdown() && getpid() == gs->master_pid) { destroy(); } for (auto port : ports) { delete port; } sw_shm_free(gs); } Worker *Server::get_worker(uint16_t worker_id) const { // Event Worker if (worker_id < worker_num) { return &(get_event_worker_pool()->workers[worker_id]); } // Task Worker uint32_t task_worker_max = task_worker_num + worker_num; if (worker_id < task_worker_max) { return &(get_task_worker_pool()->workers[worker_id - worker_num]); } // User Worker uint32_t user_worker_max = task_worker_max + user_worker_list.size(); if (worker_id < user_worker_max) { return &(user_workers[worker_id - task_worker_max]); } return nullptr; } int Server::create() { if (is_created()) { return SW_ERR; } assert(!ports.empty()); if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE, this); } if (!init_network_interface_addr_map()) { return SW_ERR; } session_list = static_cast(sw_shm_calloc(SW_SESSION_LIST_SIZE, sizeof(Session))); if (session_list == nullptr) { swoole_sys_warning("sw_shm_calloc(%d, %zu) for session_list failed", SW_SESSION_LIST_SIZE, sizeof(Session)); return SW_ERR; } port_gs_list = static_cast(sw_shm_calloc(ports.size(), sizeof(ServerPortGS))); if (port_gs_list == nullptr) { swoole_sys_warning( "sw_shm_calloc(%zu, %zu) for port_connection_num_array failed", ports.size(), sizeof(ServerPortGS)); return SW_ERR; } int index = 0; for (auto port : ports) { port->gs = &port_gs_list[index++]; } if (enable_static_handler and locations == nullptr) { locations = std::make_shared>(); } if (http_compression_types && !http_compression_types->empty()) { http_compression = true; } // Max Connections uint32_t minimum_connection = (worker_num + task_worker_num) * 2 + 32; if (!ports.empty()) { minimum_connection += ports.back()->get_fd(); } if (max_connection < minimum_connection) { auto real_max_connection = SW_MAX(minimum_connection + 1, SwooleG.max_sockets); swoole_warning( "max_connection must be bigger than %u, it's reset to %u", minimum_connection, real_max_connection); max_connection = real_max_connection; } // Reactor Thread Num if (reactor_num > SW_CPU_NUM * SW_MAX_THREAD_NCPU) { swoole_warning( "reactor_num == %d, Too many threads, reset to max value %d", reactor_num, SW_CPU_NUM * SW_MAX_THREAD_NCPU); reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU; } else if (reactor_num == 0) { reactor_num = SW_CPU_NUM; } if (single_thread) { reactor_num = 1; } // Worker Process Num if (worker_num > SW_CPU_NUM * SW_MAX_WORKER_NCPU) { swoole_warning( "worker_num == %d, Too many processes, reset to max value %d", worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU; } if (worker_num < reactor_num) { reactor_num = worker_num; } // TaskWorker Process Num if (task_worker_num > 0) { if (task_worker_num > SW_CPU_NUM * SW_MAX_WORKER_NCPU) { swoole_warning("task_worker_num == %d, Too many processes, reset to max value %d", task_worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); task_worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU; } } workers = static_cast(sw_shm_calloc(worker_num, sizeof(Worker))); if (workers == nullptr) { swoole_sys_warning("sw_shm_calloc(%d, %zu) for workers failed", worker_num, sizeof(Worker)); return SW_ERR; } if (is_base_mode()) { factory_ = create_base_factory(); } else if (is_thread_mode()) { factory_ = create_thread_factory(); } else { factory_ = create_process_factory(); } if (!factory_) { return SW_ERR; } if (task_worker_num > 0 && !create_task_workers()) { return SW_ERR; } if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, this); } return SW_OK; } void Server::clear_timer() { if (master_timer) { swoole_timer_del(master_timer); master_timer = nullptr; } if (heartbeat_timer) { swoole_timer_del(heartbeat_timer); heartbeat_timer = nullptr; } if (enable_accept_timer) { swoole_timer_del(enable_accept_timer); enable_accept_timer = nullptr; } } bool Server::shutdown() { if (sw_unlikely(!is_started())) { swoole_set_last_error(SW_ERROR_WRONG_OPERATION); return false; } /** * In thread mode, the worker thread masks all signals, and only a specific signal is processed. * Sending a signal to its own process can inform the main thread to prepare for exit. */ if (is_thread_mode() && is_master_thread()) { stop_master_thread(); return true; } if (is_process_mode()) { /** * The working process may be using a non-root user, and it will not be possible to send a signal to the master * process. A shutdown command must be sent via a pipe. */ SendData _send{}; _send.info.type = SW_SERVER_EVENT_SHUTDOWN_SIGNAL; if (is_process_mode()) { return get_reactor_pipe_socket(0, 0)->send_async(&_send, sizeof(_send)) > 0; } } else { pid_t pid; if (is_base_mode()) { pid = get_manager_pid() == 0 ? get_master_pid() : get_manager_pid(); } else { pid = get_master_pid(); } if (swoole_kill(pid, SIGTERM) < 0) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SYSTEM_CALL_FAIL, "failed to shutdown, kill(%d, SIGTERM) failed", pid); return false; } } return true; } bool Server::signal_handler_reload(bool reload_all_workers) const { const auto rv = reload(reload_all_workers); sw_logger()->reopen(); return rv; } bool Server::signal_handler_read_message() const { get_event_worker_pool()->read_message = true; return true; } bool Server::signal_handler_reopen_logger() const { swoole_trace_log(SW_TRACE_SERVER, "reopen log file ['%s']", sw_logger()->get_file()); sw_logger()->reopen(); if (is_process_mode()) { swoole_kill(gs->manager_pid, SIGWINCH); } return true; } void Server::stop_master_thread() { Reactor *reactor = SwooleTG.reactor; reactor->set_wait_exit(true); for (auto port : ports) { if (port->is_dgram() && !is_base_mode()) { continue; } if (!port->socket->removed) { reactor->del(port->socket); } } if (pipe_command) { reactor->del(pipe_command->get_socket(true)); } clear_timer(); if (max_wait_time > 0) { time_t shutdown_time = std::time(nullptr); auto fn = [shutdown_time, this](Reactor *reactor, size_t &) { time_t now = std::time(nullptr); if (now - shutdown_time > max_wait_time) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, "graceful shutdown failed, forced termination"); reactor->running = false; } return true; }; reactor->set_exit_condition(Reactor::EXIT_CONDITION_FORCED_TERMINATION, fn); } if (is_thread_mode()) { stop_worker_threads(); } if (is_process_mode() && single_thread) { get_thread(0)->shutdown(reactor); } } bool Server::signal_handler_shutdown() { swoole_trace_log(SW_TRACE_SERVER, "shutdown begin"); if (is_base_mode()) { if (gs->manager_pid > 0) { running = false; } else { // single process worker, exit directly get_event_worker_pool()->running = false; stop_async_worker(sw_worker()); } return true; } if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, this); } if (onBeforeShutdown) { onBeforeShutdown(this); } running = false; stop_master_thread(); swoole_trace_log(SW_TRACE_SERVER, "shutdown end"); return true; } bool Server::signal_handler_child_exit() const { if (!running) { return false; } if (is_base_mode()) { return false; } int status; pid_t pid = waitpid(-1, &status, WNOHANG); if (pid > 0 && pid == gs->manager_pid) { swoole_warning("Fatal Error: manager process exit. status=%d, signal=[%s]", WEXITSTATUS(status), swoole_signal_to_str(WTERMSIG(status))); } return true; } void Server::destroy() { if (!factory_) { return; } swoole_trace_log(SW_TRACE_SERVER, "release service"); if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN, this); } if (is_process_mode()) { swoole_trace_log(SW_TRACE_SERVER, "terminate reactor threads"); /** * Wait until all the end of the thread */ join_reactor_thread(); } /** * The position of the following code cannot be modified. * We need to ensure that in SWOOLE_PROCESS mode, all SW_WRITE_EVENT events in the reactor thread are fully * completed. Therefore, the worker process must wait for the reactor thread to exit first; otherwise, the main * thread will keep waiting for the reactor thread to exit. */ if (is_started()) { factory_->shutdown(); } SW_LOOP_N(worker_num) { Worker *worker = &workers[i]; destroy_worker(worker); } release_pipe_buffers(); for (auto port : ports) { port->close(); } if (user_workers) { sw_shm_free(user_workers); user_workers = nullptr; } swoole_signal_clear(); gs->start = 0; gs->shutdown = 1; if (onShutdown) { onShutdown(this); } SW_LOOP_N(SW_MAX_HOOK_TYPE) { if (hooks[i]) { auto l = static_cast *>(hooks[i]); hooks[i] = nullptr; delete l; } } if (is_base_mode()) { destroy_base_factory(); } else if (is_thread_mode()) { destroy_thread_factory(); } else { destroy_process_factory(); } if (task_worker_num > 0) { swoole_trace_log(SW_TRACE_SERVER, "terminate task workers"); destroy_task_workers(); } sw_shm_free(session_list); sw_shm_free(port_gs_list); sw_shm_free(workers); session_list = nullptr; port_gs_list = nullptr; workers = nullptr; delete factory_; factory_ = nullptr; SwooleG.server = nullptr; } /** * worker to master process */ bool Server::feedback(Connection *conn, enum ServerEventType event) { SendData _send{}; _send.info.type = event; _send.info.fd = conn->session_id; _send.info.reactor_id = conn->reactor_id; if (is_process_mode()) { return send_to_reactor_thread( reinterpret_cast(&_send.info), sizeof(_send.info), conn->session_id) > 0; } else { return send_to_connection(&_send) == SW_OK; } } bool Server::command(WorkerId process_id, Command::ProcessType process_type, const std::string &name, const std::string &msg, const Command::Callback &fn) { if (!is_started()) { return false; } auto iter = commands.find(name); if (iter == commands.end()) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[%s]", name.c_str()); return false; } if (is_process_mode() && !is_master()) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "command() can only be used in master process"); return false; } else if (is_base_mode() && sw_worker()->id != 0) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "command() can only be used in worker process 0"); return false; } if (is_base_mode() && process_type == Command::EVENT_WORKER && process_id == 0) { process_type = Command::MASTER; } if (is_process_mode() && process_type == Command::REACTOR_THREAD && process_id == reactor_num) { process_type = Command::MASTER; process_id = 0; } int command_id = iter->second.id; int64_t request_id = command_current_request_id++; Socket *pipe_sock; SendData task{}; task.info.fd = request_id; task.info.reactor_id = process_id; task.info.server_fd = command_id; task.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; task.info.len = msg.length(); task.data = msg.c_str(); command_callbacks[request_id] = fn; if (!(process_type & iter->second.accepted_process_types)) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [process_type]"); _fail: command_callbacks.erase(request_id); return false; } if (process_type == Command::REACTOR_THREAD) { if (!is_process_mode()) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [server_mode]"); goto _fail; } if (process_id >= reactor_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid thread_id[%d]", process_id); goto _fail; } pipe_sock = get_worker(process_id)->pipe_worker; } else if (process_type == Command::EVENT_WORKER) { if (process_id >= worker_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid worker_id[%d]", process_id); goto _fail; } pipe_sock = get_worker(process_id)->pipe_master; } else if (process_type == Command::TASK_WORKER) { if (process_id >= task_worker_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid task_worker_id[%d]", process_id); goto _fail; } EventData buf; if (!task_pack(&buf, msg.c_str(), msg.length())) { goto _fail; } buf.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; buf.info.fd = request_id; buf.info.server_fd = command_id; int _dst_worker_id = process_id; if (!this->task(&buf, &_dst_worker_id)) { goto _fail; } return true; } else if (process_type == Command::MANAGER) { EventData buf; if (msg.length() >= sizeof(buf.data)) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_DATA_LENGTH_TOO_LARGE, "message is too large, maximum length is %lu, the given length is %lu", sizeof(buf.data), msg.length()); goto _fail; } memset(&buf.info, 0, sizeof(buf.info)); buf.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; buf.info.fd = request_id; buf.info.server_fd = command_id; buf.info.len = msg.length(); memcpy(buf.data, msg.c_str(), msg.length()); if (get_event_worker_pool()->push_message(&buf) < 0) { goto _fail; } return true; } else if (process_type == Command::MASTER) { auto result = call_command_handler_in_master(command_id, msg); fn(this, result); return true; } else { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [process_type]"); goto _fail; } if (!message_bus.write(pipe_sock, &task)) { goto _fail; } return true; } void Server::store_pipe_fd(UnixSocket *p) { Socket *master_socket = p->get_socket(true); Socket *worker_socket = p->get_socket(false); connection_list[master_socket->fd].object = p; connection_list[worker_socket->fd].object = p; if (master_socket->fd > get_maxfd()) { set_maxfd(master_socket->fd); } if (worker_socket->fd > get_maxfd()) { set_maxfd(worker_socket->fd); } } /** * @process Worker */ bool Server::send(SessionId session_id, const void *data, uint32_t length) const { SendData _send{}; _send.info.fd = session_id; _send.info.type = SW_SERVER_EVENT_SEND_DATA; _send.data = (char *) data; _send.info.len = length; if (factory_->finish(&_send)) { sw_atomic_fetch_add(&gs->response_count, 1); sw_atomic_fetch_add(&gs->total_send_bytes, length); ListenPort *port = get_port_by_session_id(session_id); if (port) { sw_atomic_fetch_add(&port->gs->response_count, 1); sw_atomic_fetch_add(&port->gs->total_send_bytes, length); } if (sw_worker()) { sw_worker()->response_count++; } return true; } return false; } bool Server::has_kernel_nobufs_error(SessionId session_id) const { auto conn = get_connection(session_id); if (!conn || !conn->socket) { return false; } if (is_process_mode()) { return get_reactor_pipe_socket(session_id, conn->reactor_id)->has_kernel_nobufs(); } else { return conn->socket->has_kernel_nobufs(); } } int Server::schedule_worker(int fd, SendData *data) { uint32_t key = 0; if (dispatch_func) { int id = dispatch_func(this, get_connection(fd), data); if (id != DISPATCH_RESULT_USERFUNC_FALLBACK) { return id; } } // polling mode if (dispatch_mode == DISPATCH_ROUND) { key = sw_atomic_fetch_add(&worker_round_id, 1); } // Using the FD touch access to hash else if (dispatch_mode == DISPATCH_FDMOD) { key = fd; } // Using the IP touch access to hash else if (dispatch_mode == DISPATCH_IPMOD) { Connection *conn = get_connection(fd); Address *addr; if (conn == nullptr) { auto *packet = (DgramPacket *) data->data; addr = &packet->socket_addr; } else { addr = &conn->info; } if (Socket::is_inet4(addr->type)) { key = ntohl(addr->addr.inet_v4.sin_addr.s_addr); } else { key = swoole_hash_php((char *) &addr->addr.inet_v6, sizeof(addr->addr.inet_v6)); } } else if (dispatch_mode == DISPATCH_UIDMOD) { Connection *conn = get_connection(fd); if (conn == nullptr || conn->uid == 0) { key = fd; } else { key = conn->uid; } } else if (dispatch_mode == DISPATCH_CO_CONN_LB) { Connection *conn = get_connection(fd); if (conn == nullptr) { return fd % worker_num; } if (conn->worker_id < 0) { conn->worker_id = get_lowest_load_worker_id(); } return conn->worker_id; } else if (dispatch_mode == DISPATCH_CO_REQ_LB) { return get_lowest_load_worker_id(); } else if (dispatch_mode == DISPATCH_CONCURRENT_LB) { return get_lowest_concurrent_worker_id(); } // deliver tasks to idle worker processes else { return get_idle_worker_id(); } return key % worker_num; } /** * [Master] send to client or append to out_buffer * @return SW_OK or SW_ERR */ int Server::send_to_connection(const SendData *_send) const { const SessionId session_id = _send->info.fd; const char *_send_data = _send->data; uint32_t _send_length = _send->info.len; Connection *conn; if (_send->info.type != SW_SERVER_EVENT_CLOSE) { conn = get_connection_verify(session_id); } else { conn = get_connection_verify_no_ssl(session_id); } if (!conn) { if (_send->info.type == SW_SERVER_EVENT_SEND_DATA) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "send %d byte failed, session#%ld does not exist", _send_length, session_id); } else { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "send event[%d] failed, session#%ld does not exist", _send->info.type, session_id); } return SW_ERR; } int fd = conn->fd; Reactor *reactor = SwooleTG.reactor; ListenPort *port = get_port_by_server_fd(conn->server_fd); if (!single_thread) { assert(fd % reactor_num == reactor->id); assert(fd % reactor_num == SwooleTG.id); } if (!is_process_mode() && conn->overflow) { if (send_yield) { swoole_set_last_error(SW_ERROR_OUTPUT_SEND_YIELD); } else { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "socket#%d output buffer overflow", fd); } return SW_ERR; } Socket *_socket = conn->socket; /** * Reset send buffer, Immediately close the connection. */ if (_send->info.type == SW_SERVER_EVENT_CLOSE && (conn->close_reset || conn->close_force || conn->peer_closed)) { goto _close_fd; } /** * pause recv data */ else if (_send->info.type == SW_SERVER_EVENT_PAUSE_RECV) { if (_socket->removed || !(_socket->events & SW_EVENT_READ)) { return SW_OK; } if (_socket->events & SW_EVENT_WRITE) { return reactor->set(conn->socket, SW_EVENT_WRITE); } else { return reactor->del(conn->socket); } } /** * resume recv data */ else if (_send->info.type == SW_SERVER_EVENT_RESUME_RECV) { if (!_socket->removed || (_socket->events & SW_EVENT_READ)) { return SW_OK; } if (_socket->events & SW_EVENT_WRITE) { return reactor->set(_socket, SW_EVENT_READ | SW_EVENT_WRITE); } else { return reactor->add(_socket, SW_EVENT_READ); } } if (Buffer::empty(_socket->out_buffer)) { /** * close connection. */ if (_send->info.type == SW_SERVER_EVENT_CLOSE) { _close_fd: reactor->close(reactor, _socket); return SW_OK; } // Direct send if (_send->info.type != SW_SERVER_EVENT_SEND_FILE) { if (!_socket->direct_send) { goto _buffer_send; } _direct_send: ssize_t n = _socket->send(_send_data, _send_length, 0); if (n == _send_length) { conn->last_send_time = microtime(); return SW_OK; } else if (n > 0) { _send_data += n; _send_length -= n; goto _buffer_send; } else if (errno == EINTR) { goto _direct_send; } else { goto _buffer_send; } } // buffer send else { _buffer_send: if (!_socket->out_buffer) { _socket->out_buffer = new Buffer(SW_SEND_BUFFER_SIZE); } } } // close connection if (_send->info.type == SW_SERVER_EVENT_CLOSE) { _socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); conn->close_queued = 1; } // sendfile to client else if (_send->info.type == SW_SERVER_EVENT_SEND_FILE) { auto *task = (SendfileTask *) _send_data; if (conn->socket->sendfile_async(task->filename, task->offset, task->length) < 0) { return false; } } // send data else { // connection is closed if (conn->peer_closed) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED_BY_CLIENT, "socket#%d is closed by client", fd); return false; } // connection output buffer overflow if (_socket->out_buffer->length() >= _socket->buffer_size) { if (send_yield) { swoole_set_last_error(SW_ERROR_OUTPUT_SEND_YIELD); } else { swoole_error_log( SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "connection#%d output buffer overflow", fd); } conn->overflow = 1; if (onBufferEmpty && onBufferFull == nullptr) { conn->high_watermark = 1; } } _socket->out_buffer->append(_send_data, _send_length); conn->send_queued_bytes = _socket->out_buffer->length(); if (onBufferFull && conn->high_watermark == 0 && _socket->out_buffer->length() >= port->buffer_high_watermark) { notify(conn, SW_SERVER_EVENT_BUFFER_FULL); conn->high_watermark = 1; } } if (port->max_idle_time > 0 && _socket->send_timer == nullptr) { const auto timeout_callback = get_timeout_callback(port, reactor, conn); _socket->read_timeout = port->max_idle_time; _socket->last_sent_time = time(true); _socket->send_timer = swoole_timer_add(sec2msec(port->max_idle_time), true, timeout_callback); swoole_trace_log(SW_TRACE_SERVER, "added send_timer[id=%ld], port->max_idle_time=%f", _socket->send_timer->id, port->max_idle_time); } if (!_socket->isset_writable_event()) { reactor->add_write_event(_socket); } return SW_OK; } /** * use in master process */ bool Server::notify(Connection *conn, ServerEventType event) const { DataHead notify_event{}; notify_event.type = event; notify_event.reactor_id = conn->reactor_id; notify_event.fd = conn->fd; notify_event.server_fd = conn->server_fd; return factory_->notify(¬ify_event); } /** * @process Worker */ bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length) const { if (sw_unlikely(session_id <= 0)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SESSION_INVALID_ID, "invalid fd[%ld]", session_id); return false; } if (sw_unlikely(is_master())) { swoole_error_log( SW_LOG_ERROR, SW_ERROR_SERVER_SEND_IN_MASTER, "can't send data to the connections in master process"); return false; } char _buffer[SW_IPC_BUFFER_SIZE]; auto *req = reinterpret_cast(_buffer); // file name size if (sw_unlikely(l_file > sizeof(_buffer) - sizeof(*req) - 1)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_NAME_TOO_LONG, "sendfile name[%.8s...] length %u is exceed the max name len %u", file, l_file, (uint32_t)(SW_IPC_BUFFER_SIZE - sizeof(SendfileTask) - 1)); return false; } // string must be zero termination (for `state` system call) swoole_strlcpy(req->filename, file, sizeof(_buffer) - sizeof(*req)); // check state struct stat file_stat; if (stat(req->filename, &file_stat) < 0) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SYSTEM_CALL_FAIL, "stat(%s) failed", req->filename); return false; } if (!S_ISREG(file_stat.st_mode)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_IS_NOT_REGULAR_FILE, "the path[%s] given is not a regular file", req->filename); return false; } if (file_stat.st_size <= offset) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SYSTEM_CALL_FAIL, "file[offset=%ld] is empty", (long) offset); return false; } req->offset = offset; req->length = length; // construct send data SendData send_data{}; send_data.info.fd = session_id; send_data.info.type = SW_SERVER_EVENT_SEND_FILE; send_data.info.len = sizeof(SendfileTask) + l_file + 1; send_data.data = _buffer; return factory_->finish(&send_data); } /** * [Worker] Returns the number of bytes sent */ bool Server::sendwait(SessionId session_id, const void *data, uint32_t length) const { Connection *conn = get_connection_verify(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "send %d byte failed, because session#%ld is not exists", length, session_id); return false; } return conn->socket->send_sync(data, length) == length; } void Server::call_hook(HookType type, void *arg) { assert(type <= HOOK_END); swoole::hook_call(hooks, type, arg); } /** * [Worker] */ bool Server::close(SessionId session_id, bool reset) const { return factory_->end(session_id, reset ? (CLOSE_ACTIVELY | CLOSE_RESET) : CLOSE_ACTIVELY); } bool Server::send_pipe_message(WorkerId worker_id, EventData *msg) { msg->info.type = SW_SERVER_EVENT_PIPE_MESSAGE; return send_to_worker_from_worker(get_worker(worker_id), msg, msg->size(), SW_PIPE_MASTER | SW_PIPE_NONBLOCK) > 0; } bool Server::send_pipe_message(WorkerId worker_id, const char *data, size_t len) { EventData buf; if (!task_pack(&buf, data, len)) { return false; } return send_pipe_message(worker_id, &buf); } void Server::init_signal_handler() const { swoole_signal_set(SIGPIPE, nullptr); swoole_signal_set(SIGHUP, nullptr); if (is_process_mode()) { swoole_signal_set(SIGCHLD, master_signal_handler); } else { swoole_signal_set(SIGIO, master_signal_handler); } swoole_signal_set(SIGUSR1, master_signal_handler); swoole_signal_set(SIGUSR2, master_signal_handler); swoole_signal_set(SIGTERM, master_signal_handler); swoole_signal_set(SIGWINCH, master_signal_handler); #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, master_signal_handler); #endif if (SwooleG.signal_fd > 0) { set_minfd(SwooleG.signal_fd); } } void Server::timer_callback(Timer *timer, TimerNode *tnode) { auto *serv = static_cast(tnode->data); time_t now = ::time(nullptr); if (serv->scheduler_warning && serv->warning_time < now) { serv->scheduler_warning = false; serv->warning_time = now; swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_NO_IDLE_WORKER, "No idle worker is available"); } auto task_pool = serv->get_task_worker_pool(); if (task_pool->scheduler_warning && task_pool->warning_time < now) { task_pool->scheduler_warning = 0; task_pool->warning_time = now; swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_NO_IDLE_WORKER, "No idle task worker is available"); } if (serv->hooks[Server::HOOK_MASTER_TIMER]) { serv->call_hook(Server::HOOK_MASTER_TIMER, serv); } if (!serv->is_running()) { sw_reactor()->running = false; serv->stop_master_thread(); } } int Server::add_worker(Worker *worker) { if (is_created()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "must add worker before server is created"); return SW_ERR; } user_worker_list.push_back(worker); worker->id = user_worker_list.size() - 1; return worker->id; } void Server::add_hook(Server::HookType type, const Callback &func, int push_back) { swoole::hook_add(hooks, (int) type, func, push_back); } bool Server::add_command(const std::string &name, int accepted_process_types, const Command::Handler &func) { if (is_started()) { return false; } if (commands.find(name) != commands.end()) { return false; } if (!is_base_mode() && pipe_command == nullptr) { auto _pipe = new UnixSocket(false, SOCK_DGRAM); if (!_pipe->ready()) { delete _pipe; return false; } pipe_command = _pipe; } int command_id = command_current_id++; Command command{ command_id, accepted_process_types, name, }; commands.emplace(name, command); command_handlers[command_id] = func; return true; } void Server::check_port_type(const ListenPort *ls) { if (ls->is_dgram()) { // dgram socket, setting socket buffer size ls->socket->set_buffer_size(ls->socket_buffer_size); have_dgram_sock = true; dgram_port_num++; if (ls->type == SW_SOCK_UDP) { udp_socket_ipv4 = ls->socket; } else if (ls->type == SW_SOCK_UDP6) { udp_socket_ipv6 = ls->socket; } else if (ls->type == SW_SOCK_UNIX_DGRAM) { dgram_socket = ls->socket; } } else { have_stream_sock = true; } } bool Server::is_healthy_connection(double now, const Connection *conn) const { if (conn->protect || conn->last_recv_time == 0) { return true; } auto lp = get_port_by_session_id(conn->session_id); if (!lp) { return true; } if (lp->heartbeat_idle_time == 0) { return true; } if (conn->last_recv_time > now - lp->heartbeat_idle_time) { return true; } return false; } /** * Return the number of ports successfully */ int Server::add_systemd_socket() { int pid; if (!swoole_get_env("LISTEN_PID", &pid) && getpid() != pid) { swoole_warning("invalid LISTEN_PID"); return 0; } int n = swoole_get_systemd_listen_fds(); if (n <= 0) { return 0; } int count = 0; int start_fd; if (!swoole_get_env("LISTEN_FDS_START", &start_fd)) { start_fd = SW_SYSTEMD_FDS_START; } else if (start_fd < 0) { swoole_warning("invalid LISTEN_FDS_START"); return 0; } for (int sock = start_fd; sock < start_fd + n; sock++) { std::unique_ptr ptr(new ListenPort(this)); ListenPort *ls = ptr.get(); if (!ls->import(sock)) { continue; } // O_NONBLOCK & O_CLOEXEC ls->socket->set_fd_option(1, 1); ptr.release(); check_port_type(ls); ports.push_back(ls); ls->object_id = ports.size(); count++; } return count; } ListenPort *Server::add_port(SocketType type, const char *host, int port) { if (is_created()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "must add port before server is created"); return nullptr; } if (ports.size() >= SW_MAX_LISTEN_PORT) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_TOO_MANY_LISTEN_PORT, "up to %d listening ports are allowed", SW_MAX_LISTEN_PORT); return nullptr; } if (!Socket::is_local(type) && !Address::verify_port(port)) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_LISTEN_PORT, "invalid port [%d]", port); return nullptr; } if (strlen(host) + 1 > SW_HOST_MAXSIZE) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_NAME_TOO_LONG, "address '%s' exceeds the limit of %ld characters", host, SW_HOST_MAXSIZE - 1); return nullptr; } std::unique_ptr ptr(new ListenPort(this)); ListenPort *ls = ptr.get(); ls->type = type; ls->port = port; ls->host = host; if (type & SW_SOCK_SSL) { type = static_cast(type & (~SW_SOCK_SSL)); ls->type = type; ls->ssl = 1; ls->ssl_context_init(); } if (ls->create_socket() < 0) { swoole_set_last_error(errno); return nullptr; } check_port_type(ls); ptr.release(); ports.push_back(ls); ls->object_id = ports.size(); return ls; } void Server::master_signal_handler(int signo) { swoole_trace_log(SW_TRACE_SERVER, "signal[%d] %s triggered in %d", signo, swoole_signal_to_str(signo), getpid()); Server *serv = sw_server(); if (!SwooleG.running || !serv || !serv->is_running()) { return; } switch (signo) { case SIGTERM: serv->signal_handler_shutdown(); break; case SIGCHLD: serv->signal_handler_child_exit(); break; case SIGUSR1: case SIGUSR2: serv->signal_handler_reload(signo == SIGUSR1); break; case SIGIO: serv->signal_handler_read_message(); break; case SIGWINCH: serv->signal_handler_reopen_logger(); break; default: #ifdef SIGRTMIN if (signo == SIGRTMIN) { serv->signal_handler_reopen_logger(); } #endif break; } } void Server::foreach_connection(const std::function &callback) const { for (int fd = get_minfd(); fd <= get_maxfd(); fd++) { Connection *conn = get_connection(fd); if (is_valid_connection(conn)) { callback(conn); } } } void Server::abort_connection(Reactor *reactor, const ListenPort *ls, Socket *_socket) const { sw_atomic_fetch_add(&gs->abort_count, 1); sw_atomic_fetch_add(&ls->gs->abort_count, 1); if (_socket->object) { reactor->close(reactor, _socket); } else { _socket->free(); } } // see https://github.com/swoole/swoole-src/issues/5407 // see https://github.com/swoole/swoole-src/issues/5432 void Server::reset_worker_counter(Worker *worker) const { auto value = worker->concurrency; if (value > 0 && sw_atomic_value_cmp_set(&worker->concurrency, value, 0) == value) { sw_atomic_sub_fetch(&gs->concurrency, value); if ((int) gs->concurrency < 0) { gs->concurrency = 0; } } worker->request_count = 0; worker->response_count = 0; worker->dispatch_count = 0; } void Server::abort_worker(Worker *worker) const { reset_worker_counter(worker); if (is_base_mode()) { SW_LOOP_N(SW_SESSION_LIST_SIZE) { Session *session = get_session(i); if (session->reactor_id == worker->id) { session->fd = 0; } } } } bool Server::init_network_interface_addr_map() { struct ifaddrs *ifaddr = nullptr; if (getifaddrs(&ifaddr) != 0) { swoole_sys_warning("getifaddrs() failed"); return false; } uint16_t index = 1; for (struct ifaddrs *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if (!ifa->ifa_addr || !(ifa->ifa_flags & IFF_UP)) { continue; } network::Address na{}; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *) ifa->ifa_addr; na.addr.inet_v4.sin_family = AF_INET; na.addr.inet_v4.sin_addr = sin->sin_addr; na.type = SW_SOCK_TCP; local_addr_v4_map.emplace(index++, na); } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) ifa->ifa_addr; na.addr.inet_v6.sin6_family = AF_INET6; na.addr.inet_v6.sin6_addr = sin6->sin6_addr; na.type = SW_SOCK_TCP6; local_addr_v6_map.emplace(index++, na); } } freeifaddrs(ifaddr); return true; } uint16_t Server::get_local_addr_index(network::Address *addr) { if (addr->type == SW_SOCK_TCP) { for (auto kv : local_addr_v4_map) { if (memcmp(addr->addr_v4(), kv.second.addr_v4(), sizeof(*addr->addr_v4())) == 0) { return kv.first; } } } else { for (auto kv : local_addr_v6_map) { if (memcmp(addr->addr_v6(), kv.second.addr_v6(), sizeof(*addr->addr_v6())) == 0) { return kv.first; } } } return 0; } const char *Server::get_local_addr(Connection *conn) { if (conn->socket_type == SW_SOCK_TCP) { auto iter = local_addr_v4_map.find(conn->local_addr_index); if (iter != local_addr_v4_map.end()) { return iter->second.get_addr(); } else { return "127.0.0.1"; } } else if (conn->socket_type == SW_SOCK_TCP6) { auto iter = local_addr_v6_map.find(conn->local_addr_index); if (iter != local_addr_v6_map.end()) { return iter->second.get_addr(); } else { return "::1"; } } else { return get_port_by_server_fd(conn->server_fd)->host.c_str(); } } const char *Server::get_remote_addr(Connection *conn) { return conn->info.get_addr(); } /** * new connection */ Connection *Server::add_connection(const ListenPort *ls, Socket *_socket, int server_fd) { int fd = _socket->fd; Connection *connection = &(connection_list[fd]); ReactorId reactor_id = is_base_mode() ? swoole_get_worker_id() : fd % reactor_num; *connection = {}; sw_spinlock(&gs->spinlock); SessionId session_id = gs->session_round; // get session id SW_LOOP_N(max_connection) { Session *session = get_session(++session_id); // available slot if (session->fd == 0) { session->fd = fd; session->id = session_id; session->reactor_id = reactor_id; goto _find_available_slot; } } sw_spinlock_release(&gs->spinlock); swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_TOO_MANY_SOCKET, "no available session slot, fd=%d", fd); return nullptr; _find_available_slot: sw_spinlock_release(&gs->spinlock); gs->session_round = session_id; connection->session_id = session_id; _socket->object = connection; _socket->removed = 1; _socket->buffer_size = ls->socket_buffer_size; _socket->write_timeout = _socket->read_timeout = 0; // TCP Nodelay if (ls->open_tcp_nodelay && ls->socket->is_tcp()) { if (!_socket->set_tcp_nodelay()) { swoole_sys_warning("setsockopt(TCP_NODELAY) failed"); } _socket->enable_tcp_nodelay = true; } // socket recv buffer size if (ls->kernel_socket_recv_buffer_size > 0) { if (ls->socket->set_option(SOL_SOCKET, SO_RCVBUF, ls->kernel_socket_recv_buffer_size) != 0) { swoole_sys_warning("setsockopt(SO_RCVBUF, %d) failed", ls->kernel_socket_recv_buffer_size); } } // socket send buffer size if (ls->kernel_socket_send_buffer_size > 0) { if (ls->socket->set_option(SOL_SOCKET, SO_SNDBUF, ls->kernel_socket_send_buffer_size) != 0) { swoole_sys_warning("setsockopt(SO_SNDBUF, %d) failed", ls->kernel_socket_send_buffer_size); } } connection->fd = fd; connection->reactor_id = reactor_id; connection->server_fd = server_fd; connection->last_recv_time = connection->connect_time = microtime(); connection->active = 1; connection->worker_id = -1; connection->socket_type = ls->type; connection->socket = _socket; connection->ssl = _socket->ssl != nullptr; memcpy(&connection->info.addr, &_socket->info.addr, _socket->info.len); connection->info.len = _socket->info.len; connection->info.type = connection->socket_type; connection->socket->get_name(); connection->local_port = connection->socket->info.get_port(); connection->local_addr_index = get_local_addr_index(&connection->socket->info); if (!ls->ssl) { _socket->direct_send = 1; } lock(); if (fd > get_maxfd()) { set_maxfd(fd); } else if (fd < get_minfd()) { set_minfd(fd); } unlock(); gs->accept_count++; ls->gs->accept_count++; if (is_base_mode()) { sw_atomic_fetch_add(&gs->connection_nums[reactor_id], 1); sw_atomic_fetch_add(&ls->gs->connection_nums[reactor_id], 1); } else { sw_atomic_fetch_add(&gs->connection_num, 1); sw_atomic_fetch_add(&ls->gs->connection_num, 1); } return connection; } void Server::init_ipc_max_size() { ipc_max_size = SW_IPC_BUFFER_MAX_SIZE; } void Server::init_pipe_sockets(MessageBus *mb) const { assert(is_started()); size_t n = get_core_worker_num(); SW_LOOP_N(n) { const auto worker = get_worker(i); if (i >= worker_num && task_ipc_mode != TASK_IPC_UNIXSOCK) { continue; } mb->init_pipe_socket(worker->pipe_master); mb->init_pipe_socket(worker->pipe_worker); } } /** * allocate memory for Server::pipe_buffers */ int Server::create_pipe_buffers() { message_bus.set_buffer_size(ipc_max_size); return message_bus.alloc_buffer() ? SW_OK : SW_ERR; } void Server::release_pipe_buffers() { message_bus.free_buffer(); } uint32_t Server::get_idle_worker_num() const { uint32_t idle_worker_num = 0; for (uint32_t i = 0; i < worker_num; i++) { Worker *worker = get_worker(i); if (worker->is_idle()) { idle_worker_num++; } } return idle_worker_num; } int Server::get_idle_task_worker_num() const { uint32_t idle_worker_num = 0; for (uint32_t i = worker_num; i < (worker_num + task_worker_num); i++) { const Worker *worker = get_worker(i); if (worker->is_idle()) { idle_worker_num++; } } return idle_worker_num; } int Server::get_tasking_num() const { // TODO Why need to reset ? int tasking_num = gs->tasking_num; if (tasking_num < 0) { tasking_num = gs->tasking_num = 0; } return tasking_num; } } // namespace swoole ================================================ FILE: src/server/port.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_http.h" #include "swoole_http2.h" #include "swoole_websocket.h" #include "swoole_client.h" #include "swoole_mqtt.h" #include "swoole_redis.h" #include "swoole_ssl.h" using swoole::http_server::Request; using swoole::network::Address; using swoole::network::Socket; namespace swoole { ListenPort::ListenPort(Server *server) { object_id = 0; protocol.package_length_type = 'N'; protocol.package_length_size = 4; protocol.package_body_offset = 4; protocol.package_max_length = SW_INPUT_BUFFER_SIZE; protocol.package_eof_len = sizeof(SW_DATA_EOF) - 1; memcpy(protocol.package_eof, SW_DATA_EOF, protocol.package_eof_len); protocol.private_data_2 = server; } bool ListenPort::ssl_add_sni_cert(const std::string &name, const std::shared_ptr &ctx) { if (!ssl_context_create(ctx.get())) { return false; } sni_contexts.emplace(name, ctx); return true; } bool ListenPort::ssl_matches_wildcard_name(const char *subject_name, const char *cert_name) { const char *wildcard = nullptr; if (strcasecmp(subject_name, cert_name) == 0) { return true; } /* wildcard, if present, must only be present in the left-most component */ if (!((wildcard = strchr(cert_name, '*'))) || memchr(cert_name, '.', wildcard - cert_name)) { return false; } /* 1) prefix, if not empty, must match subject */ ptrdiff_t prefix_len = wildcard - cert_name; if (prefix_len && strncasecmp(subject_name, cert_name, prefix_len) != 0) { return false; } size_t suffix_len = strlen(wildcard + 1); size_t subject_len = strlen(subject_name); if (suffix_len <= subject_len) { /* 2) suffix must match * 3) no . between prefix and suffix **/ return strcasecmp(wildcard + 1, subject_name + subject_len - suffix_len) == 0 && memchr(subject_name + prefix_len, '.', subject_len - suffix_len - prefix_len) == nullptr; } return false; } int ListenPort::ssl_server_sni_callback(SSL *ssl, int *al, void *arg) { const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!server_name) { return SSL_TLSEXT_ERR_NOACK; } auto *port = static_cast(SSL_get_ex_data(ssl, swoole_ssl_get_ex_port_index())); if (port->sni_contexts.empty()) { return SSL_TLSEXT_ERR_NOACK; } for (auto &sni_context : port->sni_contexts) { if (ssl_matches_wildcard_name(server_name, sni_context.first.c_str())) { SSL_set_SSL_CTX(ssl, sni_context.second->get_context()); return SSL_TLSEXT_ERR_OK; } } return SSL_TLSEXT_ERR_NOACK; } #ifdef SW_SUPPORT_DTLS dtls::Session *ListenPort::create_dtls_session(Socket *sock) const { auto *session = new dtls::Session(sock, ssl_context); if (!session->init()) { delete session; return nullptr; } dtls_sessions->emplace(sock->get_fd(), session); return session; } #endif bool ListenPort::ssl_context_init() { ssl_context = std::make_shared(); ssl_context->prefer_server_ciphers = 1; ssl_context->session_tickets = 0; ssl_context->stapling = 1; ssl_context->stapling_verify = 1; ssl_context->ciphers = SW_SSL_CIPHER_LIST; ssl_context->ecdh_curve = SW_SSL_ECDH_CURVE; if (is_dgram()) { #ifdef SW_SUPPORT_DTLS ssl_context->protocols = SW_SSL_DTLS; dtls_sessions = new std::unordered_map; #else swoole_warning("DTLS support require openssl-1.1 or later"); return false; #endif } return true; } bool ListenPort::ssl_init() const { if (!ssl_context_create(ssl_context.get())) { return false; } if (!sni_contexts.empty()) { SSL_CTX_set_tlsext_servername_callback(ssl_context->get_context(), ssl_server_sni_callback); } return true; } bool ListenPort::ssl_create(Socket *sock) { if (sock->ssl_create(ssl_context.get(), SW_SSL_SERVER) < 0) { swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } if (SSL_set_ex_data(sock->ssl, swoole_ssl_get_ex_port_index(), this) == 0) { swoole_warning("SSL_set_ex_data() failed"); return false; } return true; } bool ListenPort::ssl_context_create(SSLContext *context) const { if (context->cert_file.empty() || context->key_file.empty()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "require `ssl_cert_file` and `ssl_key_file` options"); return false; } if (open_http_protocol) { context->http = 1; } if (open_http2_protocol) { context->http_v2 = 1; } if (!context->create()) { swoole_warning("failed to create ssl content"); return false; } return true; } int ListenPort::listen() { // listen stream socket if (!listening && socket->listen(backlog) < 0) { swoole_sys_warning("listen(%s:%d, %d) failed", host.c_str(), port, backlog); return SW_ERR; } listening = true; #ifdef TCP_DEFER_ACCEPT if (tcp_defer_accept) { if (socket->set_option(IPPROTO_TCP, TCP_DEFER_ACCEPT, tcp_defer_accept) != 0) { swoole_sys_warning("setsockopt(TCP_DEFER_ACCEPT) failed"); } } #endif #ifdef SO_ACCEPTFILTER if (tcp_defer_accept) { struct accept_filter_arg a; memset(&a, 0, sizeof(a)); strcpy(a.af_name, "httpready"); if (socket->set_option(SOL_SOCKET, SO_ACCEPTFILTER, &a, sizeof(a)) != 0) { swoole_sys_warning("setsockopt(SO_ACCEPTFILTER) failed"); } } #endif #ifdef TCP_FASTOPEN if (tcp_fastopen) { if (socket->set_option(IPPROTO_TCP, TCP_FASTOPEN, tcp_fastopen) != 0) { swoole_sys_warning("setsockopt(TCP_FASTOPEN) failed"); } } #endif #ifdef SO_KEEPALIVE if (open_tcp_keepalive == 1) { if (socket->set_option(SOL_SOCKET, SO_KEEPALIVE, 1) != 0) { swoole_sys_warning("setsockopt(SO_KEEPALIVE) failed"); } #ifdef TCP_KEEPIDLE if (socket->set_option(IPPROTO_TCP, TCP_KEEPIDLE, tcp_keepidle) < 0) { swoole_sys_warning("setsockopt(TCP_KEEPIDLE) failed"); } if (socket->set_option(IPPROTO_TCP, TCP_KEEPINTVL, tcp_keepinterval) < 0) { swoole_sys_warning("setsockopt(TCP_KEEPINTVL) failed"); } if (socket->set_option(IPPROTO_TCP, TCP_KEEPCNT, tcp_keepcount) < 0) { swoole_sys_warning("setsockopt(TCP_KEEPCNT) failed"); } #endif #ifdef TCP_USER_TIMEOUT if (tcp_user_timeout > 0 && socket->set_option(IPPROTO_TCP, TCP_USER_TIMEOUT, tcp_user_timeout) != 0) { swoole_sys_warning("setsockopt(TCP_USER_TIMEOUT) failed"); } #endif } #endif if (buffer_high_watermark == 0) { buffer_high_watermark = socket_buffer_size * 0.8; } return SW_OK; } void ListenPort::init_protocol() { if (is_dgram() && !is_dtls()) { return; } if (open_eof_check) { if (protocol.package_eof_len > SW_DATA_EOF_MAXLEN) { protocol.package_eof_len = SW_DATA_EOF_MAXLEN; } protocol.onPackage = Server::dispatch_task; onRead = readable_callback_eof; } else if (open_length_check) { if (protocol.package_length_type != '\0') { protocol.get_package_length = Protocol::default_length_func; } protocol.onPackage = Server::dispatch_task; onRead = readable_callback_length; } else if (open_http_protocol) { if (open_http2_protocol && open_websocket_protocol) { protocol.get_package_length = http_server::get_package_length; protocol.get_package_length_size = http_server::get_package_length_size; protocol.onPackage = http_server::dispatch_frame; } else if (open_http2_protocol) { protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; protocol.get_package_length = http2::get_frame_length; protocol.onPackage = Server::dispatch_task; } else if (open_websocket_protocol) { protocol.package_length_size = SW_WEBSOCKET_FRAME_HEADER_SIZE; protocol.get_package_length = websocket::get_package_length; protocol.onPackage = websocket::dispatch_frame; } protocol.package_length_offset = 0; protocol.package_body_offset = 0; onRead = readable_callback_http; } else if (open_mqtt_protocol) { mqtt::set_protocol(&protocol); protocol.onPackage = Server::dispatch_task; onRead = readable_callback_length; } else if (open_redis_protocol) { protocol.onPackage = Server::dispatch_task; onRead = readable_callback_redis; } else { onRead = readable_callback_raw; } } void ListenPort::set_eof_protocol(const std::string &eof, bool find_from_right) { open_eof_check = true; protocol.split_by_eof = !find_from_right; protocol.package_eof_len = std::min(eof.length(), sizeof(protocol.package_eof)); memcpy(protocol.package_eof, eof.c_str(), protocol.package_eof_len); } void ListenPort::set_length_protocol(uint32_t length_offset, char length_type, uint32_t body_offset) { open_length_check = true; protocol.package_length_type = length_type; protocol.package_length_size = swoole_type_size(length_type); protocol.package_length_offset = length_offset; protocol.package_body_offset = body_offset; } void ListenPort::set_stream_protocol() { open_length_check = true; network::Stream::set_protocol(&protocol); } /** * @description: import listen port from socket-fd */ bool ListenPort::import(int sock) { int _type; auto tmp_sock = socket = new Socket(); tmp_sock->fd = sock; // get socket type if (socket->get_option(SOL_SOCKET, SO_TYPE, &_type) < 0) { swoole_sys_warning("getsockopt(%d, SOL_SOCKET, SO_TYPE) failed", sock); _fail: tmp_sock->move_fd(); delete tmp_sock; return false; } if (tmp_sock->get_name() < 0) { swoole_sys_warning("getsockname(%d) failed", sock); goto _fail; } int optval; if (tmp_sock->get_option(SOL_SOCKET, SO_ACCEPTCONN, &optval) < 0) { swoole_sys_warning("getsockopt(%d, SOL_SOCKET, SO_ACCEPTCONN) failed", sock); goto _fail; } if (optval == 0) { swoole_error_log(SW_LOG_WARNING, EINVAL, "the socket[%d] is not a listening socket", sock); goto _fail; } socket = tmp_sock; int _family = socket->info.addr.ss.sa_family; socket->socket_type = socket->info.type = type = Socket::convert_to_type(_family, _type); host = socket->info.get_addr(); port = socket->info.get_port(); listening = true; socket->fd_type = socket->is_dgram() ? SW_FD_DGRAM_SERVER : SW_FD_STREAM_SERVER; socket->removed = 1; return true; } void ListenPort::clear_protocol() { open_eof_check = false; open_length_check = false; open_http_protocol = false; open_websocket_protocol = false; open_http2_protocol = false; open_mqtt_protocol = false; open_redis_protocol = false; } int ListenPort::readable_callback_raw(Reactor *reactor, ListenPort *port, Event *event) { auto _socket = event->socket; auto conn = static_cast(_socket->object); auto serv = static_cast(reactor->ptr); auto buffer = serv->get_recv_buffer(_socket); RecvData rdata{}; ssize_t n = _socket->recv(buffer->str, buffer->size, 0); if (n < 0) { switch (_socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("recv from connection#%d failed", event->fd); return SW_OK; case SW_CLOSE: conn->close_errno = errno; goto _close_fd; default: return SW_OK; } } else if (n == 0) { _close_fd: reactor->trigger_close_event(event); return SW_OK; } else { buffer->offset = buffer->length = n; rdata.info.len = n; rdata.data = buffer->str; return Server::dispatch_task(&port->protocol, _socket, &rdata); } } int ListenPort::readable_callback_length(Reactor *reactor, ListenPort *port, Event *event) { auto _socket = event->socket; auto conn = static_cast(_socket->object); auto protocol = &port->protocol; auto serv = static_cast(reactor->ptr); auto buffer = serv->get_recv_buffer(_socket); if (protocol->recv_with_length_protocol(_socket, buffer) < 0) { swoole_trace("Close Event.FD=%d|From=%d", event->fd, event->reactor_id); conn->close_errno = errno; reactor->trigger_close_event(event); } /** * if the length is 0, which means the onPackage has been called, we can free the buffer. */ if (_socket->recv_buffer && _socket->recv_buffer->length == 0 && _socket->recv_buffer->size > SW_BUFFER_SIZE_BIG * 2) { delete _socket->recv_buffer; _socket->recv_buffer = nullptr; } return SW_OK; } #define CLIENT_INFO_FMT " from session#%ld on %s:%d" #define CLIENT_INFO_ARGS conn->session_id, port->host.c_str(), port->port /** * For Http Protocol */ int ListenPort::readable_callback_http(Reactor *reactor, ListenPort *port, Event *event) { Socket *_socket = event->socket; auto *conn = static_cast(_socket->object); auto *serv = static_cast(reactor->ptr); RecvData dispatch_data{}; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { if (conn->http_upgrade == 0) { port->destroy_http_request(conn); conn->websocket_status = websocket::STATUS_ACTIVE; conn->http_upgrade = 1; } return readable_callback_length(reactor, port, event); } if (conn->http2_stream) { return readable_callback_length(reactor, port, event); } Request *request = nullptr; Protocol *protocol = &port->protocol; if (conn->object == nullptr) { request = new Request(); conn->object = request; } else { request = static_cast(conn->object); } if (!request->buffer_) { request->buffer_ = serv->get_recv_buffer(_socket); } String *buffer = request->buffer_; _recv_data: ssize_t n = _socket->recv(buffer->str + buffer->length, buffer->size - buffer->length, 0); if (n < 0) { switch (_socket->catch_read_error(errno)) { case SW_ERROR: swoole_sys_warning("recv from connection#%d failed", event->fd); return SW_OK; case SW_CLOSE: conn->close_errno = errno; goto _close_fd; default: return SW_OK; } } if (n == 0) { if (false) { _bad_request: _socket->send(SW_STRL(SW_HTTP_BAD_REQUEST_PACKET), 0); } if (false) { _too_large: _socket->send(SW_STRL(SW_HTTP_REQUEST_ENTITY_TOO_LARGE_PACKET), 0); } if (false) { _unavailable: _socket->send(SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET), 0); } _close_fd: port->destroy_http_request(conn); reactor->trigger_close_event(event); return SW_OK; } buffer->length += n; _parse: if (request->method == 0 && request->get_protocol() < 0) { if (!request->excepted && buffer->length < SW_HTTP_HEADER_MAX_SIZE) { return SW_OK; } swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Bad Request: unknown protocol" CLIENT_INFO_FMT, CLIENT_INFO_ARGS); goto _bad_request; } if (request->method > SW_HTTP_PRI) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Bad Request: unknown HTTP method" CLIENT_INFO_FMT, CLIENT_INFO_ARGS); goto _bad_request; } else if (request->method == SW_HTTP_PRI) { if (sw_unlikely(!port->open_http2_protocol)) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Bad Request: can not handle HTTP2 request" CLIENT_INFO_FMT, CLIENT_INFO_ARGS); goto _bad_request; } conn->http2_stream = 1; http2::send_setting_frame(protocol, _socket); if (buffer->length == sizeof(SW_HTTP2_PRI_STRING) - 1) { port->destroy_http_request(conn); buffer->clear(); return SW_OK; } buffer->reduce(buffer->offset); port->destroy_http_request(conn); conn->socket->skip_recv = 1; return readable_callback_length(reactor, port, event); } // http header is not the end if (request->header_length_ == 0) { if (request->get_header_length() < 0) { if (buffer->size == buffer->length) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Bad Request: request header size is too large" CLIENT_INFO_FMT, CLIENT_INFO_ARGS); goto _bad_request; } goto _recv_data; } } // parse http header and got http body length if (!request->header_parsed) { request->parse_header_info(); request->max_length_ = protocol->package_max_length; swoole_trace_log(SW_TRACE_SERVER, "content-length=%" PRIu64 ", keep-alive=%u, chunked=%u", request->content_length_, request->keep_alive, request->chunked); if (request->form_data_) { if (serv->upload_max_filesize > 0 && request->header_length_ + request->content_length_ > request->max_length_) { request->init_multipart_parser(serv); buffer = request->buffer_; } else { delete request->form_data_; request->form_data_ = nullptr; } } } if (request->form_data_) { if (!request->multipart_header_parsed && memmem(buffer->str, buffer->length, SW_STRL("\r\n\r\n")) == nullptr) { return SW_OK; } if (!request->parse_multipart_data(buffer)) { goto _bad_request; } if (request->too_large) { goto _too_large; } if (request->unavailable) { goto _unavailable; } if (!request->tried_to_dispatch) { return SW_OK; } request->destroy_multipart_parser(); buffer = request->buffer_; } // content length (equal to 0) or (field not found but not chunked) if (!request->tried_to_dispatch) { // recv nobody_chunked eof if (request->nobody_chunked) { if (buffer->length < request->header_length_ + (sizeof(SW_HTTP_CHUNK_EOF) - 1)) { goto _recv_data; } request->header_length_ += (sizeof(SW_HTTP_CHUNK_EOF) - 1); } request->tried_to_dispatch = 1; // (know content-length is equal to 0) or (no content-length field and no chunked) if (request->content_length_ == 0 && (request->known_length || !request->chunked)) { buffer->offset = request->header_length_; // send static file content directly in the reactor thread if (!serv->enable_static_handler || !serv->select_static_handler(request, conn)) { // dynamic request, dispatch to worker dispatch_data.info.len = request->header_length_; dispatch_data.data = buffer->str; if (http_server::dispatch_request(serv, protocol, _socket, &dispatch_data) < 0) { goto _close_fd; } } if (!conn->active || _socket->removed) { return SW_OK; } if (buffer->length > request->header_length_) { // http pipeline, multi requests, parse the next one buffer->reduce(request->header_length_); request->clean(); goto _parse; } else { port->destroy_http_request(conn); buffer->clear(); return SW_OK; } } } size_t request_length; if (request->chunked) { /* unknown length, should find chunked eof */ if (request->get_chunked_body_length() < 0) { if (request->excepted) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Bad Request: protocol error when parse chunked length" CLIENT_INFO_FMT, CLIENT_INFO_ARGS); goto _bad_request; } request_length = buffer->size + SW_BUFFER_SIZE_BIG; if (request_length > protocol->package_max_length) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP_INVALID_PROTOCOL, "Request Entity Too Large: request length (chunked) has already been greater than the " "package_max_length(%u)" CLIENT_INFO_FMT, protocol->package_max_length, CLIENT_INFO_ARGS); goto _too_large; } if (buffer->length == buffer->size) { buffer->extend(request_length); } goto _recv_data; } else { request_length = request->header_length_ + request->content_length_; } swoole_trace_log( SW_TRACE_SERVER, "received chunked eof, real content-length=%" PRIu64, request->content_length_); } else { request_length = request->header_length_ + request->content_length_; if (request_length > protocol->package_max_length) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP_INVALID_PROTOCOL, "Request Entity Too Large: header-length (%u) + content-length (%" PRIu64 ") is greater than the " "package_max_length(%u)" CLIENT_INFO_FMT, request->header_length_, request->content_length_, protocol->package_max_length, CLIENT_INFO_ARGS); goto _too_large; } if (request_length > buffer->size) { buffer->extend(request_length); } if (buffer->length < request_length) { // Expect: 100-continue if (request->has_expect_header()) { _socket->send(SW_STRL(SW_HTTP_100_CONTINUE_PACKET), 0); } else { swoole_trace_log( SW_TRACE_SERVER, "PostWait: request->content_length=%" PRIu64 ", buffer->length=%zu, request->header_length=%d\n", request->content_length_, buffer->length, request->header_length_); } goto _recv_data; } } // discard the redundant data if (buffer->length > request_length) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Invalid Request: %zu bytes has been discard" CLIENT_INFO_FMT, buffer->length - request_length, CLIENT_INFO_ARGS); buffer->length = request_length; } buffer->offset = request_length; dispatch_data.data = buffer->str; dispatch_data.info.len = buffer->length; if (http_server::dispatch_request(serv, protocol, _socket, &dispatch_data) < 0) { goto _close_fd; } if (conn->active && !_socket->removed) { port->destroy_http_request(conn); if (_socket->recv_buffer && _socket->recv_buffer->size > SW_BUFFER_SIZE_BIG * 2) { delete _socket->recv_buffer; _socket->recv_buffer = nullptr; } else { buffer->clear(); } } return SW_OK; } int ListenPort::readable_callback_redis(Reactor *reactor, ListenPort *port, Event *event) { auto _socket = event->socket; auto conn = static_cast(_socket->object); auto protocol = &port->protocol; auto serv = static_cast(reactor->ptr); auto buffer = serv->get_recv_buffer(_socket); if (redis::recv_packet(protocol, conn, buffer) < 0) { conn->close_errno = errno; reactor->trigger_close_event(event); } return SW_OK; } int ListenPort::readable_callback_eof(Reactor *reactor, ListenPort *port, Event *event) { Socket *_socket = event->socket; auto *conn = static_cast(_socket->object); Protocol *protocol = &port->protocol; auto *serv = static_cast(reactor->ptr); String *buffer = serv->get_recv_buffer(_socket); if (!buffer) { reactor->trigger_close_event(event); return SW_ERR; } if (protocol->recv_with_eof_protocol(_socket, buffer) < 0) { conn->close_errno = errno; reactor->trigger_close_event(event); } // If the length is 0, which means the onPackage has been called, we can free the buffer. if (_socket->recv_buffer && _socket->recv_buffer->length == 0 && _socket->recv_buffer->size > SW_BUFFER_SIZE_BIG * 2) { delete _socket->recv_buffer; _socket->recv_buffer = nullptr; } return SW_OK; } void ListenPort::close() { if (ssl) { if (ssl_context) { ssl_context.reset(); } #ifdef SW_SUPPORT_DTLS delete dtls_sessions; #endif } if (socket) { socket->free(); socket = nullptr; } // remove unix socket file if (type == SW_SOCK_UNIX_STREAM || type == SW_SOCK_UNIX_DGRAM) { unlink(host.c_str()); } } const char *ListenPort::get_protocols() const { if (is_dgram()) { return "dgram"; } if (open_eof_check) { return "eof"; } else if (open_length_check) { return "length"; } else if (open_http_protocol) { if (open_http2_protocol && open_websocket_protocol) { return "http|http2|websocket"; } else if (open_http2_protocol) { return "http|http2"; } else if (open_websocket_protocol) { return "http|websocket"; } else { return "http"; } } else if (open_mqtt_protocol) { return "mqtt"; } else if (open_redis_protocol) { return "redis"; } else { return "raw"; } } size_t ListenPort::get_connection_num() const { if (gs->connection_nums) { size_t num = 0; for (uint32_t i = 0; i < sw_server()->worker_num; i++) { num += gs->connection_nums[i]; } return num; } else { return gs->connection_num; } } int ListenPort::create_socket() { auto *server = static_cast(protocol.private_data_2); if (socket) { #if defined(__linux__) && defined(HAVE_REUSEPORT) if (server->enable_reuse_port) { close_socket(); } else #endif { return SW_OK; } } socket = make_socket(type, is_dgram() ? SW_FD_DGRAM_SERVER : SW_FD_STREAM_SERVER, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); if (socket == nullptr) { swoole_set_last_error(errno); return SW_ERR; } #if defined(SW_SUPPORT_DTLS) && defined(HAVE_KQUEUE) if (is_dtls()) { socket->set_reuse_port(); } #endif #if defined(__linux__) && defined(HAVE_REUSEPORT) if (server->enable_reuse_port) { if (socket->set_reuse_port() < 0) { goto __cleanup; } } #endif Address addr; if (!addr.assign(type, host, port, true)) { auto type_str = Address::type_str(type); swoole_warning("Invalid %s address '%s:%d'", type_str, host.c_str(), port); goto __cleanup; } if (socket->set_reuse_addr() < 0) { swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", socket->get_fd()); } if (socket->bind(addr) < 0) { goto __cleanup; } if (socket->get_name() < 0) { __cleanup: swoole_set_last_error(errno); socket->free(); return SW_ERR; } port = socket->get_port(); return SW_OK; } void ListenPort::close_socket() { if (::close(socket->fd) < 0) { swoole_sys_warning("close(%d) failed", socket->fd); } delete socket; socket = nullptr; } void ListenPort::destroy_http_request(Connection *conn) { const auto request = static_cast(conn->object); if (!request) { return; } delete request; conn->object = nullptr; } } // namespace swoole ================================================ FILE: src/server/process.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" namespace swoole { using network::Socket; Factory *Server::create_process_factory() { /** * init reactor thread pool */ reactor_threads = new ReactorThread[reactor_num](); /** * alloc the memory for connection_list */ connection_list = static_cast(sw_shm_calloc(max_connection, sizeof(Connection))); if (connection_list == nullptr) { swoole_sys_warning("sw_shm_calloc(%u, %zu) for connection_list failed", max_connection, sizeof(Connection)); return nullptr; } reactor_pipe_num = worker_num / reactor_num; reactor_thread_barrier.init(false, reactor_num + 1); gs->manager_barrier.init(true, 2); return new ProcessFactory(this); } void Server::destroy_process_factory() { sw_shm_free(connection_list); delete[] reactor_threads; reactor_thread_barrier.destroy(); gs->manager_barrier.destroy(); if (get_event_worker_pool()->message_box) { get_event_worker_pool()->message_box->destroy(); } } ProcessFactory::ProcessFactory(Server *server) : Factory(server) {} ProcessFactory::~ProcessFactory() = default; /** * kill and wait all user process */ void Factory::kill_user_workers() const { if (server_->user_worker_map.empty()) { return; } for (const auto &kv : server_->user_worker_map) { swoole_kill(kv.second->pid, SIGTERM); } for (const auto &kv : server_->user_worker_map) { int _stat_loc; if (swoole_waitpid(kv.second->pid, &_stat_loc, 0) < 0) { swoole_sys_warning("waitpid(%d) failed", kv.second->pid); } } } /** * [Manager] kill and wait all event worker process */ void Factory::kill_event_workers() const { int status; if (server_->worker_num == 0) { return; } SW_LOOP_N(server_->worker_num) { swoole_trace_log(SW_TRACE_SERVER, "kill worker#%d[pid=%d]", server_->workers[i].id, server_->workers[i].pid); swoole_kill(server_->workers[i].pid, SIGTERM); } SW_LOOP_N(server_->worker_num) { swoole_trace_log(SW_TRACE_SERVER, "wait worker#%d[pid=%d]", server_->workers[i].id, server_->workers[i].pid); if (swoole_waitpid(server_->workers[i].pid, &status, 0) < 0) { swoole_sys_warning("waitpid(%d) failed", server_->workers[i].pid); } } } /** * [Manager] kill and wait task worker process */ void Factory::kill_task_workers() const { int status; if (server_->task_worker_num == 0) { return; } auto pool = server_->get_task_worker_pool(); pool->kill_all_workers(SIGTERM); SW_LOOP_N(server_->task_worker_num) { swoole_trace_log(SW_TRACE_SERVER, "wait worker#%d[pid=%d]", pool->workers[i].id, pool->workers[i].pid); if (swoole_waitpid(pool->workers[i].pid, &status, 0) < 0) { swoole_sys_warning("waitpid(%d) failed", pool->workers[i].pid); } } } pid_t Factory::spawn_event_worker(Worker *worker) const { pid_t pid = swoole_fork(0); if (pid < 0) { swoole_sys_warning("failed to fork event worker"); return SW_ERR; } else if (pid == 0) { worker->pid = getpid(); swoole_set_worker_id(worker->id); swoole_set_worker_pid(worker->pid); swoole_set_worker_type(SW_EVENT_WORKER); SwooleWG.worker = worker; } else { worker->pid = pid; return pid; } if (server_->is_base_mode()) { server_->get_event_worker_pool()->main_loop(server_->get_event_worker_pool(), worker); } else { server_->start_event_worker(worker); } exit(0); return 0; } pid_t Factory::spawn_user_worker(Worker *worker) const { pid_t pid = swoole_fork(0); if (worker->pid) { server_->user_worker_map.erase(worker->pid); } if (pid < 0) { swoole_sys_warning("failed to spawn the user worker"); return SW_ERR; } // child else if (pid == 0) { worker->pid = getpid(); swoole_set_worker_type(SW_USER_WORKER); swoole_set_worker_id(worker->id); swoole_set_worker_pid(worker->pid); SwooleWG.worker = worker; server_->onUserWorkerStart(server_, worker); exit(0); } // parent else { /** * worker: local memory * user_workers: shared memory */ server_->get_worker(worker->id)->pid = worker->pid = pid; server_->user_worker_map.emplace(pid, worker); return pid; } } pid_t Factory::spawn_task_worker(Worker *worker) const { return server_->get_task_worker_pool()->spawn(worker); } void Factory::check_worker_exit_status(Worker *worker, const ExitStatus &exit_status) const { if (exit_status.get_status() != 0) { worker->report_error(exit_status); server_->call_worker_error_callback(worker, exit_status); } } bool ProcessFactory::shutdown() { int status; if (swoole_kill(server_->gs->manager_pid, SIGTERM) < 0) { swoole_sys_warning("kill(%d) failed", server_->gs->manager_pid); } if (swoole_waitpid(server_->gs->manager_pid, &status, 0) < 0) { swoole_sys_warning("waitpid(%d) failed", server_->gs->manager_pid); } return SW_OK; } bool Server::create_worker_pipes() { SW_LOOP_N(worker_num) { auto _sock = new UnixSocket(true, SOCK_DGRAM); if (!_sock->ready()) { delete _sock; return false; } worker_pipes.emplace_back(_sock); workers[i].pipe_master = _sock->get_socket(true); workers[i].pipe_worker = _sock->get_socket(false); workers[i].pipe_object = _sock; } init_ipc_max_size(); if (create_pipe_buffers() < 0) { return false; } return true; } bool ProcessFactory::start() { if (!server_->create_worker_pipes()) { return false; } return server_->start_manager_process() == SW_OK; } /** * [ReactorThread] notify info to worker process */ bool ProcessFactory::notify(DataHead *ev) { SendData task; task.info = *ev; task.data = nullptr; return dispatch(&task); } /** * [ReactorThread] dispatch request to worker */ bool ProcessFactory::dispatch(SendData *task) { // the task->info.fd is real fd, not session_id, it will be converted to session after dispatch int fd = static_cast(task->info.fd); int target_worker_id = server_->schedule_worker(fd, task); if (target_worker_id < 0) { switch (target_worker_id) { case Server::DISPATCH_RESULT_DISCARD_PACKET: return false; case Server::DISPATCH_RESULT_CLOSE_CONNECTION: // TODO: close connection return false; default: swoole_warning("invalid target worker id[%d]", target_worker_id); return false; } } if (Server::is_stream_event(task->info.type)) { Connection *conn = server_->get_connection(fd); if (conn == nullptr || conn->active == 0) { swoole_warning("dispatch[type=%d] failed, connection#%d is not active", task->info.type, fd); return false; } // server active close, discard data. if (conn->closed) { // Connection has been closed by server if (!(task->info.type == SW_SERVER_EVENT_CLOSE && conn->close_force)) { return false; } } // converted fd to session_id task->info.fd = conn->session_id; task->info.server_fd = conn->server_fd; } Worker *worker = server_->get_worker(target_worker_id); if (task->info.type == SW_SERVER_EVENT_RECV_DATA) { sw_atomic_fetch_add(&worker->dispatch_count, 1); } SendData _task; memcpy(&_task, task, sizeof(SendData)); MessageBus *mb = &server_->get_thread(swoole_get_thread_id())->message_bus; Socket *sock = mb->get_pipe_socket(worker->pipe_master); return mb->write(sock, &_task); } static bool process_is_supported_send_yield(Server *serv, const Connection *conn) { if (!serv->is_hash_dispatch_mode()) { return false; } else { return serv->schedule_worker(conn->fd, nullptr) == static_cast(swoole_get_worker_id()); } } /** * [Worker] send to client, proxy by reactor */ bool ProcessFactory::finish(SendData *resp) { /** * More than the output buffer */ if (resp->info.len > server_->output_buffer_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_DATA_LENGTH_TOO_LARGE, "The length of data [%u] exceeds the output buffer size[%u], " "please use the sendfile, chunked transfer mode or adjust the output_buffer_size", resp->info.len, server_->output_buffer_size); return false; } SessionId session_id = resp->info.fd; Connection *conn; if (resp->info.type != SW_SERVER_EVENT_CLOSE) { conn = server_->get_connection_verify(session_id); } else { conn = server_->get_connection_verify_no_ssl(session_id); } if (!conn) { if (resp->info.type != SW_SERVER_EVENT_CLOSE) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session#%ld does not exists", session_id); } return false; } else if ((conn->closed || conn->peer_closed) && resp->info.type != SW_SERVER_EVENT_CLOSE) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_CLOSED, "send %d bytes failed, because session#%ld is closed", resp->info.len, session_id); return false; } else if (conn->overflow && (resp->info.type == SW_SERVER_EVENT_SEND_DATA || resp->info.type == SW_SERVER_EVENT_SEND_FILE)) { if (server_->send_yield && process_is_supported_send_yield(server_, conn)) { swoole_set_last_error(SW_ERROR_OUTPUT_SEND_YIELD); } else { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "send failed, session=%ld output buffer overflow", session_id); } return false; } SendData task; memcpy(&task, resp, sizeof(SendData)); task.info.fd = session_id; task.info.reactor_id = conn->reactor_id; task.info.server_fd = swoole_get_worker_id(); swoole_trace("worker_id=%d, type=%d", task.info.server_fd, task.info.type); return server_->message_bus.write(server_->get_reactor_pipe_socket(session_id, task.info.reactor_id), &task); } bool ProcessFactory::end(SessionId session_id, int flags) { SendData _send{}; DataHead info{}; _send.info.fd = session_id; _send.info.len = 0; _send.info.type = SW_SERVER_EVENT_CLOSE; Connection *conn = server_->get_connection_verify_no_ssl(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session#%ld does not exists", session_id); return false; } // Reset send buffer, Immediately close the connection. if (flags & Server::CLOSE_RESET) { conn->close_reset = 1; } // Server is initiative to close the connection if (flags & Server::CLOSE_ACTIVELY) { conn->close_actively = 1; } swoole_trace_log(SW_TRACE_CLOSE, "session_id=%ld, fd=%d", session_id, conn->fd); DataHead ev = {}; /** * Only close actively needs to determine whether it is in the process of connection binding. * If the worker process is not currently bound to this connection, * MUST forward to the correct worker process */ if (conn->close_actively) { Worker *worker; bool hash = server_->is_hash_dispatch_mode(); int worker_id = hash ? server_->schedule_worker(conn->fd, nullptr) : conn->fd % server_->worker_num; if (server_->is_worker() && (!hash || worker_id == (int) swoole_get_worker_id())) { goto _close; } worker = server_->get_worker(worker_id); ev.type = SW_SERVER_EVENT_CLOSE; ev.fd = session_id; ev.reactor_id = conn->reactor_id; return server_->send_to_worker_from_worker(worker, &ev, sizeof(ev), SW_PIPE_MASTER) > 0; } _close: if (conn->closing) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSING, "session#%ld is closing", session_id); return false; } else if (!(conn->close_force || conn->close_reset) && conn->closed) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_CLOSED, "session#%ld is closed", session_id); return false; } if (server_->onClose != nullptr && !conn->closed) { info.fd = session_id; if (conn->close_actively) { info.reactor_id = -1; } else { info.reactor_id = conn->reactor_id; } info.server_fd = conn->server_fd; conn->closing = 1; server_->onClose(server_, &info); conn->closing = 0; } conn->closed = 1; conn->close_errno = 0; return finish(&_send); } } // namespace swoole ================================================ FILE: src/server/reactor_process.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" namespace swoole { using network::Socket; static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event); static int ReactorProcess_onClose(Reactor *reactor, Event *event); static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode); int Server::start_reactor_processes() { single_thread = true; // listen TCP if (have_stream_sock == 1) { for (auto ls : ports) { if (ls->is_stream()) { #if defined(__linux__) && defined(HAVE_REUSEPORT) if (!enable_reuse_port) { #endif // listen server socket if (ls->listen() < 0) { return SW_ERR; } #if defined(__linux__) && defined(HAVE_REUSEPORT) } else { ls->close_socket(); } #endif } } } ProcessPool *pool = get_event_worker_pool(); *pool = {}; if (pool->create(worker_num, 0, SW_IPC_UNIXSOCK) < 0) { return SW_ERR; } pool->set_max_request(max_request, max_request_grace); /** * store to ProcessPool object */ pool->ptr = this; pool->max_wait_time = max_wait_time; pool->use_msgqueue = 0; pool->main_loop = reactor_process_main_loop; pool->onWorkerNotFound = wait_other_worker; memcpy(workers, pool->workers, sizeof(*workers) * worker_num); pool->workers = workers; SW_LOOP_N(worker_num) { pool->workers[i].pool = pool; pool->workers[i].id = i; pool->workers[i].type = SW_WORKER; } init_ipc_max_size(); if (create_pipe_buffers() < 0) { return SW_ERR; } if (is_single_worker()) { Worker *worker = &pool->workers[0]; SwooleWG.worker = worker; int retval = reactor_process_main_loop(pool, worker); if (retval == SW_OK) { pool->destroy(); } return retval; } return start_manager_process(); } static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event) { SendData _send; auto *serv = static_cast(reactor->ptr); auto *factory = serv->factory_; auto *pipe_buffer = serv->message_bus.get_buffer(); auto *worker = serv->get_worker(reactor->id); ssize_t retval = serv->message_bus.read(event->socket); if (retval <= 0) { return SW_OK; } switch (pipe_buffer->info.type) { case SW_SERVER_EVENT_PIPE_MESSAGE: { serv->onPipeMessage(serv, reinterpret_cast(pipe_buffer)); break; } case SW_SERVER_EVENT_FINISH: { serv->onFinish(serv, reinterpret_cast(pipe_buffer)); break; } case SW_SERVER_EVENT_SHUTDOWN: { serv->stop_async_worker(worker); break; } case SW_SERVER_EVENT_SEND_FILE: { _send.info = pipe_buffer->info; _send.data = pipe_buffer->data; factory->finish(&_send); break; } case SW_SERVER_EVENT_SEND_DATA: { if (pipe_buffer->info.reactor_id < 0 || pipe_buffer->info.reactor_id >= (int16_t) serv->get_all_worker_num()) { swoole_warning("invalid worker_id=%d", pipe_buffer->info.reactor_id); return SW_OK; } auto packet = serv->message_bus.get_packet(); memcpy(&_send.info, &pipe_buffer->info, sizeof(_send.info)); _send.info.type = SW_SERVER_EVENT_RECV_DATA; _send.data = packet.data; _send.info.len = packet.length; factory->finish(&_send); break; } case SW_SERVER_EVENT_CLOSE: case SW_SERVER_EVENT_CLOSE_FORWARD: { factory->end(pipe_buffer->info.fd, Server::CLOSE_ACTIVELY); break; } case SW_SERVER_EVENT_COMMAND_REQUEST: { serv->call_command_handler(serv->message_bus, sw_worker()->id, serv->get_worker(0)->pipe_master); break; } case SW_SERVER_EVENT_COMMAND_RESPONSE: { int64_t request_id = pipe_buffer->info.fd; auto packet = serv->message_bus.get_packet(); serv->call_command_callback(request_id, std::string(packet.data, packet.length)); break; } default: break; } serv->message_bus.pop(); return SW_OK; } int Server::reactor_process_main_loop(ProcessPool *pool, Worker *worker) { auto *serv = static_cast(pool->ptr); swoole_set_worker_type(SW_EVENT_WORKER); swoole_set_worker_id(worker->id); swoole_set_worker_pid(getpid()); serv->init_event_worker(worker); if (!SwooleTG.reactor) { if (swoole_event_init(0) < 0) { return SW_ERR; } } Reactor *reactor = SwooleTG.reactor; if (SwooleTG.timer && SwooleTG.timer->get_reactor() == nullptr) { SwooleTG.timer->reinit(); } serv->worker_signal_init(); serv->gs->connection_nums[worker->id] = 0; for (auto ls : serv->ports) { #if defined(__linux__) and defined(HAVE_REUSEPORT) if (ls->is_stream() && serv->enable_reuse_port) { if (ls->create_socket() < 0) { swoole_event_free(); return SW_ERR; } if (ls->listen() < 0) { return SW_ERR; } } #endif ls->gs->connection_nums[worker->id] = 0; if (reactor->add(ls->socket, SW_EVENT_READ) < 0) { return SW_ERR; } } reactor->id = worker->id; reactor->ptr = serv; reactor->max_socket = serv->get_max_connection(); reactor->close = close_connection; // set event handler // connect reactor->set_handler(SW_FD_STREAM_SERVER, SW_EVENT_READ, accept_connection); // close reactor->default_error_handler = ReactorProcess_onClose; // pipe reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, ReactorProcess_onPipeRead); serv->store_listen_socket(); if (worker->pipe_worker) { worker->pipe_worker->set_nonblock(); worker->pipe_master->set_nonblock(); if (reactor->add(worker->pipe_worker, SW_EVENT_READ) < 0) { return SW_ERR; } if (reactor->add(worker->pipe_master, SW_EVENT_READ) < 0) { return SW_ERR; } } // task workers if (serv->task_worker_num > 0) { if (serv->task_ipc_mode == Server::TASK_IPC_UNIXSOCK) { SW_LOOP_N(serv->get_task_worker_pool()->worker_num) { serv->get_task_worker_pool()->workers[i].pipe_master->set_nonblock(); } } } serv->init_reactor(reactor); if (worker->id == 0) { serv->gs->master_pid = getpid(); if (serv->onStart && !serv->gs->onstart_called) { serv->gs->onstart_called = true; serv->onStart(serv); } } if ((serv->master_timer = swoole_timer_add(1000L, true, timer_callback, serv)) == nullptr) { _fail: swoole_event_free(); return SW_ERR; } serv->worker_start_callback(worker); /** * for heartbeat check */ if (serv->heartbeat_check_interval > 0) { serv->heartbeat_timer = swoole_timer_add(sec2msec(serv->heartbeat_check_interval), true, ReactorProcess_onTimeout, reactor); if (serv->heartbeat_timer == nullptr) { goto _fail; } } int retval = reactor->wait(); /** * Close all connections */ serv->foreach_connection([serv](Connection *conn) { serv->close(conn->session_id, true); }); /** * call internal serv hooks */ if (serv->isset_hook(HOOK_WORKER_CLOSE)) { void *hook_args[2]; hook_args[0] = serv; hook_args[1] = (void *) (uintptr_t) worker->id; serv->call_hook(HOOK_WORKER_CLOSE, hook_args); } swoole_event_free(); serv->worker_stop_callback(worker); return retval; } static int ReactorProcess_onClose(Reactor *reactor, Event *event) { int fd = event->fd; auto *serv = (Server *) reactor->ptr; Connection *conn = serv->get_connection(fd); if (conn == nullptr || conn->active == 0) { return SW_ERR; } if (event->socket->removed) { return Server::close_connection(reactor, event->socket); } if (reactor->del(event->socket) == 0) { if (conn->close_queued) { return Server::close_connection(reactor, event->socket); } else { /** * peer_closed indicates that the client has closed the connection * and the connection is no longer available. */ conn->peer_closed = 1; return serv->notify(conn, SW_SERVER_EVENT_CLOSE) ? SW_OK : SW_ERR; } } else { return SW_ERR; } } static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode) { auto *reactor = static_cast(tnode->data); auto *serv = static_cast(reactor->ptr); Event notify_ev{}; double now = microtime(); notify_ev.type = SW_FD_SESSION; serv->foreach_connection([serv, reactor, now, ¬ify_ev](Connection *conn) { if (serv->is_healthy_connection(now, conn)) { return; } if (conn->socket->ssl && conn->socket->ssl_state != SW_SSL_STATE_READY) { Server::close_connection(reactor, conn->socket); return; } if (serv->disable_notify || conn->close_force) { Server::close_connection(reactor, conn->socket); return; } conn->close_force = 1; notify_ev.fd = conn->fd; notify_ev.socket = conn->socket; notify_ev.reactor_id = conn->reactor_id; ReactorProcess_onClose(reactor, ¬ify_ev); }); } } // namespace swoole ================================================ FILE: src/server/reactor_thread.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_hash.h" #include "swoole_util.h" #include using std::unordered_map; namespace swoole { using namespace network; static int ReactorThread_onPipeWrite(Reactor *reactor, Event *ev); static int ReactorThread_onPipeRead(Reactor *reactor, Event *ev); static int ReactorThread_onRead(Reactor *reactor, Event *ev); static int ReactorThread_onWrite(Reactor *reactor, Event *ev); static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event); static int ReactorThread_onClose(Reactor *reactor, Event *event); static void ReactorThread_resume_data_receiving(Timer *timer, TimerNode *tnode); static inline ReturnCode ReactorThread_verify_ssl_state(Reactor *reactor, ListenPort *port, Socket *_socket) { auto serv = static_cast(reactor->ptr); if (!_socket->ssl || _socket->ssl_state == SW_SSL_STATE_READY) { return SW_CONTINUE; } ReturnCode code = _socket->ssl_accept(); if (code != SW_READY) { return code; } auto conn = static_cast(_socket->object); conn->ssl_ready = 1; if (!port->get_ssl_client_cert_file().empty()) { if (!_socket->ssl_get_peer_certificate(sw_tg_buffer())) { if (port->get_ssl_verify_peer()) { return SW_ERROR; } } else { if (!port->get_ssl_verify_peer() || _socket->ssl_verify(port->get_ssl_allow_self_signed())) { SendData task; task.info.fd = _socket->fd; task.info.type = SW_SERVER_EVENT_CONNECT; task.info.reactor_id = reactor->id; task.info.len = sw_tg_buffer()->length; task.data = sw_tg_buffer()->str; serv->factory_->dispatch(&task); goto _delay_receive; } else { return SW_ERROR; } } } if (serv->onConnect) { serv->notify(static_cast(_socket->object), SW_SERVER_EVENT_CONNECT); } _delay_receive: if (serv->enable_delay_receive) { if (reactor->del(_socket) < 0) { return SW_ERROR; } } return SW_READY; } /** * for udp */ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { int fd = event->fd; auto serv = static_cast(reactor->ptr); Connection *server_sock = serv->get_connection(fd); Socket *sock = server_sock->socket; SendData task = {}; auto pkt = reinterpret_cast(sw_tg_buffer()->str); task.info.server_fd = fd; task.info.reactor_id = SwooleTG.id; task.info.type = SW_SERVER_EVENT_RECV_DGRAM; task.info.time = microtime(); pkt->socket_addr.type = pkt->socket_type = server_sock->socket_type; _do_recvfrom: ssize_t ret = sock->recvfrom(pkt->data, sw_tg_buffer()->size - sizeof(*pkt), 0, &pkt->socket_addr); if (ret <= 0) { if (errno == EAGAIN) { return SW_OK; } else { swoole_sys_warning("recvfrom(%d) failed", fd); return SW_ERR; } } #ifdef SW_SUPPORT_DTLS auto port = static_cast(server_sock->object); if (port->is_dtls()) { dtls::Session *session = serv->accept_dtls_connection(port, &pkt->socket_addr); if (!session) { return SW_ERR; } session->append(pkt->data, ret); if (!session->listen()) { return Server::close_connection(reactor, session->socket); } auto conn = static_cast(session->socket->object); if (serv->single_thread) { if (serv->connection_incoming(reactor, conn) < 0) { reactor->close(reactor, session->socket); return SW_OK; } } else { DataHead ev{}; ev.type = SW_SERVER_EVENT_INCOMING; ev.fd = conn->session_id; ev.reactor_id = conn->reactor_id; if (serv->send_to_reactor_thread(reinterpret_cast(&ev), sizeof(ev), conn->session_id) < 0) { reactor->close(reactor, session->socket); return SW_OK; } } return SW_OK; } #endif if (pkt->socket_type == SW_SOCK_UDP) { task.info.fd = *reinterpret_cast(&pkt->socket_addr.addr.inet_v4.sin_addr); } else { task.info.fd = swoole_crc32(pkt->socket_addr.get_addr(), pkt->socket_addr.len); } pkt->length = ret; task.info.len = sizeof(*pkt) + ret; task.data = reinterpret_cast(pkt); if (!serv->factory_->dispatch(&task)) { return SW_ERR; } else { goto _do_recvfrom; } } /** * close connection */ int Server::close_connection(Reactor *reactor, Socket *socket) { auto serv = static_cast(reactor->ptr); auto conn = static_cast(socket->object); ListenPort *port = serv->get_port_by_fd(socket->fd); if (conn->timer) { swoole_timer_del(conn->timer); } if (!socket->removed && reactor->del(socket) < 0) { return SW_ERR; } sw_atomic_fetch_add(&serv->gs->close_count, 1); sw_atomic_fetch_add(&port->gs->close_count, 1); if (serv->is_base_mode()) { sw_atomic_fetch_sub(&serv->gs->connection_nums[reactor->id], 1); sw_atomic_fetch_sub(&port->gs->connection_nums[reactor->id], 1); } else { sw_atomic_fetch_sub(&serv->gs->connection_num, 1); sw_atomic_fetch_sub(&port->gs->connection_num, 1); } swoole_trace("Close Event.fd=%d|from=%d", socket->fd, reactor->id); if (socket->ssl) { conn->socket->ssl_quiet_shutdown = conn->peer_closed; conn->socket->ssl_close(); } #ifdef SW_SUPPORT_DTLS if (socket->dtls) { dtls::Session *session = port->dtls_sessions->find(socket->fd)->second; port->dtls_sessions->erase(socket->fd); delete session; } #endif // free the reception memory buffer if (socket->recv_buffer) { delete socket->recv_buffer; socket->recv_buffer = nullptr; } if (port->open_http_protocol && conn->object) { port->destroy_http_request(conn); } if (port->open_redis_protocol && conn->object) { sw_free(conn->object); conn->object = nullptr; } #ifdef SW_USE_SOCKET_LINGER if (conn->close_force || conn->close_reset) { struct linger linger; linger.l_onoff = 1; linger.l_linger = 0; if (conn->socket->set_option(SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger)) != 0) { swoole_sys_warning("setsockopt(SO_LINGER) failed"); } } #endif Session *session = serv->get_session(conn->session_id); session->fd = 0; /** * reset maxfd, for connection_list */ int fd = socket->fd; serv->lock(); if (fd == serv->get_maxfd()) { int find_max_fd = fd - 1; swoole_trace_log(SW_TRACE_SERVER, "set_maxfd=%d|close_fd=%d", find_max_fd, fd); // find the new max_fd for (; !serv->is_valid_connection(serv->get_connection(find_max_fd)) && find_max_fd > serv->get_minfd(); find_max_fd--) { // pass } serv->set_maxfd(find_max_fd); } serv->unlock(); *conn = {}; return Reactor::_close(reactor, socket); } /** * close the connection */ static int ReactorThread_onClose(Reactor *reactor, Event *event) { auto *serv = static_cast(reactor->ptr); int fd = event->fd; DataHead notify_ev{}; Socket *socket = event->socket; assert(fd % serv->reactor_num == reactor->id); assert(fd % serv->reactor_num == SwooleTG.id); notify_ev.reactor_id = reactor->id; notify_ev.fd = fd; notify_ev.type = SW_SERVER_EVENT_CLOSE; swoole_trace_log(SW_TRACE_CLOSE, "client[fd=%d] close the connection", fd); Connection *conn = serv->get_connection(fd); if (conn == nullptr || conn->active == 0) { return SW_ERR; } else if (serv->disable_notify) { Server::close_connection(reactor, socket); return SW_OK; } else if (reactor->del(socket) == 0) { if (conn->close_queued) { Server::close_connection(reactor, socket); return SW_OK; } else { /** * peer_closed indicates that the client has closed the connection * and the connection is no longer available. */ conn->peer_closed = 1; return serv->factory_->notify(¬ify_ev); } } else { return SW_ERR; } } void ReactorThread::shutdown(Reactor *reactor) { auto *serv = static_cast(reactor->ptr); // stop listen UDP Port if (serv->have_dgram_sock == 1) { for (auto ls : serv->ports) { if (ls->is_dgram()) { if (ls->socket->fd % serv->reactor_num != reactor->id) { continue; } if (!ls->socket->removed) { reactor->del(ls->socket); } } } } if (heartbeat_timer) { swoole_timer_del(heartbeat_timer); heartbeat_timer = nullptr; } reactor->add_destroy_callback([serv, reactor](void *) { serv->foreach_connection([reactor](Connection *conn) { if (conn->reactor_id == reactor->id) { Server::close_connection(reactor, conn->socket); } }); }); if (serv->is_thread_mode()) { serv->stop_async_worker(serv->get_worker(reactor->id)); return; } SW_LOOP_N(serv->worker_num) { if (i % serv->reactor_num != reactor->id) { continue; } Socket *socket = message_bus.get_pipe_socket(serv->get_worker_pipe_master(i)); reactor->remove_read_event(socket); } serv->foreach_connection([serv, reactor](Connection *conn) { if (conn->fd % serv->reactor_num != reactor->id) { return; } if (!conn->peer_closed && !conn->socket->removed) { reactor->remove_read_event(conn->socket); } }); reactor->set_wait_exit(true); } int ReactorThread::close_connection(Reactor *reactor, SessionId session_id) { auto *serv = static_cast(reactor->ptr); Connection *conn = serv->get_connection_verify_no_ssl(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "force close connection failed, session#%ld does not exist", session_id); return SW_OK; } if (serv->disable_notify || conn->close_force) { return Server::close_connection(reactor, conn->socket); } /** * SSL connections that have not completed the handshake, * do not need to notify the workers, just close */ if (conn->ssl && !conn->ssl_ready) { return Server::close_connection(reactor, conn->socket); } conn->close_force = 1; Event _ev = {}; _ev.fd = conn->fd; _ev.socket = conn->socket; reactor->trigger_close_event(&_ev); return SW_OK; } /** * receive data from worker process pipe */ static int ReactorThread_onPipeRead(Reactor *reactor, Event *ev) { SendData _send; auto *serv = static_cast(reactor->ptr); ReactorThread *thread = serv->get_thread(reactor->id); SW_LOOP { PipeBuffer *resp = thread->message_bus.get_buffer(); ssize_t n = thread->message_bus.read_with_buffer(ev->socket); if (n <= 0) { return SW_OK; } switch (resp->info.type) { case SW_SERVER_EVENT_INCOMING: { Connection *conn = serv->get_connection_verify_no_ssl(resp->info.fd); if (conn && serv->connection_incoming(reactor, conn) < 0) { reactor->close(reactor, conn->socket); } break; } case SW_SERVER_EVENT_COMMAND_REQUEST: { serv->call_command_handler(thread->message_bus, thread->id, thread->pipe_command); break; } case SW_SERVER_EVENT_COMMAND_RESPONSE: { auto packet = thread->message_bus.get_packet(); serv->call_command_callback(resp->info.fd, std::string(packet.data, packet.length)); break; } case SW_SERVER_EVENT_SHUTDOWN: { thread->shutdown(reactor); break; } case SW_SERVER_EVENT_SHUTDOWN_SIGNAL: { swoole_kill(getpid(), SIGTERM); break; } case SW_SERVER_EVENT_FINISH: { serv->onFinish(serv, reinterpret_cast(resp)); break; } case SW_SERVER_EVENT_PIPE_MESSAGE: { serv->onPipeMessage(serv, reinterpret_cast(resp)); break; } case SW_SERVER_EVENT_CLOSE_FORCE: { thread->close_connection(reactor, resp->info.fd); break; } case SW_SERVER_EVENT_CLOSE_FORWARD: { serv->factory_->end(resp->info.fd, Server::CLOSE_ACTIVELY); break; } default: { PacketPtr packet = thread->message_bus.get_packet(); _send.info = resp->info; _send.info.len = packet.length; _send.data = packet.data; serv->send_to_connection(&_send); break; } } thread->message_bus.pop(); } return SW_OK; } /** * [ReactorThread] worker pipe can write. */ static int ReactorThread_onPipeWrite(Reactor *reactor, Event *ev) { int ret; auto *serv = static_cast(reactor->ptr); Buffer *buffer = ev->socket->out_buffer; while (!Buffer::empty(buffer)) { const BufferChunk *chunk = buffer->front(); const auto *send_data = reinterpret_cast(chunk->value.str); // server actively closed connection, should discard the data if (Server::is_stream_event(send_data->info.type)) { // send_data->info.fd is session_id Connection *conn = serv->get_connection_verify(send_data->info.fd); if (conn) { conn->last_send_time = microtime(); if (conn->closed) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED_BY_SERVER, "Session#%ld is closed by server", send_data->info.fd); _discard: buffer->pop(); continue; } } else if (serv->discard_timeout_request) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SESSION_DISCARD_TIMEOUT_DATA, "[1] ignore data[%u bytes] received from session#%ld", send_data->info.len, send_data->info.fd); goto _discard; } } ret = ev->socket->send(chunk->value.str, chunk->length, 0); if (ret < 0) { return (ev->socket->catch_write_error(errno) == SW_WAIT) ? SW_OK : SW_ERR; } else { buffer->pop(); } } if (Buffer::empty(buffer)) { if (reactor->remove_write_event(ev->socket) < 0) { swoole_sys_warning("reactor->set(%d) failed", ev->fd); } } return SW_OK; } void Server::init_reactor(Reactor *reactor) { // support 64K packet if (have_dgram_sock) { sw_tg_buffer()->extend(); } // UDP Packet reactor->set_handler(SW_FD_DGRAM_SERVER, SW_EVENT_READ, ReactorThread_onPacketReceived); // Write reactor->set_handler(SW_FD_SESSION, SW_EVENT_WRITE, ReactorThread_onWrite); // Read reactor->set_handler(SW_FD_SESSION, SW_EVENT_READ, ReactorThread_onRead); // listen the all tcp port for (auto port : ports) { port->init_protocol(); } } static int ReactorThread_onRead(Reactor *reactor, Event *event) { auto *serv = static_cast(reactor->ptr); Connection *conn = serv->get_connection(event->fd); /** * invalid event * The server has been actively closed the connection, the client also initiated off, fd has been reused. */ if (!conn || conn->server_fd == 0) { return SW_OK; } ListenPort *port = serv->get_port_by_fd(event->fd); #ifdef SW_SUPPORT_DTLS if (port->is_dtls()) { dtls::Buffer *buffer = static_cast(sw_malloc(sizeof(*buffer) + SW_BUFFER_SIZE_UDP)); buffer->length = event->socket->read(buffer->data, SW_BUFFER_SIZE_UDP); dtls::Session *session = port->dtls_sessions->find(event->fd)->second; session->append(buffer); if (!session->listened && !session->listen()) { serv->abort_connection(reactor, port, event->socket); return SW_OK; } } #endif ReturnCode code = ReactorThread_verify_ssl_state(reactor, port, event->socket); switch (code) { case SW_ERROR: serv->abort_connection(reactor, port, event->socket); return SW_OK; case SW_READY: #ifdef SW_SUPPORT_DTLS if (event->socket->dtls) { return SW_OK; } #endif break; case SW_WAIT: return SW_OK; case SW_CONTINUE: break; default: abort(); } conn->last_recv_time = microtime(); long last_recv_bytes = event->socket->total_recv_bytes; int retval = port->onRead(reactor, port, event); long socket_recv_bytes = event->socket->total_recv_bytes - last_recv_bytes; if (socket_recv_bytes > 0) { sw_atomic_fetch_add(&port->gs->total_recv_bytes, socket_recv_bytes); sw_atomic_fetch_add(&serv->gs->total_recv_bytes, socket_recv_bytes); } if (!conn->active) { return retval; } if (serv->is_process_mode() && serv->max_queued_bytes && conn->recv_queued_bytes > serv->max_queued_bytes) { conn->waiting_time = 1; conn->timer = swoole_timer_add((long) conn->waiting_time, false, ReactorThread_resume_data_receiving, event->socket); if (conn->timer) { reactor->remove_read_event(event->socket); } } return retval; } static int ReactorThread_onWrite(Reactor *reactor, Event *ev) { int ret; auto serv = static_cast(reactor->ptr); auto socket = ev->socket; int fd = ev->fd; auto port = serv->get_port_by_fd(fd); if (serv->is_process_mode()) { assert(fd % serv->reactor_num == reactor->id); assert(fd % serv->reactor_num == SwooleTG.id); } Connection *conn = serv->get_connection(fd); if (conn == nullptr || conn->active == 0) { return SW_ERR; } swoole_trace_log(SW_TRACE_REACTOR, "fd=%d, conn->close_notify=%d, serv->disable_notify=%d, conn->close_force=%d", fd, conn->close_notify, serv->disable_notify, conn->close_force); if (conn->close_notify) { if (socket->ssl && socket->ssl_state != SW_SSL_STATE_READY) { return Server::close_connection(reactor, socket); } serv->notify(conn, SW_SERVER_EVENT_CLOSE); conn->close_notify = 0; return SW_OK; } else if (serv->disable_notify && conn->close_force) { return Server::close_connection(reactor, socket); } while (!Buffer::empty(socket->out_buffer)) { BufferChunk *chunk = socket->out_buffer->front(); if (chunk->type == BufferChunk::TYPE_CLOSE) { return reactor->close(reactor, socket); } else if (chunk->type == BufferChunk::TYPE_SENDFILE) { ret = socket->handle_sendfile(); } else { ret = socket->handle_send(); if (SW_OK == ret) { conn->send_queued_bytes = socket->out_buffer->length(); } } if (ret < 0) { if (socket->close_wait) { conn->close_errno = errno; return reactor->trigger_close_event(ev); } else if (socket->send_wait) { break; } } } if (conn->overflow && socket->get_out_buffer_length() < socket->buffer_size) { conn->overflow = 0; } if (serv->onBufferEmpty && conn->high_watermark) { if (socket->get_out_buffer_length() <= port->buffer_low_watermark) { conn->high_watermark = 0; serv->notify(conn, SW_SERVER_EVENT_BUFFER_EMPTY); } } if (socket->send_timer) { if (Buffer::empty(socket->out_buffer)) { swoole_timer_del(socket->send_timer); socket->send_timer = nullptr; } else { swoole_timer_delay(socket->send_timer, port->max_idle_time); } } // remove EPOLLOUT event if (!conn->peer_closed && !socket->removed && Buffer::empty(socket->out_buffer)) { reactor->set(socket, SW_EVENT_READ); } return SW_OK; } void Server::heartbeat_check(Timer *timer, TimerNode *tnode) { double now = microtime(); auto reactor = static_cast(tnode->data); auto serv = static_cast(reactor->ptr); ReactorThread *thread = serv->get_thread(reactor->id); serv->foreach_connection([=](Connection *conn) { SessionId session_id = conn->session_id; if (session_id <= 0) { return; } if (conn->reactor_id != reactor->id) { return; } if (serv->is_healthy_connection(now, conn)) { return; } thread->close_connection(reactor, session_id); }); } /** * [master] */ int Server::start_reactor_threads() { if (swoole_event_init(0) < 0) { return SW_ERR; } Reactor *reactor = sw_reactor(); for (const auto port : ports) { if (port->is_dgram()) { continue; } if (port->listen() < 0) { swoole_event_free(); return SW_ERR; } reactor->add(port->socket, SW_EVENT_READ); } store_listen_socket(); if (single_thread) { get_thread(0)->init(this, reactor, 0); goto _init_master_thread; } /** * multi-threads */ else { /** * set a special id */ reactor->id = reactor_num; SwooleTG.id = reactor_num; } SW_LOOP_N(reactor_num) { get_thread(i)->thread = std::thread([=]() { swoole_thread_init(false); reactor_thread_main_loop(this, i); swoole_thread_clean(false); }); } _init_master_thread: /** * heartbeat thread */ if (heartbeat_check_interval >= 1) { if (single_thread) { heartbeat_timer = swoole_timer_add(sec2msec(heartbeat_check_interval), true, heartbeat_check, reactor); } else { start_heartbeat_thread(); } } return start_master_thread(reactor); } int ReactorThread::init(Server *serv, Reactor *reactor, uint16_t reactor_id) { reactor->ptr = serv; reactor->id = reactor_id; reactor->wait_exit = false; reactor->max_socket = serv->get_max_connection(); reactor->close = Server::close_connection; reactor->default_error_handler = ReactorThread_onClose; reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, ReactorThread_onPipeRead); reactor->set_handler(SW_FD_PIPE, SW_EVENT_WRITE, ReactorThread_onPipeWrite); // listen UDP port if (serv->have_dgram_sock == 1) { for (auto ls : serv->ports) { if (ls->is_stream()) { continue; } int server_fd = ls->socket->fd; if (server_fd % serv->reactor_num != reactor_id) { continue; } Connection *serv_sock = serv->get_connection(server_fd); serv_sock->fd = server_fd; serv_sock->socket_type = ls->type; serv_sock->object = ls; ls->thread_id = pthread_self(); if (reactor->add(ls->socket, SW_EVENT_READ) < 0) { return SW_ERR; } } } serv->init_reactor(reactor); serv->init_pipe_sockets(&message_bus); if (serv->is_thread_mode()) { Worker *worker = serv->get_worker(reactor_id); serv->init_event_worker(worker); auto pipe_worker = message_bus.get_pipe_socket(worker->pipe_worker); reactor->add(pipe_worker, SW_EVENT_READ); if (serv->heartbeat_check_interval > 0) { heartbeat_timer = swoole_timer_add(sec2msec(serv->heartbeat_check_interval), true, Server::heartbeat_check, reactor); } } if (serv->pipe_command) { auto pipe_socket = serv->pipe_command->get_socket(false); message_bus.init_pipe_socket(pipe_socket); pipe_command = message_bus.get_pipe_socket(pipe_socket); pipe_command->buffer_size = UINT_MAX; } message_bus.set_id_generator(serv->msg_id_generator); message_bus.set_buffer_size(serv->ipc_max_size); message_bus.set_always_chunked_transfer(); if (!message_bus.alloc_buffer()) { return SW_ERR; } SW_LOOP_N(serv->worker_num) { if (i % serv->reactor_num != reactor_id) { continue; } Socket *socket = message_bus.get_pipe_socket(serv->get_worker_pipe_master(i)); if (reactor->add(socket, SW_EVENT_READ) < 0) { return SW_ERR; } /** * It will only send data to the notify pipeline synchronously, * which is thread-safe and does not require separate memory */ if (notify_pipe == nullptr) { notify_pipe = serv->workers[i].pipe_worker; } } return SW_OK; } void ReactorThread::clean() { message_bus.free_buffer(); } void Server::reactor_thread_main_loop(Server *serv, int reactor_id) { ReactorThread *thread = serv->get_thread(reactor_id); thread->id = reactor_id; SwooleTG.message_bus = &thread->message_bus; if (swoole_event_init(0) < 0) { return; } if (serv->is_thread_mode()) { serv->call_worker_start_callback(serv->get_worker(reactor_id)); } else { swoole_set_thread_id(reactor_id); swoole_set_thread_type(Server::THREAD_REACTOR); } Reactor *reactor = sw_reactor(); if (thread->init(serv, reactor, reactor_id) < 0) { return; } // wait other thread if (serv->is_process_mode()) { serv->reactor_thread_barrier.wait(); } // main loop swoole_event_wait(); if (serv->is_thread_mode()) { serv->call_worker_stop_callback(serv->get_worker(reactor_id)); } thread->clean(); } static void ReactorThread_resume_data_receiving(Timer *timer, TimerNode *tnode) { auto *_socket = static_cast(tnode->data); auto *conn = static_cast(_socket->object); if (conn->recv_queued_bytes > sw_server()->max_queued_bytes) { if (conn->waiting_time != 1024) { conn->waiting_time *= 2; } conn->timer = swoole_timer_add((long) conn->waiting_time, false, ReactorThread_resume_data_receiving, _socket); if (conn->timer) { return; } } timer->get_reactor()->add_read_event(_socket); conn->timer = nullptr; } /** * dispatch request data [only data frame] */ int Server::dispatch_task(const Protocol *proto, Socket *_socket, const RecvData *rdata) { auto *serv = static_cast(proto->private_data_2); SendData task; auto *conn = static_cast(_socket->object); const ListenPort *port = serv->get_port_by_fd(conn->fd); sw_memset_zero(&task.info, sizeof(task.info)); task.info.server_fd = conn->server_fd; task.info.reactor_id = conn->reactor_id; task.info.ext_flags = rdata->info.ext_flags; task.info.type = SW_SERVER_EVENT_RECV_DATA; task.info.time = conn->last_recv_time; swoole_trace("dispatch task, size=%u bytes", rdata->info.len); task.info.fd = conn->fd; task.info.len = rdata->info.len; task.data = rdata->data; if (rdata->info.len > 0) { sw_atomic_fetch_add(&conn->recv_queued_bytes, rdata->info.len); swoole_trace_log(SW_TRACE_SERVER, "session_id=%ld, len=%d, qb=%d", conn->session_id, rdata->info.len, conn->recv_queued_bytes); } if (!serv->factory_->dispatch(&task)) { if (rdata->info.len > 0) { sw_atomic_fetch_sub(&conn->recv_queued_bytes, rdata->info.len); } return SW_ERR; } else { if (serv->is_process_mode()) { ReactorThread *thread = serv->get_thread(conn->reactor_id); thread->dispatch_count++; } sw_atomic_fetch_add(&serv->gs->dispatch_count, 1); sw_atomic_fetch_add(&port->gs->dispatch_count, 1); return SW_OK; } } void Server::join_heartbeat_thread() { /** * Shutdown heartbeat thread */ if (heartbeat_thread.joinable()) { swoole_trace_log(SW_TRACE_SERVER, "terminate heartbeat thread"); heartbeat_thread.join(); } } void Server::join_reactor_thread() { if (single_thread) { return; } if (heartbeat_check_interval > 0) { join_heartbeat_thread(); } for (int i = 0; i < reactor_num; i++) { ReactorThread *thread = get_thread(i); if (!thread->thread.joinable()) { continue; } if (thread->notify_pipe) { DataHead ev = {}; ev.type = SW_SERVER_EVENT_SHUTDOWN; thread->notify_pipe->send_sync((void *) &ev, sizeof(ev)); } thread->thread.join(); } } void Server::start_heartbeat_thread() { heartbeat_thread = std::thread([this]() { swoole_signal_block_all(); swoole_set_thread_type(THREAD_HEARTBEAT); swoole_set_thread_type(reactor_num + 1); while (running) { double now = microtime(); foreach_connection([this, now](Connection *conn) { SessionId session_id = conn->session_id; if (session_id <= 0) { return; } if (is_healthy_connection(now, conn)) { return; } DataHead ev{}; ev.type = SW_SERVER_EVENT_CLOSE_FORCE; // convert fd to session_id, in order to verify the connection before the force close connection ev.fd = session_id; get_reactor_pipe_socket(session_id, conn->reactor_id)->send_sync(&ev, sizeof(ev)); }); sleep(heartbeat_check_interval); } }); } } // namespace swoole ================================================ FILE: src/server/static_handler.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_static_handler.h" #include "swoole_util.h" #include #include #include #include #include namespace swoole { namespace http_server { bool StaticHandler::is_modified(const std::string &date_if_modified_since) const { char date_tmp[64]; if (date_if_modified_since.empty() || date_if_modified_since.length() > sizeof(date_tmp) - 1) { return false; } struct tm tm3 {}; memcpy(date_tmp, date_if_modified_since.c_str(), date_if_modified_since.length()); date_tmp[date_if_modified_since.length()] = 0; const char *date_format = nullptr; if (strptime(date_tmp, SW_HTTP_RFC1123_DATE_GMT, &tm3) != nullptr) { date_format = SW_HTTP_RFC1123_DATE_GMT; } else if (strptime(date_tmp, SW_HTTP_RFC1123_DATE_UTC, &tm3) != nullptr) { date_format = SW_HTTP_RFC1123_DATE_UTC; } else if (strptime(date_tmp, SW_HTTP_RFC850_DATE, &tm3) != nullptr) { date_format = SW_HTTP_RFC850_DATE; } else if (strptime(date_tmp, SW_HTTP_ASCTIME_DATE, &tm3) != nullptr) { date_format = SW_HTTP_ASCTIME_DATE; } return date_format && mktime(&tm3) - (time_t) serv->timezone_ >= get_file_mtime(); } bool StaticHandler::is_modified_range(const std::string &date_range) const { if (date_range.empty()) { return false; } tm tm3{}; const char *date_format = nullptr; if (strptime(date_range.c_str(), SW_HTTP_RFC1123_DATE_GMT, &tm3) != nullptr) { date_format = SW_HTTP_RFC1123_DATE_GMT; } else if (strptime(date_range.c_str(), SW_HTTP_RFC1123_DATE_UTC, &tm3) != nullptr) { date_format = SW_HTTP_RFC1123_DATE_UTC; } else if (strptime(date_range.c_str(), SW_HTTP_RFC850_DATE, &tm3) != nullptr) { date_format = SW_HTTP_RFC850_DATE; } else if (strptime(date_range.c_str(), SW_HTTP_ASCTIME_DATE, &tm3) != nullptr) { date_format = SW_HTTP_ASCTIME_DATE; } time_t file_mtime = get_file_mtime(); tm *tm_file_mtime = gmtime(&file_mtime); return date_format && mktime(&tm3) != mktime(tm_file_mtime); } std::string StaticHandler::get_date() { char date_[64]; time_t now = ::time(nullptr); tm *tm1 = gmtime(&now); strftime(date_, sizeof(date_), "%a, %d %b %Y %H:%M:%S %Z", tm1); return date_; } std::string StaticHandler::get_date_last_modified() const { char date_last_modified[64]; time_t file_mtime = get_file_mtime(); tm *tm2 = gmtime(&file_mtime); strftime(date_last_modified, sizeof(date_last_modified), "%a, %d %b %Y %H:%M:%S %Z", tm2); return date_last_modified; } bool StaticHandler::get_absolute_path() { char abs_path[PATH_MAX]; if (!realpath(filename, abs_path)) { return false; } size_t abs_path_len = strlen(abs_path); if (abs_path_len >= PATH_MAX) { return false; } memcpy(filename, abs_path, abs_path_len + 1); l_filename = abs_path_len; return true; } bool StaticHandler::try_serve() { serv->apply_rewrite_rules(this); char *p = filename; const char *url = request_url.c_str(); size_t url_length = request_url.length(); /** * discard the url parameter * [/test.jpg?version=1#position] -> [/test.jpg] */ auto params = (char *) memchr(url, '?', url_length); if (params == nullptr) { params = (char *) memchr(url, '#', url_length); } size_t n = params ? params - url : url_length; const std::string &document_root = serv->get_document_root(); const size_t l_document_root = document_root.length(); memcpy(p, document_root.c_str(), l_document_root); p += l_document_root; if (!serv->locations->empty()) { for (const auto &i : *serv->locations) { if (swoole_str_istarts_with(url, url_length, i.c_str(), i.size())) { last = true; } } if (!last) { return false; } } if (l_document_root + n >= PATH_MAX) { return catch_error(); } memcpy(p, url, n); p += n; *p = '\0'; if (!dir_path.empty()) { dir_path.clear(); } dir_path = std::string(url, n); l_filename = url_decode(filename, p - filename); filename[l_filename] = '\0'; // The file does not exist if (lstat(filename, &file_stat) < 0) { return catch_error(); } // The filename is relative path, allows for the resolution of symbolic links. // This path is formed by concatenating the document root and that is permitted for access. if (is_absolute_path()) { if (is_link()) { // Use the realpath function to resolve a symbolic link to its actual path. if (!get_absolute_path()) { return catch_error(); } if (lstat(filename, &file_stat) < 0) { return catch_error(); } } } else { if (!get_absolute_path() || !is_located_in_document_root()) { return catch_error(); } } if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) { return true; } if (serv->http_autoindex && is_dir()) { return true; } if (!mime_type::exists(filename) && !last) { return false; } if (!is_file()) { return false; } return true; } bool StaticHandler::try_serve_index_file() { if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) { if (!get_dir_files()) { return false; } index_file = intersection(*serv->http_index_files, dir_files); if (has_index_file() && !set_filename(index_file)) { return false; } if (!has_index_file() && !is_enabled_auto_index()) { return false; } } return true; } size_t StaticHandler::make_index_page(String *buffer) { get_dir_files(); if (dir_path.back() != '/') { dir_path.append("/"); } buffer->format_impl(String::FORMAT_APPEND | String::FORMAT_GROW, "\n" "\n" "\t\nIndex of %s" "\n" "\n" SW_HTTP_PAGE_CSS "

Index of %s

" "\t
    \n", dir_path.c_str(), dir_path.c_str()); for (const auto &dir_file : dir_files) { if (dir_file == "." || (dir_path == "/" && dir_file == "..")) { continue; } buffer->format_impl(String::FORMAT_APPEND | String::FORMAT_GROW, "\t\t
  • %s
  • \n", dir_path.c_str(), dir_file.c_str(), dir_file.c_str()); } buffer->append(SW_STRL("\t
\n" SW_HTTP_POWER_BY "\n\n")); return buffer->length; } bool StaticHandler::get_dir_files() { if (!dir_files.empty()) { return true; } if (!is_dir()) { return false; } DIR *dir = opendir(filename); if (dir == nullptr) { return false; } struct dirent *ptr; while ((ptr = readdir(dir)) != nullptr) { dir_files.insert(ptr->d_name); } closedir(dir); return true; } bool StaticHandler::set_filename(const std::string &_filename) { char *p = filename + l_filename; if (*p != '/') { *p = '/'; p += 1; } memcpy(p, _filename.c_str(), _filename.length()); p += _filename.length(); *p = 0; if (lstat(filename, &file_stat) < 0) { return false; } if (!is_file()) { return false; } return true; } void StaticHandler::parse_range(const char *range, const char *if_range) { task_t _task{}; _task.length = 0; // range if (range && '\0' != *range) { const char *p = range; // bytes= if (!SW_STR_ISTARTS_WITH(p, strlen(range), "bytes=")) { _task.offset = 0; _task.length = content_length = get_filesize(); tasks.push_back(_task); return; } p += 6; size_t start, end, size = 0, cutoff = SIZE_MAX / 10, cutlim = SIZE_MAX % 10, suffix, _content_length = get_filesize(); content_length = 0; for (;;) { start = 0; end = 0; suffix = 0; while (*p == ' ') { p++; } if (*p != '-') { if (*p < '0' || *p > '9') { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } while (*p >= '0' && *p <= '9') { if (start >= cutoff && (start > cutoff || (size_t) (*p - '0') > cutlim)) { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } start = start * 10 + (*p++ - '0'); } while (*p == ' ') { p++; } if (*p++ != '-') { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } while (*p == ' ') { p++; } if (*p == ',' || *p == '\0') { end = _content_length; goto found; } } else { suffix = 1; p++; } if (*p < '0' || *p > '9') { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } while (*p >= '0' && *p <= '9') { if (end >= cutoff && (end > cutoff || (size_t) (*p - '0') > cutlim)) { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } end = end * 10 + (*p++ - '0'); } while (*p == ' ') { p++; } if (*p != ',' && *p != '\0' && *p != '\r') { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } if (suffix) { start = (end < _content_length) ? _content_length - end : 0; end = _content_length - 1; } if (end >= _content_length) { end = _content_length; } else { end++; } found: if (start < end) { if (size > SIZE_MAX - (end - start)) { status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; return; } size += end - start; _task.offset = start; _task.length = end - start; content_length += sw_snprintf(_task.part_header, sizeof(_task.part_header), "%s--%s\r\n" "Content-Type: %s\r\n" "Content-Range: bytes %zu-%zu/%zu\r\n\r\n", tasks.empty() ? "" : "\r\n", get_boundary().c_str(), get_mimetype().c_str(), (size_t) _task.offset, end - 1, get_filesize()) + _task.length; tasks.push_back(_task); } else if (start == 0) { break; } if (*p++ != ',' || '\r' == *p || '\0' == *p) { break; } } } if (_task.length > 0) { if (1 == tasks.size()) { content_length = _task.length; } else { end_part = std::string("\r\n--") + get_boundary() + "--\r\n"; content_length += end_part.size(); } status_code = SW_HTTP_PARTIAL_CONTENT; } else { _task.offset = 0; _task.length = content_length = get_filesize(); tasks.push_back(_task); } // if-range if (if_range) { if (is_modified_range(if_range)) { tasks.clear(); _task.offset = 0; _task.length = content_length = get_filesize(); tasks.push_back(_task); status_code = SW_HTTP_OK; } } } } // namespace http_server void Server::add_rewrite_rule(const std::string &pattern, const std::string &replacement) { if (rewrite_rules == nullptr) { rewrite_rules = std::make_shared>(); } http_server::RewriteRule rule; rule.replacement = replacement; if (pattern.length() >= 2 && pattern.at(0) == '~' && pattern.at(pattern.length() - 1) == '~') { rule.pattern = pattern.substr(1, pattern.length() - 2); rule.is_regex = true; } else { rule.pattern = pattern; rule.is_regex = false; } rewrite_rules->emplace_back(rule); } void Server::add_static_handler_location(const std::string &location) { if (locations == nullptr) { locations = std::make_shared>(); } locations->emplace(location); } void Server::add_static_handler_index_files(const std::string &file) { if (http_index_files == nullptr) { http_index_files = std::make_shared>(); } auto iter = std::find(http_index_files->begin(), http_index_files->end(), file); if (iter == http_index_files->end()) { http_index_files->emplace_back(file); } } bool Server::select_static_handler(const http_server::Request *request, const Connection *conn) { const char *url = request->buffer_->str + request->url_offset_; size_t url_length = request->url_length_; http_server::StaticHandler handler(this, url, url_length); if (!handler.try_serve()) { return false; } char header_buffer[1024]; SendData response; response.info.fd = conn->session_id; response.info.type = SW_SERVER_EVENT_SEND_DATA; if (handler.status_code == SW_HTTP_NOT_FOUND) { response.info.len = sw_snprintf(header_buffer, sizeof(header_buffer), "HTTP/1.1 %s\r\n" "Server: " SW_HTTP_SERVER_SOFTWARE "\r\n" "Content-Length: %zu\r\n" "\r\n%s", http_server::get_status_message(SW_HTTP_NOT_FOUND), sizeof(SW_HTTP_PAGE_404) - 1, SW_HTTP_PAGE_404); response.data = header_buffer; send_to_connection(&response); return true; } auto date_str = handler.get_date(); auto date_str_last_modified = handler.get_date_last_modified(); std::string date_if_modified_since = request->get_header("If-Modified-Since"); if (!date_if_modified_since.empty() && handler.is_modified(date_if_modified_since)) { response.info.len = sw_snprintf(header_buffer, sizeof(header_buffer), "HTTP/1.1 304 Not Modified\r\n" "Connection: %s\r\n" "Date: %s\r\n" "Last-Modified: %s\r\n" "Server: %s\r\n\r\n", request->keep_alive ? "keep-alive" : "close", date_str.c_str(), date_str_last_modified.c_str(), SW_HTTP_SERVER_SOFTWARE); response.data = header_buffer; send_to_connection(&response); return true; } /** * if http_index_files is enabled, need to search the index file first. * if the index file is found, set filename to index filename. */ if (!handler.try_serve_index_file()) { return false; } /** * the index file was not found in the current directory, * if http_autoindex is enabled, should show the list of files in the current directory. */ if (!handler.has_index_file() && handler.is_enabled_auto_index() && handler.is_dir()) { sw_tg_buffer()->clear(); size_t body_length = handler.make_index_page(sw_tg_buffer()); response.info.len = sw_snprintf(header_buffer, sizeof(header_buffer), "HTTP/1.1 200 OK\r\n" "Connection: %s\r\n" "Content-Length: %ld\r\n" "Content-Type: text/html\r\n" "Date: %s\r\n" "Last-Modified: %s\r\n" "Server: %s\r\n\r\n", request->keep_alive ? "keep-alive" : "close", static_cast(body_length), date_str.c_str(), date_str_last_modified.c_str(), SW_HTTP_SERVER_SOFTWARE); response.data = header_buffer; send_to_connection(&response); response.info.len = body_length; response.data = sw_tg_buffer()->str; send_to_connection(&response); return true; } handler.parse_range(request->get_header("Range").c_str(), request->get_header("If-Range").c_str()); auto tasks = handler.get_tasks(); std::stringstream header_stream; if (1 == tasks.size()) { if (SW_HTTP_PARTIAL_CONTENT == handler.status_code) { header_stream << "Content-Range: bytes " << tasks[0].offset << "-" << (tasks[0].length + tasks[0].offset - 1) << "/" << handler.get_filesize() << "\r\n"; } else { header_stream << "Accept-Ranges: bytes\r\n"; } } response.info.len = sw_snprintf( header_buffer, sizeof(header_buffer), "HTTP/1.1 %s\r\n" "Connection: %s\r\n" "Content-Length: %ld\r\n" "Content-Type: %s\r\n" "%s" "Date: %s\r\n" "Last-Modified: %s\r\n" "Server: %s\r\n\r\n", http_server::get_status_message(handler.status_code), request->keep_alive ? "keep-alive" : "close", SW_HTTP_HEAD == request->method ? 0 : handler.get_content_length(), SW_HTTP_HEAD == request->method ? handler.get_mimetype().c_str() : handler.get_content_type().c_str(), header_stream.str().c_str(), date_str.c_str(), date_str_last_modified.c_str(), SW_HTTP_SERVER_SOFTWARE); response.data = header_buffer; // Use tcp_nopush to improve sending efficiency conn->socket->cork(); // Send HTTP header send_to_connection(&response); // Send HTTP body if (SW_HTTP_HEAD != request->method) { if (!tasks.empty()) { size_t task_size = sizeof(network::SendfileTask) + strlen(handler.get_filename()) + 1; auto task = static_cast(sw_malloc(task_size)); strcpy(task->filename, handler.get_filename()); if (tasks.size() > 1) { for (const auto &i : tasks) { response.info.type = SW_SERVER_EVENT_SEND_DATA; response.info.len = strlen(i.part_header); response.data = i.part_header; send_to_connection(&response); task->offset = i.offset; task->length = i.length; response.info.type = SW_SERVER_EVENT_SEND_FILE; response.info.len = task_size; response.data = reinterpret_cast(task); send_to_connection(&response); } response.info.type = SW_SERVER_EVENT_SEND_DATA; response.info.len = handler.get_end_part().length(); response.data = handler.get_end_part().c_str(); send_to_connection(&response); } else if (tasks[0].length > 0) { task->offset = tasks[0].offset; task->length = tasks[0].length; response.info.type = SW_SERVER_EVENT_SEND_FILE; response.info.len = task_size; response.data = reinterpret_cast(task); send_to_connection(&response); } sw_free(task); } } // Close the connection if keepalive is not used if (!request->keep_alive) { response.info.type = SW_SERVER_EVENT_CLOSE; response.info.len = 0; response.data = nullptr; send_to_connection(&response); } return true; } bool Server::apply_rewrite_rules(http_server::StaticHandler *handler) { if (!rewrite_rules || rewrite_rules->empty()) { return false; } bool rewritten = false; auto current_url = handler->get_request_url(); for (const auto &rule : *rewrite_rules) { if (rule.is_regex) { try { std::regex pattern(rule.pattern); std::string rewritten_url; if (std::regex_search(current_url, pattern)) { rewritten_url = std::regex_replace(current_url, pattern, rule.replacement); if (rewritten_url != current_url) { handler->set_request_url(rewritten_url); rewritten = true; break; } } } catch (const std::regex_error &e) { continue; } } else { if (starts_with(current_url, rule.pattern)) { std::string rewritten_url = rule.replacement + current_url.substr(rule.pattern.length()); handler->set_request_url(rewritten_url); rewritten = true; break; } } } return rewritten; } } // namespace swoole ================================================ FILE: src/server/task_worker.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" namespace swoole { using network::Socket; static void TaskWorker_signal_init(ProcessPool *pool); static int TaskWorker_onPipeReceive(Reactor *reactor, Event *event); static int TaskWorker_loop_async(ProcessPool *pool, Worker *worker); static void TaskWorker_onStart(ProcessPool *pool, Worker *worker); static void TaskWorker_onStop(ProcessPool *pool, Worker *worker); static int TaskWorker_onTask(ProcessPool *pool, Worker *worker, EventData *task); static SW_THREAD_LOCAL EventData *latest_task = nullptr; /** * after pool->create, before pool->start */ bool Server::init_task_workers() { ProcessPool *pool = get_task_worker_pool(); pool->ptr = this; pool->onTask = TaskWorker_onTask; pool->onWorkerStart = TaskWorker_onStart; pool->onWorkerStop = TaskWorker_onStop; /** * Make the task worker support asynchronous */ if (task_enable_coroutine) { if (task_ipc_mode == TASK_IPC_MSGQUEUE || task_ipc_mode == TASK_IPC_PREEMPTIVE) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "cannot use msgqueue when task_enable_coroutine is enable"); return false; } pool->main_loop = TaskWorker_loop_async; } if (task_ipc_mode == TASK_IPC_PREEMPTIVE) { pool->schedule_by_sysvmsg = true; } SW_LOOP_N(task_worker_num) { create_worker(&pool->workers[i]); } return true; } static int TaskWorker_call_command_handler(const ProcessPool *pool, const Worker *worker, EventData *req) { auto *serv = static_cast(pool->ptr); int command_id = serv->get_command_id(req); const auto iter = serv->command_handlers.find(command_id); if (iter == serv->command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[%d]", command_id); return SW_OK; } Server::Command::Handler handler = iter->second; PacketPtr packet; if (!Server::task_unpack(req, sw_tg_buffer(), &packet)) { return SW_OK; } auto result = handler(serv, std::string(packet.data, packet.length)); SendData task{}; task.info.fd = serv->get_task_id(req); task.info.reactor_id = worker->id; task.info.server_fd = -1; task.info.type = SW_SERVER_EVENT_COMMAND_RESPONSE; task.info.len = result.length(); task.data = result.c_str(); return serv->message_bus.write(serv->get_command_reply_socket(), &task) ? SW_OK : SW_ERR; } static int TaskWorker_onTask(ProcessPool *pool, Worker *worker, EventData *task) { int ret = SW_OK; auto *serv = static_cast(pool->ptr); latest_task = task; worker->set_status_to_busy(); if (task->info.type == SW_SERVER_EVENT_PIPE_MESSAGE) { serv->onPipeMessage(serv, task); } else if (task->info.type == SW_SERVER_EVENT_SHUTDOWN) { worker->shutdown(); if (swoole_event_is_available()) { serv->stop_async_worker(worker); } return SW_OK; } else if (task->info.type == SW_SERVER_EVENT_COMMAND_REQUEST) { ret = TaskWorker_call_command_handler(pool, worker, task); } else { ret = serv->onTask(serv, task); /** * only server task as requests, * do not increase the count for pipeline communication and command processing. */ worker->add_request_count(); } worker->set_status_to_idle(); return ret; } void Server::task_dump(EventData *task) { char buf[1024]; task->info.dump(buf, sizeof(buf)); sw_printf("%s", buf); if (task->info.ext_flags & SW_TASK_TMPFILE) { auto pkg = reinterpret_cast(task->data); sw_printf("Task[tmpfile]=%.*s\n", (int) pkg->length, pkg->tmpfile); } } bool Server::task_pack(EventData *task, const void *_data, size_t _length) { task->info = {}; task->info.type = SW_SERVER_EVENT_TASK; task->info.fd = SwooleG.current_task_id++; task->info.reactor_id = swoole_get_worker_id(); task->info.time = microtime(); if (_length < SW_IPC_MAX_SIZE - sizeof(task->info)) { memcpy(task->data, _data, _length); task->info.len = _length; return true; } PacketTask pkg{}; File file = make_tmpfile(); if (!file.ready()) { return false; } if (file.write_all(_data, _length) != _length) { swoole_warning("write to tmpfile failed"); return false; } task->info.len = sizeof(pkg); task->info.ext_flags |= SW_TASK_TMPFILE; swoole_strlcpy(pkg.tmpfile, file.get_path().c_str(), sizeof(pkg.tmpfile)); pkg.length = _length; memcpy(task->data, &pkg, sizeof(pkg)); return true; } bool Server::task(EventData *_task, int *dst_worker_id, bool blocking) { sw_atomic_fetch_add(&gs->tasking_num, 1); swResultCode retval; if (blocking) { retval = get_task_worker_pool()->dispatch_sync(_task, dst_worker_id); } else { retval = get_task_worker_pool()->dispatch(_task, dst_worker_id); } if (retval == SW_OK) { sw_atomic_fetch_add(&gs->task_count, 1); return true; } sw_atomic_fetch_sub(&gs->tasking_num, 1); return false; } bool Server::task_sync(EventData *_task, int *dst_worker_id, double timeout) { uint64_t notify; EventData *task_result = get_task_result(); sw_memset_zero(task_result, sizeof(*task_result)); Pipe *pipe = task_notify_pipes.at(swoole_get_worker_id()).get(); TaskId task_id = get_task_id(_task); pipe->clean(); pipe->set_timeout(timeout); if (!task(_task, dst_worker_id, true)) { return false; } SW_LOOP { if (pipe->read(¬ify, sizeof(notify)) > 0) { if (get_task_id(task_result) != task_id) { continue; } return true; } break; } return false; } int Server::MultiTask::find(TaskId task_id) { auto iter = map.find(task_id); if (iter != map.end()) { return iter->second; } else { return -1; } } bool Server::task_sync(MultiTask &mtask, double timeout) { WorkerId worker_id = swoole_get_worker_id(); uint64_t notify; EventData *task_result = get_task_result(); task_result->info = {}; Pipe *pipe = task_notify_pipes.at(worker_id).get(); Worker *worker = get_worker(worker_id); int dst_worker_id; File fp = make_tmpfile(); if (!fp.ready()) { swoole_set_last_error(errno); return false; } std::string file_path = fp.get_path(); fp.close(); auto finish_count = reinterpret_cast(task_result->data); worker->lock->lock(); *finish_count = 0; swoole_strlcpy(task_result->data + 4, file_path.c_str(), SW_TASK_TMP_PATH_SIZE); worker->lock->unlock(); // clear history task pipe->clean(); auto n_task = mtask.count; SW_LOOP_N(mtask.count) { EventData buf; TaskId task_id = mtask.pack(i, &buf); if (task_id < 0) { swoole_warning("task pack failed"); goto _fail; } buf.info.ext_flags |= SW_TASK_WAITALL; sw_atomic_fetch_add(&gs->tasking_num, 1); dst_worker_id = -1; if (!task(&buf, &dst_worker_id, true)) { swoole_warning("failed to dispatch task"); task_id = -1; _fail: mtask.fail(i); n_task--; } else { sw_atomic_fetch_sub(&gs->tasking_num, 1); } mtask.map[task_id] = i; } if (n_task == 0) { swoole_set_last_error(SW_ERROR_TASK_DISPATCH_FAIL); return false; } if (timeout > 0) { pipe->set_timeout(timeout); } double stated_at = microtime(); while (*finish_count < n_task) { const int ret = pipe->read(¬ify, sizeof(notify)); if (ret <= 0) { break; } if (timeout > 0 && microtime() - stated_at > timeout) { break; } } worker->lock->lock(); auto content = file_get_contents(file_path); worker->lock->unlock(); if (!content) { return false; } do { auto *result = reinterpret_cast(content->str + content->offset); int index = mtask.find(get_task_id(result)); if (index != -1) { mtask.unpack(index, result); } content->offset += result->size(); } while (content->offset < 0 || (size_t) content->offset < content->length); // delete tmp file unlink(file_path.c_str()); return true; } bool Server::task_unpack(EventData *task, String *buffer, PacketPtr *packet) { if (!(task->info.ext_flags & SW_TASK_TMPFILE)) { packet->data = task->data; packet->length = task->info.len; return true; } PacketTask _pkg{}; memcpy(&_pkg, task->data, sizeof(_pkg) - 1); File fp(_pkg.tmpfile, O_RDONLY); if (!fp.ready()) { swoole_sys_warning("open(%s) failed", _pkg.tmpfile); return false; } if (buffer->size < _pkg.length) { buffer->extend(_pkg.length); } if (fp.read_all(buffer->str, _pkg.length) != _pkg.length) { return false; } if (!(task->info.ext_flags & SW_TASK_PEEK)) { unlink(_pkg.tmpfile); } buffer->length = _pkg.length; packet->data = buffer->str; packet->length = buffer->length; return true; } static void TaskWorker_signal_init(ProcessPool *pool) { auto *serv = static_cast(pool->ptr); if (serv->is_thread_mode()) { return; } swoole_signal_set(SIGHUP, nullptr); swoole_signal_set(SIGPIPE, nullptr); swoole_signal_set(SIGUSR1, nullptr); swoole_signal_set(SIGUSR2, nullptr); swoole_signal_set(SIGTERM, Server::worker_signal_handler); swoole_signal_set(SIGWINCH, Server::worker_signal_handler); #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, Server::worker_signal_handler); #endif } static void TaskWorker_onStart(ProcessPool *pool, Worker *worker) { auto serv = static_cast(pool->ptr); swoole_set_worker_id(worker->id); swoole_set_worker_type(SW_TASK_WORKER); /** * Make the task worker support asynchronous */ if (serv->task_enable_coroutine) { if (swoole_event_init(0) < 0) { swoole_error("[TaskWorker] create reactor failed"); return; } SwooleG.enable_signalfd = 1; } else { SwooleG.enable_signalfd = 0; SwooleTG.reactor = nullptr; } TaskWorker_signal_init(pool); serv->worker_start_callback(worker); worker->init(); worker->set_max_request(pool->max_request, pool->max_request_grace); } static void TaskWorker_onStop(ProcessPool *pool, Worker *worker) { swoole_event_free(); auto *serv = static_cast(pool->ptr); serv->worker_stop_callback(worker); } /** * receive data from worker process */ static int TaskWorker_onPipeReceive(Reactor *reactor, Event *event) { EventData task; auto *pool = static_cast(reactor->ptr); Worker *worker = sw_worker(); auto *serv = static_cast(pool->ptr); if (event->socket->read(&task, sizeof(task)) > 0) { int retval = pool->onTask(pool, worker, &task); // maximum number of requests, process will exit. if (worker->has_exceeded_max_request()) { serv->stop_async_worker(worker); } return retval; } else { swoole_sys_warning("read(%d, %ld) failed", event->fd, sizeof(task)); return SW_ERR; } } /** * async task worker */ static int TaskWorker_loop_async(ProcessPool *pool, Worker *worker) { auto *serv = static_cast(pool->ptr); Socket *socket = serv->get_worker_pipe_worker_in_message_bus(worker); worker->set_status_to_idle(); socket->set_nonblock(); sw_reactor()->ptr = pool; swoole_event_add(socket, SW_EVENT_READ); swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, TaskWorker_onPipeReceive); for (uint i = 0; i < serv->worker_num + serv->task_worker_num; i++) { worker = serv->get_worker(i); serv->get_worker_pipe_worker_in_message_bus(worker)->buffer_size = UINT_MAX; serv->get_worker_pipe_master_in_message_bus(worker)->buffer_size = UINT_MAX; } return swoole_event_wait(); } /** * Send the task result to worker */ bool Server::finish(const char *data, size_t data_len, int flags, const EventData *current_task) { if (task_worker_num < 1) { swoole_warning("cannot use Server::task()/Server::finish() method, because no set [task_worker_num]"); return false; } if (current_task == nullptr) { current_task = latest_task; } if (current_task->info.type == SW_SERVER_EVENT_PIPE_MESSAGE) { swoole_warning("Server::task()/Server::finish() is not supported in onPipeMessage callback"); return false; } if (current_task->info.ext_flags & SW_TASK_NOREPLY) { swoole_warning("Server::finish() can only be used in the worker process"); return false; } uint16_t source_worker_id = current_task->info.reactor_id; Worker *worker = get_worker(source_worker_id); if (worker == nullptr) { swoole_warning("invalid worker_id[%d]", source_worker_id); return false; } ssize_t retval; // for swoole_server_task if (current_task->info.ext_flags & SW_TASK_NONBLOCK) { // write to file EventData buf; if (!task_pack(&buf, data, data_len)) { swoole_warning("large task pack failed()"); return false; } // callback function if (current_task->info.ext_flags & SW_TASK_CALLBACK) { flags |= SW_TASK_CALLBACK; } else if (current_task->info.ext_flags & SW_TASK_COROUTINE) { flags |= SW_TASK_COROUTINE; } buf.info.ext_flags |= flags; buf.info.type = SW_SERVER_EVENT_FINISH; buf.info.fd = get_task_id(current_task); if (worker->pool->use_socket && worker->pool->stream_info_->last_connection) { uint32_t _len = htonl(data_len); retval = worker->pool->stream_info_->last_connection->send_sync((void *) &_len, sizeof(_len)); if (retval > 0) { retval = worker->pool->stream_info_->last_connection->send_sync(data, data_len); } } else { retval = send_to_worker_from_worker(worker, &buf, buf.size(), SW_PIPE_MASTER); } } else { uint64_t flag = 1; /** * Use worker shm store the result */ EventData *result = &(task_results[source_worker_id]); Pipe *pipe = task_notify_pipes.at(source_worker_id).get(); // lock worker worker->lock->lock(); if (current_task->info.ext_flags & SW_TASK_WAITALL) { auto *finish_count = reinterpret_cast(result->data); char *_tmpfile = result->data + 4; File file(_tmpfile, O_APPEND | O_WRONLY); if (file.ready()) { EventData buf; if (!task_pack(&buf, data, data_len)) { swoole_warning("large task pack failed()"); buf.info.len = 0; } buf.info.ext_flags |= flags; buf.info.type = SW_SERVER_EVENT_FINISH; buf.info.fd = get_task_id(current_task); size_t bytes = buf.size(); swoole_trace_log(SW_TRACE_SERVER, "write %zu bytes to tmp file '%s'", bytes, _tmpfile); if (file.write_all(&buf, bytes) != bytes) { swoole_sys_warning("write(%s, %ld) failed", _tmpfile, bytes); } sw_atomic_fetch_add(finish_count, 1); } } else { if (!task_pack(result, data, data_len)) { // unlock worker worker->lock->unlock(); swoole_warning("large task pack failed()"); return false; } result->info.ext_flags |= flags; result->info.type = SW_SERVER_EVENT_FINISH; result->info.fd = get_task_id(current_task); } // unlock worker worker->lock->unlock(); while (true) { retval = pipe->write(&flag, sizeof(flag)); auto _sock = pipe->get_socket(true); if (retval < 0 && _sock->catch_write_error(errno) == SW_WAIT) { if (_sock->wait_event(-1, SW_EVENT_WRITE) == 0) { continue; } } break; } } if (retval < 0) { if (errno == EAGAIN || errno == ETIMEDOUT || swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT, "send result to worker timed out"); } else { swoole_sys_warning("send result to worker failed"); } return false; } return true; } } // namespace swoole ================================================ FILE: src/server/thread.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_server.h" #include "swoole_thread.h" namespace swoole { using network::Socket; enum ManagerCommand { CMD_RELOAD = 0x1001, CMD_MANAGER_EXIT = 0x1002, }; static inline Worker *cmd_ptr(const ManagerCommand cmd) { return reinterpret_cast(cmd); } Factory *Server::create_thread_factory() { #ifndef SW_THREAD swoole_error_log(SW_LOG_ERROR, SW_ERROR_OPERATION_NOT_SUPPORT, "Thread support is not enabled, cannot create server with MODE_THREAD"); return nullptr; #endif reactor_num = worker_num; connection_list = static_cast(sw_calloc(max_connection, sizeof(Connection))); if (connection_list == nullptr) { swoole_sys_warning("calloc[2](%d) failed", static_cast(max_connection * sizeof(Connection))); return nullptr; } reactor_threads = new ReactorThread[reactor_num](); reactor_pipe_num = 1; return new ThreadFactory(this); } void Server::destroy_thread_factory() const { sw_free(connection_list); delete[] reactor_threads; } ThreadFactory::ThreadFactory(Server *server) : BaseFactory(server) { threads_.resize(server_->get_all_worker_num() + 1); SW_LOOP_N(server_->get_all_worker_num() + 1) { threads_[i] = std::make_shared(); } } ThreadFactory::~ThreadFactory() { ThreadFactory::shutdown(); } bool ThreadFactory::start() { if (!server_->create_worker_pipes()) { return false; } if (!server_->create_event_workers()) { return false; } if (server_->task_worker_num > 0 && server_->get_task_worker_pool()->start_check() < 0) { return false; } if (server_->get_user_worker_num() > 0 && !server_->create_user_workers()) { return false; } return true; } bool ThreadFactory::shutdown() { for (const auto &thread : threads_) { if (thread->joinable()) { thread->join(); } } return true; } void ThreadFactory::at_thread_enter(WorkerId id, int worker_type) { swoole_thread_init(false); swoole_set_worker_type(worker_type); swoole_set_worker_id(id); swoole_set_worker_pid(swoole_thread_get_native_id()); swoole_set_thread_id(id); swoole_set_thread_type(Server::THREAD_WORKER); swoole_trace_log(SW_TRACE_THREAD, "at_thread_enter=%d", id); } void ThreadFactory::push_to_wait_queue(Worker *worker) { lock_.lock(); queue_.push(worker); lock_.unlock(); cv_.notify_one(); swoole_trace_log(SW_TRACE_THREAD, "push [%p] to wait queue", worker); } void ThreadFactory::at_thread_exit(Worker *worker) { if (worker) { push_to_wait_queue(worker); } swoole_trace_log(SW_TRACE_THREAD, "at_thread_exit=%d", worker->id); swoole_thread_clean(false); } void ThreadFactory::create_message_bus() const { auto mb = new MessageBus(); mb->set_id_generator(server_->msg_id_generator); mb->set_buffer_size(server_->ipc_max_size); mb->set_always_chunked_transfer(); if (!mb->alloc_buffer()) { throw std::bad_alloc(); } server_->init_pipe_sockets(mb); SwooleTG.message_bus = mb; } void ThreadFactory::destroy_message_bus() { SwooleTG.message_bus->clear(); delete SwooleTG.message_bus; SwooleTG.message_bus = nullptr; } void ThreadFactory::spawn_event_worker(WorkerId i) { threads_[i]->start([=]() { at_thread_enter(i, SW_EVENT_WORKER); Worker *worker = server_->get_worker(i); worker->type = SW_EVENT_WORKER; worker->pid = swoole_get_worker_pid(); SwooleWG.worker = worker; server_->worker_thread_start(threads_[i], [=]() { Server::reactor_thread_main_loop(server_, i); }); at_thread_exit(worker); }); } void ThreadFactory::spawn_task_worker(WorkerId i) { threads_[i]->start([=]() { at_thread_enter(i, SW_TASK_WORKER); create_message_bus(); Worker *worker = server_->get_worker(i); worker->type = SW_TASK_WORKER; worker->pid = swoole_get_worker_pid(); worker->set_status_to_idle(); SwooleWG.worker = worker; const auto pool = server_->get_task_worker_pool(); server_->worker_thread_start(threads_[i], [=]() { if (pool->onWorkerStart != nullptr) { pool->onWorkerStart(pool, worker); } pool->main_loop(pool, worker); if (pool->onWorkerStop != nullptr) { pool->onWorkerStop(pool, worker); } }); destroy_message_bus(); at_thread_exit(worker); }); } void ThreadFactory::spawn_user_worker(WorkerId i) { threads_[i]->start([=]() { at_thread_enter(i, SW_USER_WORKER); create_message_bus(); Worker *worker = server_->get_worker(i); worker->type = SW_USER_WORKER; worker->pid = swoole_get_worker_pid(); SwooleWG.worker = worker; server_->worker_thread_start(threads_[i], [=]() { server_->onUserWorkerStart(server_, worker); }); destroy_message_bus(); at_thread_exit(worker); }); } void ThreadFactory::spawn_manager_thread(WorkerId i) { threads_[i]->start([=]() { at_thread_enter(i, SW_MANAGER); swoole_timer_create(true); server_->worker_thread_start(threads_[i], [=]() { if (server_->onManagerStart) { server_->onManagerStart(server_); } wait(); if (server_->onManagerStop) { server_->onManagerStop(server_); } }); if (server_->running) { swoole_warning("Fatal Error: manager thread exits abnormally"); } /* * In the function that closes the timer, the scheduler is called again; * therefore, it is essential to set the scheduler to null after the timer has been consumed. */ if (swoole_timer_is_available()) { swoole_timer_free(); } at_thread_exit(nullptr); }); } void ThreadFactory::wait() { while (true) { std::unique_lock lock(lock_); int64_t cv_timeout_ms_ = swoole_timer_get_next_msec(); if (cv_timeout_ms_ > 0) { cv_.wait_for(lock, std::chrono::milliseconds(cv_timeout_ms_), [this] { return !queue_.empty(); }); } else { cv_.wait(lock, [this] { return !queue_.empty(); }); } swoole_trace_log(SW_TRACE_THREAD, "manager thread is waiting for worker exit, queue size: %zu", queue_.size()); if (!queue_.empty()) { Worker *exited_worker = queue_.front(); queue_.pop(); lock.unlock(); if (exited_worker == cmd_ptr(CMD_RELOAD)) { goto _do_reload; } if (exited_worker == cmd_ptr(CMD_MANAGER_EXIT)) { break; } swoole_trace_log(SW_TRACE_THREAD, "worker(type=%d, tid=%d, id=%d) exit, status=%d", exited_worker->type, exited_worker->pid, exited_worker->id, exited_worker->status); auto thread = threads_[exited_worker->id]; int status_code = thread->get_exit_status(); if (status_code != 0) { ExitStatus exit_status(exited_worker->pid, status_code << 8); server_->call_worker_error_callback(exited_worker, exit_status); swoole_trace_log(SW_TRACE_THREAD, "worker(tid=%d, id=%d) abnormal exit, status=%d", exit_status.get_pid(), exited_worker->id, exit_status.get_code()); } thread->join(); swoole_trace_log(SW_TRACE_THREAD, "thread=%d join", exited_worker->id); switch (exited_worker->type) { case SW_EVENT_WORKER: spawn_event_worker(exited_worker->id); break; case SW_TASK_WORKER: spawn_task_worker(exited_worker->id); break; case SW_USER_WORKER: spawn_user_worker(exited_worker->id); break; default: abort(); break; } } if (sw_timer()) { sw_timer()->select(); } if (server_->running && reloading) { _do_reload: do_reload(); } } } ThreadReloadTask::ThreadReloadTask(Server *_server, bool _reload_all_workers) { server_ = _server; worker_num = server_->get_core_worker_num(); // If only reloading task workers, skip the event workers. reloaded_num = _reload_all_workers ? 0 : server_->worker_num; } void ThreadFactory::do_reload() { if (!reload_task) { reload_task = std::make_shared(server_, reload_all_workers); if (server_->onBeforeReload) { server_->onBeforeReload(server_); } } server_->kill_worker(reload_task->reloaded_num++); if (reload_task->is_completed()) { reload_task.reset(); reloading = 0; if (server_->onAfterReload) { server_->onAfterReload(server_); } } } bool ThreadFactory::reload(bool _reload_all_workers) { auto _what = _reload_all_workers ? "all" : "task"; if (server_->task_worker_num == 0 && !_reload_all_workers) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "Cannot reload %s workers, task workers are not started", _what); return false; } // Prevent duplicate submission of reload requests. if (!sw_atomic_cmp_set(&reloading, 0, 1)) { swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); return false; } reload_all_workers = _reload_all_workers; if (!server_->is_manager()) { swoole_info("Send a notification to the manager process to prepare for restarting %s worker processes.", _what); push_to_wait_queue(cmd_ptr(CMD_RELOAD)); } else { swoole_info("Server is reloading %s workers now", _what); do_reload(); } return true; } WorkerId ThreadFactory::get_manager_thread_id() const { return server_->get_all_worker_num(); } WorkerId ThreadFactory::get_master_thread_id() const { return server_->get_all_worker_num() + 1; } void ThreadFactory::terminate_manager_thread() { swoole_trace_log(SW_TRACE_THREAD, "notify manager thread to exit"); push_to_wait_queue(cmd_ptr(CMD_MANAGER_EXIT)); /** * When terminating the service, the management thread may still be joining other worker threads, * so it is essential to first reclaim the management thread to ensure it has exited. * During the shutdown, the running flag has already been set to false, * which means the management thread might not have reclaimed all worker threads and may have exited prematurely. * At this point, it is necessary to loop through and reclaim the remaining worker threads. */ auto manager_thread_id = get_manager_thread_id(); threads_[manager_thread_id]->join(); swoole_trace_log(SW_TRACE_THREAD, "manager thread is exited"); } int Server::start_worker_threads() { auto *_factory = dynamic_cast(factory_); if (task_worker_num > 0) { SW_LOOP_N(task_worker_num) { _factory->spawn_task_worker(worker_num + i); } } SW_LOOP_N(worker_num) { _factory->spawn_event_worker(i); } if (!user_worker_list.empty()) { for (size_t i = 0; i < user_worker_list.size(); i++) { _factory->spawn_user_worker(task_worker_num + worker_num + i); } } auto manager_thread_id = _factory->get_manager_thread_id(); _factory->spawn_manager_thread(manager_thread_id); if (swoole_event_init(0) < 0) { return SW_ERR; } Reactor *reactor = sw_reactor(); for (const auto port : ports) { if (port->is_dgram()) { continue; } if (port->listen() < 0) { swoole_event_free(); return SW_ERR; } reactor->add(port->socket, SW_EVENT_READ); } SwooleTG.id = reactor->id = _factory->get_master_thread_id(); store_listen_socket(); return start_master_thread(reactor); } void Server::stop_worker_threads() { auto *_factory = dynamic_cast(factory_); _factory->terminate_manager_thread(); SW_LOOP_N(get_core_worker_num()) { kill_worker(i); } } bool Server::reload_worker_threads(bool reload_all_workers) const { auto *_factory = dynamic_cast(factory_); return _factory->reload(reload_all_workers); } } // namespace swoole ================================================ FILE: src/server/worker.cc ================================================ /* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include #include #include "swoole_server.h" #include "swoole_memory.h" #include "swoole_coroutine.h" #include "swoole_api.h" namespace swoole { using namespace network; static int Worker_onPipeReceive(Reactor *reactor, Event *event); static void Worker_reactor_try_to_exit(Reactor *reactor); static void Worker_reopen_logger() { if (sw_logger()) { sw_logger()->reopen(); } } void Server::worker_signal_init() const { if (is_thread_mode()) { return; } swoole_signal_set(SIGHUP, nullptr); swoole_signal_set(SIGPIPE, SIG_IGN); swoole_signal_set(SIGUSR1, nullptr); swoole_signal_set(SIGUSR2, nullptr); swoole_signal_set(SIGTERM, worker_signal_handler); swoole_signal_set(SIGWINCH, worker_signal_handler); #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, worker_signal_handler); #endif } void Server::worker_signal_handler(int signo) { if (!SwooleG.running || !sw_server() || !sw_worker() || !sw_server()->is_running()) { return; } switch (signo) { case SIGTERM: if (swoole_event_is_available()) { sw_server()->stop_async_worker(sw_worker()); } else { sw_worker()->shutdown(); } break; case SIGWINCH: Worker_reopen_logger(); break; default: #ifdef SIGRTMIN if (signo == SIGRTMIN) { Worker_reopen_logger(); } #endif break; } } static sw_inline bool Worker_discard_data(const Server *serv, const Connection *conn, const DataHead *info) { if (conn == nullptr) { if (serv->disable_notify && !serv->discard_timeout_request) { return false; } goto _discard_data; } else { if (conn->closed) { goto _discard_data; } else { return false; } } _discard_data: swoole_error_log(SW_LOG_WARNING, SW_ERROR_SESSION_DISCARD_TIMEOUT_DATA, "[2] ignore data[%u bytes] received from session#%ld", info->len, info->fd); return true; } typedef std::function TaskCallback; static sw_inline void Worker_do_task(Server *serv, Worker *worker, const DataHead *info, const TaskCallback &callback) { RecvData recv_data; auto packet = serv->get_worker_message_bus()->get_packet(); recv_data.info = *info; recv_data.info.len = packet.length; recv_data.data = packet.data; if (callback(serv, &recv_data) == SW_OK) { worker->add_request_count(); sw_atomic_fetch_add(&serv->gs->request_count, 1); } } void Server::worker_accept_event(DataHead *info) { Worker *worker = sw_worker(); worker->set_status_to_busy(); switch (info->type) { case SW_SERVER_EVENT_RECV_DATA: { Connection *conn = get_connection_verify(info->fd); if (conn) { if (info->len > 0) { auto packet = get_worker_message_bus()->get_packet(); sw_atomic_fetch_sub(&conn->recv_queued_bytes, packet.length); swoole_trace_log(SW_TRACE_SERVER, "[Worker] session_id=%ld, len=%lu, qb=%d", conn->session_id, packet.length, conn->recv_queued_bytes); } conn->last_dispatch_time = info->time; } if (!Worker_discard_data(this, conn, info)) { Worker_do_task(this, worker, info, onReceive); } break; } case SW_SERVER_EVENT_RECV_DGRAM: { Worker_do_task(this, worker, info, onPacket); break; } case SW_SERVER_EVENT_CLOSE: { Connection *conn = get_connection_verify_no_ssl(info->fd); if (conn && conn->ssl_client_cert && conn->ssl_client_cert_pid == swoole_get_worker_pid()) { delete conn->ssl_client_cert; conn->ssl_client_cert = nullptr; } factory_->end(info->fd, false); break; } case SW_SERVER_EVENT_CONNECT: { // SSL client certificate if (info->len > 0) { Connection *conn = get_connection_verify_no_ssl(info->fd); if (conn) { auto packet = get_worker_message_bus()->get_packet(); conn->ssl_client_cert = new String(packet.data, packet.length); conn->ssl_client_cert_pid = swoole_get_worker_pid(); } } if (onConnect) { onConnect(this, info); } break; } case SW_SERVER_EVENT_BUFFER_FULL: { if (onBufferFull) { onBufferFull(this, info); } break; } case SW_SERVER_EVENT_BUFFER_EMPTY: { if (onBufferEmpty) { onBufferEmpty(this, info); } break; } case SW_SERVER_EVENT_FINISH: { onFinish(this, reinterpret_cast(get_worker_message_bus()->get_buffer())); break; } case SW_SERVER_EVENT_PIPE_MESSAGE: { onPipeMessage(this, reinterpret_cast(get_worker_message_bus()->get_buffer())); break; } case SW_SERVER_EVENT_COMMAND_REQUEST: { call_command_handler(message_bus, worker->id, pipe_command->get_socket(false)); break; } case SW_SERVER_EVENT_SHUTDOWN: { stop_async_worker(worker); break; } default: swoole_warning("[Worker] error event[type=%d]", (int) info->type); break; } worker->set_status_to_idle(); // maximum number of requests, process will exit. if (worker->has_exceeded_max_request()) { if (is_thread_mode()) { Reactor *reactor = sw_reactor(); get_thread(reactor->id)->shutdown(reactor); } else { stop_async_worker(worker); } } } void Server::worker_start_callback(Worker *worker) { if (swoole_is_root_user()) { swoole_set_isolation(group_, user_, chroot_); } SW_LOOP_N(worker_num + task_worker_num) { if (worker->id == i) { continue; } Worker *other_worker = get_worker(i); if (is_worker() && other_worker->pipe_master) { other_worker->pipe_master->set_nonblock(); } } worker->set_status_to_idle(); if (is_process_mode()) { sw_shm_protect(session_list, PROT_READ); } call_worker_start_callback(worker); } void Server::worker_stop_callback(Worker *worker) { call_worker_stop_callback(worker); } void Server::call_worker_start_callback(Worker *worker) { void *hook_args[2]; hook_args[0] = this; hook_args[1] = (void *) (uintptr_t) worker->id; if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START, hook_args); } if (isset_hook(HOOK_WORKER_START)) { call_hook(HOOK_WORKER_START, hook_args); } swoole_clear_last_error(); swoole_clear_last_error_msg(); if (onWorkerStart) { onWorkerStart(this, worker); } } void Server::call_worker_stop_callback(Worker *worker) { void *hook_args[2]; hook_args[0] = this; hook_args[1] = (void *) (uintptr_t) worker->id; if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_STOP)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_STOP, hook_args); } if (onWorkerStop) { onWorkerStop(this, worker); } if (!get_worker_message_bus()->empty()) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA, "unprocessed data in the worker process buffer"); get_worker_message_bus()->clear(); } SwooleWG.running = false; if (SwooleWG.worker_copy) { delete SwooleWG.worker_copy; SwooleWG.worker_copy = nullptr; SwooleWG.worker = nullptr; } } void Server::call_worker_error_callback(Worker *worker, const ExitStatus &status) { if (onWorkerError != nullptr) { onWorkerError(this, worker, status); } /** * The work process has exited unexpectedly, requiring a cleanup of the shared memory state. * This must be done between the termination of the old process and the initiation of the new one; * otherwise, data contention may occur. */ if (worker->type == SW_EVENT_WORKER) { abort_worker(worker); } } bool Server::kill_worker(int worker_id) { auto current_worker = sw_worker(); if (!current_worker && worker_id < 0) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "kill worker in non worker process requires specifying an id"); return false; } worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; const Worker *worker = get_worker(worker_id); if (worker == nullptr) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "the worker_id[%d] is invalid", worker_id); return false; } swoole_trace_log(SW_TRACE_SERVER, "kill worker#%d", worker_id); DataHead event = {}; event.type = SW_SERVER_EVENT_SHUTDOWN; return send_to_worker_from_worker(worker, &event, sizeof(event), SW_PIPE_MASTER) != -1; } void Server::stop_async_worker(Worker *worker) { worker->shutdown(); if (worker->type == SW_EVENT_WORKER) { reset_worker_counter(worker); } // forced termination Reactor *reactor = sw_reactor(); if (reload_async == 0) { reactor->running = false; return; } // The worker process is shutting down now. if (reactor->wait_exit) { return; } // Separated from the event worker process pool SwooleWG.worker_copy = new Worker{}; *SwooleWG.worker_copy = *worker; SwooleWG.worker = worker; auto pipe_worker = get_worker_pipe_worker_in_message_bus(worker); if (pipe_worker && !pipe_worker->removed) { reactor->remove_read_event(pipe_worker); } if (is_base_mode()) { if (is_event_worker()) { if (worker->id == 0 && get_event_worker_pool()->running == 0) { if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, this); } if (onBeforeShutdown) { onBeforeShutdown(this); } } if (worker->pipe_master && !worker->pipe_master->removed) { reactor->remove_read_event(worker->pipe_master); } for (auto ls : ports) { reactor->del(ls->socket); } foreach_connection([reactor](Connection *conn) { if (!conn->peer_closed && !conn->socket->removed) { reactor->remove_read_event(conn->socket); } }); clear_timer(); } } else if (is_process_mode()) { WorkerStopMessage msg; msg.pid = getpid(); msg.worker_id = worker->id; if (get_event_worker_pool()->push_message(SW_WORKER_MESSAGE_STOP, &msg, sizeof(msg)) < 0) { swoole_sys_warning("failed to push WORKER_STOP message"); } } else if (is_thread_mode()) { if (is_event_worker()) { /** * The thread mode will use the master pipe to forward messages, * and it may listen for writable events on this pipe, * which need to be removed before the worker thread exits. */ SW_LOOP_N(worker_num) { if (i % reactor_num == reactor->id) { auto pipe_master = get_worker_pipe_master_in_message_bus(i); if (!pipe_master->removed) { reactor->remove_read_event(pipe_master); } } } /** * Only the readable events are removed; * at this point, there may still be ongoing events for sending data. * The connection will be completely closed only when the reactor is destroyed. */ foreach_connection([reactor](Connection *conn) { if (conn->reactor_id == reactor->id && !conn->peer_closed && !conn->socket->removed) { reactor->remove_read_event(conn->socket); } }); } } else { assert(0); } reactor->set_wait_exit(true); reactor->set_end_callback(Reactor::PRIORITY_TRY_EXIT, Worker_reactor_try_to_exit); SwooleWG.exit_time = ::time(nullptr); Worker_reactor_try_to_exit(reactor); } static void Worker_reactor_try_to_exit(Reactor *reactor) { Server *serv; if (sw_likely(swoole_get_worker_type() != SW_TASK_WORKER)) { serv = static_cast(reactor->ptr); } else { auto pool = static_cast(reactor->ptr); serv = static_cast(pool->ptr); } bool has_call_worker_exit_func = false; while (true) { if (reactor->if_exit()) { reactor->running = false; } else { if (serv->onWorkerExit && !has_call_worker_exit_func) { has_call_worker_exit_func = true; serv->onWorkerExit(serv, sw_worker()); continue; } int remaining_time = serv->max_wait_time - (::time(nullptr) - SwooleWG.exit_time); if (remaining_time <= 0) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, "worker exit timeout, forced termination"); reactor->running = false; } else { int timeout_msec = remaining_time * 1000; if (reactor->timeout_msec < 0 || reactor->timeout_msec > timeout_msec) { reactor->timeout_msec = timeout_msec; } } } break; } } void Server::drain_worker_pipe() const { for (uint32_t i = 0; i < worker_num + task_worker_num; i++) { Worker *worker = get_worker(i); if (sw_reactor()) { if (worker->pipe_worker) { sw_reactor()->drain_write_buffer(worker->pipe_worker); } if (worker->pipe_master) { sw_reactor()->drain_write_buffer(worker->pipe_master); } } } } void Server::clean_worker_connections(Worker *worker) { swoole_trace_log(SW_TRACE_WORKER, "clean connections"); sw_reactor()->destroyed = true; if (sw_likely(is_base_mode())) { foreach_connection([](Connection *conn) { close_connection(sw_reactor(), conn->socket); }); } else if (is_thread_mode()) { foreach_connection([worker](Connection *conn) { if (conn->reactor_id == worker->id) { close_connection(sw_reactor(), conn->socket); } }); } } /** * main loop [Worker] * Only used in SWOOLE_PROCESS mode */ int Server::start_event_worker(Worker *worker) { swoole_set_worker_id(worker->id); swoole_set_worker_type(SW_EVENT_WORKER); init_event_worker(worker); if (swoole_event_init(0) < 0) { return SW_ERR; } worker_signal_init(); Reactor *reactor = SwooleTG.reactor; /** * set pipe buffer size */ for (uint32_t i = 0; i < worker_num + task_worker_num; i++) { const Worker *_worker = get_worker(i); if (_worker->pipe_master) { _worker->pipe_master->buffer_size = UINT_MAX; } if (_worker->pipe_worker) { _worker->pipe_worker->buffer_size = UINT_MAX; } } worker->pipe_worker->set_nonblock(); reactor->ptr = this; reactor->add(worker->pipe_worker, SW_EVENT_READ); reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, Worker_onPipeReceive); if (dispatch_mode == DISPATCH_CO_CONN_LB || dispatch_mode == DISPATCH_CO_REQ_LB) { reactor->set_end_callback(Reactor::PRIORITY_WORKER_CALLBACK, [worker](Reactor *) { worker->coroutine_num = Coroutine::count(); }); } worker_start_callback(worker); // main loop const auto rv = reactor->wait(); // drain pipe buffer drain_worker_pipe(); // reactor free swoole_event_free(); // worker shutdown worker_stop_callback(worker); delete buffer_pool; return rv; } /** * [Worker/TaskWorker/Master] Send data to ReactorThread */ ssize_t Server::send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id) const { Socket *pipe_sock = get_reactor_pipe_socket(session_id, ev_data->info.reactor_id); if (swoole_event_is_available()) { return swoole_event_write(pipe_sock, ev_data, sendn); } else { return pipe_sock->send_sync(ev_data, sendn); } } /** * send message from worker to another worker */ ssize_t Server::send_to_worker_from_worker(const Worker *dst_worker, const void *buf, size_t len, int flags) { return dst_worker->send_pipe_message(buf, len, flags); } /** * receive data from reactor * This function is intended solely for process mode; in thread or base mode, `ReactorThread_onRead()` will be executed. */ static int Worker_onPipeReceive(Reactor *reactor, Event *event) { auto *serv = static_cast(reactor->ptr); auto *pipe_buffer = serv->get_worker_message_bus()->get_buffer(); if (serv->get_worker_message_bus()->read(event->socket) <= 0) { return SW_OK; } serv->worker_accept_event(&pipe_buffer->info); serv->get_worker_message_bus()->pop(); return SW_OK; } } // namespace swoole ================================================ FILE: src/wrapper/event.cc ================================================ /** +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_reactor.h" #include "swoole_client.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" using swoole::Callback; using swoole::Reactor; using swoole::ReactorHandler; using swoole::network::Socket; int swoole_event_init(int flags) { auto *reactor = new Reactor(SW_REACTOR_MAXEVENTS); if (!reactor->ready()) { return SW_ERR; } if (flags & SW_EVENTLOOP_WAIT_EXIT) { reactor->wait_exit = true; } swoole::coroutine::Socket::init_reactor(reactor); swoole::coroutine::System::init_reactor(reactor); swoole::network::Client::init_reactor(reactor); SwooleTG.reactor = reactor; return SW_OK; } int swoole_event_add(Socket *socket, int events) { return SwooleTG.reactor->add(socket, events); } int swoole_event_add_or_update(Socket *_socket, int event) { if (event == SW_EVENT_READ) { return SwooleTG.reactor->add_read_event(_socket); } else if (event == SW_EVENT_WRITE) { return SwooleTG.reactor->add_write_event(_socket); } else { assert(0); return SW_ERR; } } int swoole_event_set(Socket *socket, int events) { return SwooleTG.reactor->set(socket, events); } int swoole_event_del(Socket *socket) { return SwooleTG.reactor->del(socket); } int swoole_event_wait() { Reactor *reactor = SwooleTG.reactor; int retval = 0; if (!reactor->wait_exit or !reactor->if_exit()) { retval = reactor->wait(); } swoole_event_free(); return retval; } int swoole_event_free() { if (!SwooleTG.reactor) { return SW_ERR; } delete SwooleTG.reactor; SwooleTG.reactor = nullptr; return SW_OK; } void swoole_event_defer(const Callback &cb, void *private_data) { SwooleTG.reactor->defer(cb, private_data); } ssize_t swoole_event_write(Socket *socket, const void *data, size_t len) { return SwooleTG.reactor->write(SwooleTG.reactor, socket, data, len); } ssize_t swoole_event_writev(Socket *socket, const iovec *iov, size_t iovcnt) { return SwooleTG.reactor->writev(SwooleTG.reactor, socket, iov, iovcnt); } void swoole_event_set_handler(const int fd_type, const int event, const ReactorHandler handler) { SwooleTG.reactor->set_handler(fd_type, event, handler); } bool swoole_event_isset_handler(const int fd_type, const int event) { return SwooleTG.reactor->isset_handler(fd_type, event); } bool swoole_event_is_available() { return SwooleTG.reactor && !SwooleTG.reactor->destroyed; } bool swoole_event_is_running() { return SwooleTG.reactor && SwooleTG.reactor->running; } Socket *swoole_event_get_socket(int fd) { return SwooleTG.reactor->get_socket(fd); } ================================================ FILE: src/wrapper/http.cc ================================================ /** +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_http.h" #include "swoole_server.h" #include "swoole_llhttp.h" #include "thirdparty/multipart_parser.h" namespace swoole { namespace http_server { static int http_request_on_url(llhttp_t *parser, const char *at, size_t length); static int http_request_on_body(llhttp_t *parser, const char *at, size_t length); static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length); static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length); static int http_request_on_headers_complete(llhttp_t *parser); static int http_request_message_complete(llhttp_t *parser); static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_complete(multipart_parser *p); static int multipart_body_on_data_end(multipart_parser *p); // clang-format off static constexpr llhttp_settings_t http_parser_settings = { nullptr, // on_message_begin nullptr, // on_protocol http_request_on_url, // on_url nullptr, // on_status nullptr, // on_method nullptr, // on_version http_request_on_header_field, // on_header_field http_request_on_header_value, // on_header_value nullptr, // on_chunk_extension_name nullptr, // on_chunk_extension_value http_request_on_headers_complete, // on_headers_complete http_request_on_body, // on_body http_request_message_complete, // on_message_complete nullptr, // on_protocol_complete nullptr, // on_url_complete nullptr, // on_status_complete nullptr, // on_method_complete nullptr, // on_version_complete nullptr, // on_header_field_complete nullptr, // on_header_value_complete nullptr, // on_chunk_extension_name_complete nullptr, // on_chunk_extension_value_complete nullptr, // on_chunk_header nullptr, // on_chunk_complete nullptr, // on_reset }; static constexpr multipart_parser_settings mt_parser_settings = { multipart_body_on_header_field, multipart_body_on_header_value, multipart_body_on_data, nullptr, multipart_body_on_header_complete, multipart_body_on_data_end, nullptr, }; // clang-format on struct ContextImpl { llhttp_t parser; multipart_parser *mt_parser; std::string current_header_name; std::string current_input_name; std::string current_form_data_name; String *form_data_buffer; bool completed = false; bool is_beginning = true; bool parse(Context &ctx, const char *at, size_t length) { swoole_llhttp_parser_init(&parser, HTTP_REQUEST, static_cast(&ctx)); swoole_llhttp_parser_execute(&parser, &http_parser_settings, at, length); return parser.error == HPE_OK && completed; } }; static int http_request_on_url(llhttp_t *parser, const char *at, size_t length) { const char *query_start = (const char *) memchr(at, '?', length); size_t path_len = query_start ? (size_t) (query_start - at) : length; auto *ctx = static_cast(parser->data); ctx->request_path = std::string(at, path_len); if (!query_start || (length - path_len) <= 1) { return 0; } const char *query_str = query_start + 1; size_t query_len = length - path_len - 1; ctx->query_string = std::string(query_str, query_len); return 0; } static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length) { auto *ctx = static_cast(parser->data); ctx->impl->current_header_name = std::string(at, length); return 0; } static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) { auto *ctx = static_cast(parser->data); ContextImpl *impl = ctx->impl; ctx->headers[impl->current_header_name] = std::string(at, length); if ((parser->method == HTTP_POST || parser->method == HTTP_PUT || parser->method == HTTP_DELETE || parser->method == HTTP_PATCH) && SW_STRCASEEQ(impl->current_header_name.c_str(), impl->current_header_name.length(), "content-type")) { if (SW_STR_ISTARTS_WITH(at, length, "application/x-www-form-urlencoded")) { ctx->post_form_urlencoded = true; } else if (SW_STR_ISTARTS_WITH(at, length, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; if (!parse_multipart_boundary(at, length, offset, &boundary_str, &boundary_len)) { return -1; } impl->mt_parser = multipart_parser_init(boundary_str, boundary_len, &mt_parser_settings); impl->form_data_buffer = new String(SW_BUFFER_SIZE_STD); impl->mt_parser->data = ctx; swoole_trace_log(SW_TRACE_HTTP, "form_data, boundary_str=%s", boundary_str); } } return 0; } static int http_request_on_headers_complete(llhttp_t *parser) { auto *ctx = static_cast(parser->data); ctx->version = parser->http_major * 100 + parser->http_minor; ctx->server_protocol = std::string(ctx->version == 101 ? "HTTP/1.1" : "HTTP/1.0"); ctx->keepalive = llhttp_should_keep_alive(parser); return 0; } static int http_request_on_body(llhttp_t *parser, const char *at, size_t length) { if (length == 0) { return 0; } auto *ctx = static_cast(parser->data); auto *impl = ctx->impl; if (impl->mt_parser != nullptr) { multipart_parser *multipart_parser = impl->mt_parser; if (impl->is_beginning) { /* Compatibility: some clients may send extra EOL */ do { if (*at != '\r' && *at != '\n') { break; } at++; length--; } while (length != 0); impl->is_beginning = false; } size_t n = multipart_parser_execute(multipart_parser, at, length); if (sw_unlikely(n != length)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %zu/%zu bytes processed", n, length); } } else { ctx->body.append(at, length); } return impl->completed ? HPE_PAUSED : 0; } static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length) { auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; return http_request_on_header_field(&impl->parser, at, length); } static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length) { auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; const char *header_name = impl->current_header_name.c_str(); size_t header_len = impl->current_header_name.length(); if (SW_STRCASEEQ(header_name, header_len, "content-disposition")) { std::unordered_map info; ParseCookieCallback cb = [&info](char *key, size_t key_len, char *value, size_t value_len) { info[std::string(key, key_len)] = std::string(value, value_len); return true; }; parse_cookie(at, length, cb); auto name = info.find("name"); auto filename = info.find("filename"); if (filename == info.end()) { impl->current_form_data_name = name->second; } else { impl->current_input_name = filename->second; } } else if (SW_STRCASEEQ(header_name, header_len, SW_HTTP_UPLOAD_FILE)) { /** * When the "SW_HTTP_UPLOAD_FILE" header appears in the request, it indicates that the uploaded file has been * saved in a temporary file. The binary content in the message body will be replaced with the temporary * filename. However, the Content-Length still reflects the original message size, causing llhttp to believe * there is still data to be received. As a result, llhttp fails to trigger the message callback. Therefore, we * need to set `ctx->completed = 1` to indicate that the message processing is complete. */ impl->completed = true; ctx->files[impl->current_form_data_name] = std::string(at, length); } return 0; } static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length) { auto *ctx = static_cast(p->data); const auto *impl = ctx->impl; if (!impl->current_form_data_name.empty()) { impl->form_data_buffer->append(at, length); return 0; } if (p->fp == nullptr) { return 0; } ssize_t n = fwrite(at, sizeof(char), length, p->fp); if (n != (off_t) length) { ctx->files[impl->current_form_data_name] = "ERROR(1)"; fclose(p->fp); p->fp = nullptr; swoole_sys_warning("write upload file failed"); } return 0; } static int multipart_body_on_header_complete(multipart_parser *p) { auto *ctx = static_cast(p->data); const auto *impl = ctx->impl; if (impl->current_input_name.empty()) { return 0; } if (ctx->files.find(impl->current_form_data_name) != ctx->files.end()) { return 0; } char file_path[SW_HTTP_UPLOAD_TMPDIR_SIZE] = "/tmp/swoole.upfile.XXXXXX"; int tmpfile = swoole_tmpfile(file_path); if (tmpfile < 0) { return 0; } FILE *fp = fdopen(tmpfile, "wb+"); if (fp == nullptr) { swoole_sys_warning("fopen(%s) failed", file_path); return 0; } p->fp = fp; ctx->files[impl->current_form_data_name] = file_path; return 0; } static int multipart_body_on_data_end(multipart_parser *p) { auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; if (!impl->current_form_data_name.empty()) { ctx->form_data[impl->current_form_data_name] = impl->form_data_buffer->to_std_string(); impl->form_data_buffer->clear(); } if (p->fp != nullptr) { fclose(p->fp); p->fp = nullptr; } impl->current_header_name.clear(); impl->current_input_name.clear(); impl->current_form_data_name.clear(); return 0; } static int http_request_message_complete(llhttp_t *p) { const auto *ctx = static_cast(p->data); auto *impl = ctx->impl; if (impl->form_data_buffer) { delete impl->form_data_buffer; impl->form_data_buffer = nullptr; } impl->completed = true; return HPE_PAUSED; } bool Context::end(const char *data, size_t length) { char buf[1024]; sw_tg_buffer()->clear(); sw_tg_buffer()->append(SW_STRL("HTTP/1.1 ")); sw_tg_buffer()->append(get_status_message(response.code)); sw_tg_buffer()->append(SW_STRL("\r\n")); if (length > 0) { response.headers["Content-Length"] = std::to_string(length); } for (auto &iter : response.headers) { size_t n = sw_snprintf(buf, sizeof(buf), "%s: %s\r\n", iter.first.c_str(), iter.second.c_str()); sw_tg_buffer()->append(buf, n); } sw_tg_buffer()->append(SW_STRL("\r\n")); if (!server_->send(session_id_, sw_tg_buffer()->str, sw_tg_buffer()->length)) { swoole_warning("failed to send HTTP header"); return false; } if (length > 0 && !server_->send(session_id_, data, length)) { swoole_warning("failed to send HTTP body"); return false; } if (!keepalive) { server_->close(session_id_, false); } return true; } Context::~Context() { for (auto &kv : files) { if (file_exists(kv.second)) { unlink(kv.second.c_str()); } } } static std::function http_server_on_request; std::shared_ptr listen(const std::string &addr, const std::function &cb, int mode) { auto server = std::make_shared(static_cast(mode)); auto index = addr.find(':'); if (index == std::string::npos) { swoole_warning("incorrect server listening address"); return nullptr; } std::string host = addr.substr(0, index); if (host.empty()) { host = "0.0.0.0"; } int port = sw_atoi(addr.substr(index + 1).c_str()); auto port_object = server->add_port(SW_SOCK_TCP, host.c_str(), port); if (!port_object) { return nullptr; } http_server_on_request = cb; server->onReceive = [](Server *server, RecvData *req) { SessionId session_id = req->info.fd; Connection *conn = server->get_connection_verify_no_ssl(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); return SW_OK; } ContextImpl impl; Context ctx(server, session_id, &impl); if (impl.parse(ctx, req->data, req->info.len)) { http_server_on_request(ctx); } else { server->send(req->session_id(), SW_STRL(SW_HTTP_BAD_REQUEST_PACKET)); } return SW_OK; }; port_object->open_http_protocol = true; if (server->create() == SW_ERR) { return nullptr; } return server; } } // namespace http_server } // namespace swoole ================================================ FILE: src/wrapper/timer.cc ================================================ /** +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "swoole_timer.h" using swoole::sec2msec; using swoole::Timer; using swoole::TimerCallback; using swoole::TimerNode; bool swoole_timer_is_available() { return SwooleTG.timer != nullptr; } TimerNode *swoole_timer_add(double timeout, bool persistent, const TimerCallback &callback, void *private_data) { if (sw_unlikely(timeout < SW_TIMER_MIN_SEC)) { timeout = SW_TIMER_MIN_SEC; } return swoole_timer_add(sec2msec(timeout), persistent, callback, private_data); } Timer *swoole_timer_create(bool manually_trigger) { SwooleTG.timer = new Timer(manually_trigger); return SwooleTG.timer; } SW_API int64_t swoole_timer_get_next_msec() { if (sw_unlikely(!swoole_timer_is_available())) { return -1; } return SwooleTG.timer->get_next_msec(); } TimerNode *swoole_timer_add(long ms, bool persistent, const TimerCallback &callback, void *private_data) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_timer_create(false); } return SwooleTG.timer->add(ms, persistent, private_data, callback); } bool swoole_timer_del(TimerNode *tnode) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } return SwooleTG.timer->remove(tnode); } void swoole_timer_delay(TimerNode *tnode, long delay_ms) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return; } return SwooleTG.timer->delay(tnode, delay_ms); } long swoole_timer_after(long ms, const TimerCallback &callback, void *private_data) { if (ms <= 0) { swoole_warning("Timer must be greater than 0"); return SW_ERR; } const auto tnode = swoole_timer_add(ms, false, callback, private_data); if (sw_unlikely(!tnode)) { return SW_ERR; } return tnode->id; } long swoole_timer_tick(long ms, const TimerCallback &callback, void *private_data) { if (sw_unlikely(ms <= 0)) { swoole_warning("Timer must be greater than 0"); return SW_ERR; } const auto tnode = swoole_timer_add(ms, true, callback, private_data); if (sw_unlikely(!tnode)) { return SW_ERR; } return tnode->id; } bool swoole_timer_exists(long timer_id) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } TimerNode *tnode = SwooleTG.timer->get(timer_id); return (tnode && !tnode->removed); } bool swoole_timer_clear(long timer_id) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } return SwooleTG.timer->remove(SwooleTG.timer->get(timer_id)); } TimerNode *swoole_timer_get(long timer_id) { if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return nullptr; } return SwooleTG.timer->get(timer_id); } void swoole_timer_free() { if (!swoole_timer_is_available()) { swoole_print_backtrace(); swoole_warning("timer is not available"); return; } delete SwooleTG.timer; SwooleTG.timer = nullptr; } void swoole_timer_select() { if (sw_likely(swoole_timer_is_available())) { SwooleTG.timer->select(); } } ================================================ FILE: tests/CONTRIBUTION ================================================ Tianfeng Han @ Swoole Group Twosee @ Swoole Group Xiaofeng Chu @ Zan Group ================================================ FILE: tests/README.md ================================================ # PHP Unit-test Run these tests to make certain that the swoole extension you installed can work well. ## Preparation try to run `./init` to initialize the databases. | | mysql | redis | | ------------- | ------------------------------------- | ----------------------------------- | | path (env) | $MYSQL_SERVER_PATH | $REDIS_SERVER_PATH | | path (actions) | ${actions}/data/run/mysqld/mysqld.sock | ${actions}/data/run/redis/redis.sock | | host (raw) | 127.0.0.1 | 127.0.0.1 | | host (docker) | mysql | redis | | port | 3306 | 6379 | | user | root | - | | password | root | root (optional) | | database | test | 0 | ## How to run 1. just run `./start.sh` 2. or use`./start.sh ./swoole_*` command to run a part of the tests 3. or use `./start.sh base` command to run base tests ## Defaults | Config | Enable | | --------- | -------- | | show-diff | yes | | show-mem | yes | | show-slow | 1000(ms) | ## Log files | suffix | intro | | ------ | --------------------------------------------- | | diff | show the differents between output and expect | | out | script output | | exp | expect output | | log | all above | | php | php temp script file | ## Clean Run `./clean` to remove all of the tests log files. ## Contribute the test script Run `./new [test-script-filename]` E.g. : `./new ./swoole_coroutine/co_sleep.phpt` It will generate the test script file and auto open on your ide (MacOS only). ![](https://cdn.jsdelivr.net/gh/sy-records/staticfile/images/swoole/generate-example.gif) ## Code Style `PSR1/PSR2` ================================================ FILE: tests/clean ================================================ #!/usr/bin/env php = 80400) { $exit_status_list = [ 1, 'exit', 0, ]; } else { $exit_status_list = [ 'undef', null, true, false, 1, 1.1, 'exit', ['exit' => 'ok'], (object)['exit' => 'ok'], STDIN, 0 ]; } function route() { controller(); } function controller() { your_code(); } function your_code() { global $exit_status_list; co::sleep(.001); $exit_status = array_shift($exit_status_list); if ($exit_status === 'undef') { exit; } else { exit($exit_status); } } $chan = new Swoole\Coroutine\Channel; go(function () use ($chan, $exit_status_list) { foreach ($exit_status_list as $val) { $chan->push($val); } }); for ($i = 0; $i < count($exit_status_list); $i++) { go(function () use ($exit_status_list, $chan) { try { // in coroutine route(); } catch (\Swoole\ExitException $e) { Assert::assert($e->getFlags() & SWOOLE_EXIT_IN_COROUTINE); $exit_status = $chan->pop(); if (PHP_VERSION_ID < 80400) { $exit_status = $exit_status === 'undef' ? null : $exit_status; } Assert::same($e->getStatus(), $exit_status); var_dump($e->getStatus()); // exit coroutine return; } echo "never here\n"; }); } Swoole\Event::wait(); ================================================ FILE: tests/include/api/http_server.php ================================================ getFreePort(), SWOOLE_BASE); $http->set(array( 'log_file' => '/dev/null', "http_parse_post" => 1, "upload_tmp_dir" => "/tmp", )); $http->on("WorkerStart", function (Swoole\Server $serv) { global $pm; if ($pm) { $pm->wakeup(); } }); $http->on('request', function ($request, Swoole\Http\Response $response) use ($pm) { $route = $request->server['request_uri']; if ($route == '/info') { $response->end($request->header['user-agent']); return; } elseif ($route == '/cookies') { $response->end(@json_encode($request->cookie)); return; } elseif ($route == '/get') { $response->end(@json_encode($request->get)); return; } elseif ($route == '/post') { $response->end(@json_encode($request->post)); return; } elseif ($route == '/get_file') { $response->sendfile(TEST_IMAGE); return; } elseif ($route == '/upload_file') { $response->end(json_encode([ 'files' => $request->files, 'md5' => md5_file($request->files['test_jpg']['tmp_name']), 'post' => $request->post ])); return; } elseif ($route == '/gzip') { $response->gzip(5); $content = co::readFile(__DIR__ . '/../../../README.md'); $response->end($content); return; } else { return; } }); $http->start(); ================================================ FILE: tests/include/api/http_test_cases.php ================================================ getFreePort()); $cli->setHeaders(['Accept-Encoding' => 'gzip',]); $cli->get('/html?bytes=' . rand(8192, 65536)); Assert::eq($cli->getHeaders()['content-encoding'], 'gzip'); $cli = new HttpClient('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['Accept-Encoding' => 'gzip',]); $cli->get('/json?bytes=' . rand(8192, 65536)); Assert::eq($cli->getHeaders()['content-encoding'], 'gzip'); $cli = new HttpClient('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['Accept-Encoding' => 'gzip',]); $cli->get('/raw?bytes=' . rand(8192, 65536)); Assert::assert(!isset($cli->getHeaders()['content-encoding'])); }); } function http2_compression_types_test(ProcessManager $pm) { run(function () use ($pm) { $cli = new Http2Client('127.0.0.1', $pm->getFreePort()); Assert::true($cli->connect()); $req1 = new Http2Request; $req1->path = '/html?bytes=' . rand(8192, 65536); $req1->headers = ['Accept-Encoding' => 'gzip',]; $stream1 = $cli->send($req1); $req2 = new Http2Request; $req2->path = '/json?bytes=' . rand(8192, 65536); $req2->headers = ['Accept-Encoding' => 'gzip',]; $stream2 = $cli->send($req2); $req3 = new Http2Request; $req3->path = '/raw?bytes=' . rand(8192, 65536); $req3->headers = ['Accept-Encoding' => 'gzip',]; $stream3 = $cli->send($req3); $n = 3; $list = []; while($n--) { $resp = $cli->recv(); $list[$resp->streamId] = $resp; } Assert::eq($list[$stream1]->headers['content-encoding'], 'gzip'); Assert::eq($list[$stream2]->headers['content-encoding'], 'gzip'); Assert::assert(!isset($list[$stream3]->headers['content-encoding'])); }); } /** * @param ProcessManager $pm * @param array $sizes * @throw RuntimeException */ function form_data_test(ProcessManager $pm, array $sizes = []) { if (count($sizes) == 0) { throw new \RuntimeException("size array cannot be empty"); } run(function () use ($pm, $sizes) { $client = new Client(SWOOLE_SOCK_TCP); Assert::true($client->connect('127.0.0.1', $pm->getFreePort())); $req = file_get_contents(SOURCE_ROOT_PATH . '/core-tests/fuzz/cases/req1.bin'); $len = strlen($req); $begin = 0; foreach ($sizes as $end) { if ($end >= $len) { throw new \RuntimeException("error offset[$end]"); } Assert::eq($client->send(substr($req, $begin, $end)), $end - $begin); usleep(10000); } $end = $sizes[count($sizes) - 1]; if ($len - $end > 0) { Assert::eq($client->send(substr($req, $end)), $len - $end); usleep(10000); } $resp = ''; $length = 0; $header = ''; while (true) { $data = $client->recv(); if ($data == false) { throw new RuntimeException("recv failed, error: " . $client->errMsg . ", resp: " . $resp); } $resp .= $data; if ($length == 0) { $crlf_pos = strpos($resp, "\r\n\r\n"); if ($crlf_pos === false) { continue; } $header = substr($resp, 0, $crlf_pos); if (!preg_match('#Content-Length:\s(\d+)#i', $header, $match)) { throw new RuntimeException("no match Content-Length"); } $length = strlen($header) + 4 + $match[1]; } if (strlen($resp) == $length) { break; } } Assert::assert($header); $body = substr($resp, strlen($header) + 4); if (!$body) { var_dump($header); } Assert::assert($body); $data = json_decode($body); Assert::assert(is_object($data)); Assert::minLength($data->test, 80); Assert::minLength($data->hello, 120); Assert::minLength($data->world, 1024); $client->close(); }); $pm->kill(); echo "DONE\n"; } function getHttpBody(string $s): string { return str_replace(EOF, '', explode("\r\n\r\n", $s)[1] ?? ''); } function generateChunkBody(array $a): string { $s = ''; foreach ($a as $c) { $s .= dechex(strlen($c)) . "\r\n" . $c . "\r\n"; } return $s . "0\r\n"; } function chunked_request(ProcessManager $pm) { run(function () use ($pm) { $request_empty_chunked = "DELETE /locks?password=9c1858261b4337b49af4fb8c57a9ec98&lock_id=1&amount=1.2&c=6331b32ac32f4c128ce0016114e11dbd&lang=zh_CN HTTP/1.1\r\n" . "x-real-ip: 10.2.100.1\r\n" . "x-forwarded-server: kitchen.pool-x.net\r\n" . "accept: application/json\r\n" . "origin: http://pool-x.net\r\n" . "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36\r\n" . "sec-fetch-site: cross-site\r\n" . "sec-fetch-mode: cors\r\n" . "referer: http://pool-x.net/assets/staking\r\n" . "accept-encoding: gzip, deflate, br\r\n" . "accept-language: zh-CN,zh;q=0.9,be;q=0.8,ru;q=0.7\r\n" . "cookie: gr_user_id=1696256d-0a68-486f-a507-74191e74dbc6; grwng_uid=2682d2d1-4de3-407d-9946-5df333a44bef; _ga=GA1.2.224995769.1577363886; X-TRACE=w60NOEhe/g1irg2+SHF63xNYUS2H/vJUtP40DAUMqGQ=; a46016b4ef684522_gr_last_sent_cs1=265sy72; a46016b4ef684522_gr_session_id=45d1c2ec-dd54-4005-af9e-a01ccad4473b; a46016b4ef684522_gr_last_sent_sid_with_cs1=45d1c2ec-dd54-4005-af9e-a01ccad4473b; a46016b4ef684522_gr_session_id_45d1c2ec-dd54-4005-af9e-a01ccad4473b=true; SESSION=ZGExNmI1ODYtZTQzNi00MWQ0LTk1NzAtNzYzOTE3NDFjZDc5; _gid=GA1.2.951149480.1577691293; a46016b4ef684522_gr_cs1=265sy72\r\n" . "x-domain: kitchen.pool-x.net\r\n" . "x-session-id: da16b586-e436-41d4-9570-76391741cd79\r\n" . "x-device-id: \r\n" . "x-origin-domain-id: pool\r\n" . "x-forwarded-proto: http\r\n" . "uber-trace-id: ffaf3497a6deee40%3A8afa1564e1e0783f%3Affaf3497a6deee40%3A1\r\n" . "x-forwarded-port: 80\r\n" . "x-forwarded-for: 127.0.0.1\r\n" . "x-user-id: 5dd5fbc9e316c178d6930678\r\n" . "x-domain-id: pool\r\n" . "kyc-country: \r\n" . "kyc-status: \r\n" . "x-forwarded-host: kitchen.pool-x.net\r\n" . "x-forwarded-prefix: /pool-staking\r\n" . "gateway-type: WEB\r\n" . "lang: zh_CN\r\n" . "Transfer-Encoding: chunked\r\n" . "Host: 10.2.1.51:9526\r\n" . "Connection: Keep-Alive\r\n" . "\r\n" . "0\r\n" . "\r\n"; $request_zero_length = "GET /locks?currency=&start_at=1576771200000&end_at=1577721599999&pageSize=20&page=1&c=6331b32ac32f4c128ce0016114e11dbd&lang=zh_CN&_t=1577694714586 HTTP/1.1\r\n" . "x-real-ip: 10.2.100.1\r\n" . "x-forwarded-server: kitchen.pool-x.net\r\n" . "accept: application/json\r\n" . "origin: http://pool-x.net\r\n" . "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36\r\n" . "sec-fetch-site: cross-site\r\n" . "sec-fetch-mode: cors\r\n" . "referer: http://pool-x.net/assets/staking\r\n" . "accept-encoding: gzip, deflate, br\r\n" . "accept-language: zh-CN,zh;q=0.9,be;q=0.8,ru;q=0.7\r\n" . "cookie: gr_user_id=1696256d-0a68-486f-a507-74191e74dbc6; grwng_uid=2682d2d1-4de3-407d-9946-5df333a44bef; _ga=GA1.2.224995769.1577363886; X-TRACE=w60NOEhe/g1irg2+SHF63xNYUS2H/vJUtP40DAUMqGQ=; a46016b4ef684522_gr_last_sent_cs1=265sy72; a46016b4ef684522_gr_session_id=45d1c2ec-dd54-4005-af9e-a01ccad4473b; a46016b4ef684522_gr_last_sent_sid_with_cs1=45d1c2ec-dd54-4005-af9e-a01ccad4473b; a46016b4ef684522_gr_session_id_45d1c2ec-dd54-4005-af9e-a01ccad4473b=true; SESSION=ZGExNmI1ODYtZTQzNi00MWQ0LTk1NzAtNzYzOTE3NDFjZDc5; _gid=GA1.2.951149480.1577691293; a46016b4ef684522_gr_cs1=265sy72\r\n" . "x-domain: kitchen.pool-x.net\r\n" . "x-session-id: da16b586-e436-41d4-9570-76391741cd79\r\n" . "x-device-id: \r\n" . "x-origin-domain-id: pool\r\n" . "x-forwarded-proto: http\r\n" . "uber-trace-id: df854c374e6d4fde%3Ada6b1dc2e4e112b5%3Adf854c374e6d4fde%3A0\r\n" . "x-forwarded-port: 80\r\n" . "x-forwarded-for: 127.0.0.1\r\n" . "x-user-id: 5dd5fbc9e316c178d6930678\r\n" . "x-domain-id: pool\r\n" . "kyc-country: \r\n" . "kyc-status: \r\n" . "x-forwarded-host: kitchen.pool-x.net\r\n" . "x-forwarded-prefix: /pool-staking\r\n" . "gateway-type: WEB\r\n" . "lang: zh_CN\r\n" . "Content-Length: 0\r\n" . "Host: 10.2.1.51:9526\r\n" . "Connection: Keep-Alive\r\n" . "\r\n"; $request_chunked_body_array = ['FOO', 'BAR', 'CHAR', str_repeat('Z', mt_rand(10, 1000))]; $request_chunked_body = generateChunkBody($request_chunked_body_array); $request_chunked = "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n{$request_chunked_body}\r\n"; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($socket->connect('127.0.0.1', $pm->getFreePort())); Assert::true($socket->setProtocol([ 'open_eof_check' => true, 'package_eof' => EOF ])); /* chunked */ $ret = $socket->sendAll($request_empty_chunked); Assert::same($ret, strlen($request_empty_chunked)); $ret = $socket->recvPacket(); Assert::isEmpty(getHttpBody($ret)); /* pipeline */ for ($n = MAX_REQUESTS_LOW; $n--;) { $ret = $socket->sendAll($request_zero_length); Assert::same($ret, strlen($request_zero_length)); } for ($n = MAX_REQUESTS_LOW; $n--;) { $ret = $socket->recvPacket(); Assert::same(getHttpBody($ret), getHttpBody($request_zero_length)); } /* chunked */ for ($n = MAX_REQUESTS_LOW; $n--;) { $ret = $socket->sendAll($request_chunked); Assert::same($ret, strlen($request_chunked)); $ret = $socket->recvPacket(); Assert::same(getHttpBody($ret), implode('', $request_chunked_body_array)); } }); echo "SUCCESS\n"; $pm->kill(); } function http_get_with_co_socket(string $domain, ?callable $cb = null) { $cli = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); $cli->setProtocol(['open_ssl' => true,]); if (!$cli->connect($domain, 443)) { echo "ERROR\n"; } $http = "GET / HTTP/1.1\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: $domain\r\nConnection: Keep-Alive\r\n" . "Keep-Alive: on\r\n\r\n"; if (!$cli->send($http)) { echo "ERROR\n"; } $content = ''; $length = 0; while (true) { $read = $cli->recv(); if (empty($read)) { var_dump($read); break; } $content .= $read; if ($length == 0) { if (preg_match('#Content-Length: (\d+)#i', $content, $match)) { $length = intval($match[1]); } } $header_length = strpos($content, "\r\n\r\n"); if (strlen($content) == $length + $header_length + 4) { break; } } if ($cb) { $cb($cli, $content); } $cli->close(); return $content; } ================================================ FILE: tests/include/api/swoole_callback/swoole_cannot_destroy_active_lambda_function.php ================================================ on("error", function($cli) { echo "error"; }); $client->on("close", function($cli) { echo "close"; }); $client->on("connect", function($cli) use($str, $onRecv) { send($str, $onRecv); }); } // !!! Fatal error: Cannot destroy active lambda swoole_function $client->on("receive", $onRecv); if ($client->isConnected()) { $client->send("PING"); } else { $client->connect("127.0.0.1", 9090); } } ================================================ FILE: tests/include/api/swoole_client/connect_timeout.php ================================================ on("connect", function(Swoole\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Client $cli) { echo "connect timeout\n"; }); $cli->on("close", function(Swoole\Client $cli) { echo "close\n"; }); $cli->connect("11.11.11.11", 9000, 0.5); ================================================ FILE: tests/include/api/swoole_client/connect_twice.php ================================================ on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); function refcount($var) { ob_start(); debug_zval_dump($var); preg_match('/refcount\((?\d)\)/', ob_get_clean(), $matches); return intval($matches["refcount"]) - 3; } @$cli->connect("11.11.11.11", 9000, 0.1); @$cli->connect("11.11.11.11", 9000, 0.1); @$cli->connect("11.11.11.11", 9000, 0.1); @$cli->connect("11.11.11.11", 9000, 0.1); @$cli->connect("11.11.11.11", 9000, 0.1); Swoole\Event::wait(); // xdebug_debug_zval("cli"); // echo refcount($cli); // php7无效 ================================================ FILE: tests/include/api/swoole_client/http_get.php ================================================ connect('httpbin.org', 80, 10)); Assert::assert($client->send("GET / HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n")); $resp = ''; while (true) { $data = $client->recv(); if ($data === '' || $data === false) { break; } $resp .= $data; } Assert::assert(str_starts_with($resp, 'HTTP/1.1 200 OK')); Assert::assert(str_contains($resp, 'https://github.com/requests/httpbin')); } ================================================ FILE: tests/include/api/swoole_client/opcode_client.php ================================================ set([ 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ])); $cli->on("connect", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); Assert::true($cli->isConnected()); }); $cli->on("receive", function(Swoole\Client $cli, $data){ $cli->close(); Assert::false($cli->isConnected()); }); $cli->on("error", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); echo "ERROR"; }); $cli->on("close", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); echo "CLOSE"; }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT); $cli->timeo_id = Swoole\Timer::after(1000, function() use($cli) { debug_log("connect timeout"); $cli->close(); Assert::false($cli->isConnected()); }); ================================================ FILE: tests/include/api/swoole_client/simple_client.php ================================================ set([ // TODO test // 'open_eof_check' => true, // 'package_eof' => "\r\n\r\n", // TODO // "socket_buffer_size" => 1, ])); $cli->on("connect", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); // TODO getSocket BUG // assert(is_resource($cli->getSocket())); /* $cli->getSocket(); // Warning: swoole_client_async::getSocket(): unable to obtain socket family Error: Bad file descriptor[9]. $cli->getSocket(); */ Assert::true($cli->isConnected()); $cli->send(RandStr::gen(1024, RandStr::ALL)); // $cli->sendfile(__DIR__.'/test.txt'); }); $cli->on("receive", function(Swoole\Client $cli, $data){ $recv_len = strlen($data); debug_log("receive: len $recv_len"); $cli->send(RandStr::gen(1024, RandStr::ALL)); $cli->close(); Assert::false($cli->isConnected()); }); $cli->on("error", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("error"); }); $cli->on("close", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("close"); }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT); $cli->timeo_id = Swoole\Timer::after(1000, function() use($cli) { debug_log("connect timeout"); $cli->close(); Assert::false($cli->isConnected()); }); ================================================ FILE: tests/include/api/swoole_client/socket_free.php ================================================ getSocket()); echo "close fd <$fd>\n"; } function onError(Swoole\Client $cli) { $fd = \EventUtil::getSocketFd($cli->getSocket()); echo "error fd <$fd>\n"; } $host = "127.0.0.1"; $port = 8050; $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); $cli->on("receive", function(Swoole\Client $cli, $data){ }); $cli->on("error", "onError"); $cli->on("close", "onClose"); $cli->on("connect", function(Swoole\Client $cli) use($host, $port) { $fd = \EventUtil::getSocketFd($cli->getSocket()); echo "connected fd <$fd>\n"; $cli->close(); // close(fd) // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= $newCli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); $newCli->on("receive", function(Swoole\Client $cli, $data){ }); $newCli->on("error", "onError"); $newCli->on("close", "onClose"); $newCli->on("connect", function(Swoole\Client $newCli) use($cli) { $fd = \EventUtil::getSocketFd($cli->getSocket()); echo "connected fd <$fd>, reuse!!!\n"; echo "free socket\n"; $cli->__destruct(); echo "send\n"; $r = $newCli->send("HELLO"); }); $newCli->connect($host, $port); // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= }); $cli->connect($host, $port); ================================================ FILE: tests/include/api/swoole_http_server/http_server.php ================================================ httpServ = new Swoole\Http\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); } else { $this->httpServ = new Swoole\Http\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); } $config = [ // 输出限制 "output_buffer_size" => 1024 * 1024 * 1024, "max_connection" => 10240, "pipe_buffer_size" => 1024 * 1024 * 1024, // 'enable_port_reuse' => true, 'user' => 'www-data', 'group' => 'www-data', 'dispatch_mode' => 3, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, 'daemonize' => 0, 'reactor_num' => 1, 'worker_num' => 2, 'max_request' => 100000, 'log_file' => TEST_LOG_FILE, /* 'package_max_length' => 1024 * 1024 * 2 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, 'open_nova_protocol' => 1, */ ]; if ($ssl) { $config['ssl_cert_file'] = SSL_FILE_DIR . '/server.crt'; $config['ssl_key_file'] = SSL_FILE_DIR . '/server.key'; } $this->httpServ->set($config); } public function start() { $this->httpServ->on('start', [$this, 'onStart']); $this->httpServ->on('shutdown', [$this, 'onShutdown']); $this->httpServ->on('workerStart', [$this, 'onWorkerStart']); $this->httpServ->on('workerStop', [$this, 'onWorkerStop']); $this->httpServ->on('workerError', [$this, 'onWorkerError']); $this->httpServ->on('connect', [$this, 'onConnect']); $this->httpServ->on('receive', [$this, 'onReceive']); $this->httpServ->on('request', [$this, 'onRequest']); $this->httpServ->on('close', [$this, 'onClose']); $this->httpServ->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(\swoole_http_server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(\swoole_http_server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(\swoole_http_server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(\swoole_http_server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(\swoole_http_server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(\swoole_http_server $swooleServer, $fd, $fromId, $data) { $recv_len = strlen($data); debug_log("receive: len $recv_len"); $swooleServer->send($fd, RandStr::gen($recv_len, RandStr::ALL)); } public function onRequest(\Swoole\Http\Request $request, \Swoole\Http\Response $response) { $uri = $request->server["request_uri"]; if ($uri === "/favicon.ico") { $response->status(404); $response->end(); return; } testSetCookie: { $name = "name"; $value = "value"; // $expire = $request->swoole_server["request_time"] + 3600; $expire = 0; $path = "/"; $domain = ""; $secure = false; $httpOnly = true; // string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); $expect = "name=value; path=/; httponly"; assert(in_array($expect, $response->cookie, true)); } if ($uri === "/ping") { $this->httpServ->send($request->fd, "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\npong\r\n"); return; } if ($uri === "/gzip") { $level = 9; $response->gzip($level); $response->end(RandStr::gen(1024 * 1024 * 2, RandStr::ALL)); return; } if ($uri === "/info") { ob_start(); print("request_uri: {$uri}\n"); print("request_method: {$request->server['request_method']}\n"); if (property_exists($request, "get")) { print("get:" . var_export($request->get, true) . "\n"); } if (property_exists($request, "post")) { print("post:" . var_export($request->post, true) . "\n"); } if (property_exists($request, "cookie")) { print("cookie:" . var_export($request->cookie, true) . "\n"); } if (property_exists($request, "header")) { print("header:" . var_export($request->header, true) . "\n"); } $response->end(nl2br(ob_get_clean())); return; } if ($uri === "/uri") { $response->end($request->server['request_uri']); return; } if ($uri === "/method") { $response->end($request->server['request_method']); return; } if ($uri === "/get") { if (!empty($request->get)) { $response->end(json_encode($request->get)); } else { $response->end("null"); } return; } if ($uri === "/post") { if (property_exists($request, "post")) { $response->end(json_encode($request->post)); } else { $response->end("{}"); } return; } if ($uri === "/cookie") { if (property_exists($request, "cookie")) { $response->end(json_encode($request->cookie)); } else { $response->end("{}"); } return; } if ($uri === "/header") { if (property_exists($request, "header")) { $response->end(json_encode($request->header)); } else { $response->end("{}"); } return; } if ($uri === "/sleep") { Swoole\Timer::after(1000, function() use($response) { $response->end(); }); return; } if ($uri === "/404") { $response->status(404); $response->end(); return; } if ($uri === "/302") { $response->header("Location", "http://www.swoole.com/"); $response->status(302); $response->end(); return; } if ($uri === "/code") { $response->end(highlight_string(file_get_contents(__FILE__), true)); return; } if ($uri === "/json") { $response->header("Content-Type", "application/json"); $response->end(json_encode($request->server, JSON_PRETTY_PRINT)); return; } if ($uri === "/chunked") { $write = function($str) use($request) { return $this->httpServ->send($request->fd, $str); }; $write("HTTP/1.1 200 OK\r\n"); $write("Content-Encoding: chunked\r\n"); $write("Transfer-Encoding: chunked\r\n"); $write("Content-Type: text/html\r\n"); $write("Connection: keep-alive\r\n"); $write("\r\n"); // "0\r\n\r\n" finish $writeChunk = function($str = "") use($write) { $hexLen = dechex(strlen($str)); return $write("$hexLen\r\n$str\r\n"); }; $timer = Swoole\Timer::tick(200, function() use(&$timer, $writeChunk) { static $i = 0; $str = RandStr::gen($i++ % 40 + 1, RandStr::CHINESE) . "
"; if ($writeChunk($str) === false) { Swoole\Timer::clear($timer); } }); return; } if ($uri === "/content_length") { // $body = $request->rawcontent(); if (property_exists($request, "header")) { if (isset($request->header['content-length'])) { $response->end($request->header['content-length']); } else { $response->end(0); } return; } } if ($uri === "/rawcontent") { $response->end($request->rawcontent()); return; } if ($uri === "/file") { $response->header("Content-Type", "text"); $response->header("Content-Disposition", "attachment; filename=\"test.php\""); // TODO 这里会超时 $response->sendfile(__FILE__); } if ($uri === "/rawcookie") { $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); $response->rawcookie("rawcontent", $request->rawcontent()); } $response->end("Hello World!"); } } ================================================ FILE: tests/include/api/swoole_http_server/http_server_without_response.php ================================================ on("request", function ($request, $response) { }); $httpServer->start(); ================================================ FILE: tests/include/api/swoole_http_server/simple_http_server.php ================================================ start(); ================================================ FILE: tests/include/api/swoole_http_server/simple_https_server.php ================================================ start(); ================================================ FILE: tests/include/api/swoole_server/TestServer.php ================================================ | +----------------------------------------------------------------------+ */ abstract class TestServer { protected $index = array(); protected $recv_bytes = 0; protected $count = 0; protected $show_lost_package = false; public static $PKG_NUM; const LEN_MIN = 0; const LEN_MAX = 200; /** * @var swoole_server */ protected $serv; abstract function onReceive($serv, $fd, $reactor_id, $data); function __construct(int $port, bool $base = false) { $mode = $base ? SWOOLE_BASE : SWOOLE_PROCESS; $serv = new Swoole\Server('127.0.0.1', $port, $mode); $serv->on('Connect', [$this, 'onConnect']); $serv->on('receive', [$this, '_receive']); $serv->on('workerStart', [$this, 'onWorkerStart']); $serv->on('Close', [$this, 'onClose']); $this->serv = $serv; } function onConnect($serv, $fd, $reactor_id) { } function _receive($serv, $fd, $reactor_id, $data) { $this->count++; $this->recv_bytes += strlen($data); $this->onReceive($serv, $fd, $reactor_id, $data); if ($this->count == self::$PKG_NUM) { $serv->send($fd, "end\n"); } } function onClose($serv, $fd, $reactor_id) { echo "Total count={$this->count}, bytes={$this->recv_bytes}\n"; if ($this->show_lost_package) { for ($i = 0; $i < self::$PKG_NUM; $i++) { if (!isset($this->index[$i])) { echo "lost package#$i\n"; } } } $this->count = $this->recv_bytes = 0; unset($this->index); $this->index = array(); } function set($conf) { $this->serv->set($conf); } function start() { $this->serv->start(); } function onWorkerStart($serv, $id) { //sleep(1); } static function random() { return rand(self::LEN_MIN, self::LEN_MAX); } } ================================================ FILE: tests/include/api/swoole_server/multi_protocol_server.php ================================================ start(PHP_INT_MAX); $host = isset($argv[1]) ? $argv[1] : null; $port = isset($argv[2]) ? $argv[2] : null; $port1 = isset($argv[3]) ? $argv[3] : null; $port2 = isset($argv[4]) ? $argv[4] : null; (new OpcodeServer($host, $port, $port1, $port2))->start(); class OpcodeServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct($host, $port, $port1, $port2) { $this->swooleServer = new Swoole\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ 'dispatch_mode' => 3, 'worker_num' => 2, 'open_eof_split' => true, 'package_eof' => "\r\n", ]); $this->swooleServer->on("receive", function(Swoole\Server $server, $fd, $fromReactorId, $recv) use($port) { Assert::same(intval($recv), $port); $r = $server->send($fd, opcode_encode("return", $port)); assert($r !== false); }); $serv1 = $this->swooleServer->listen(TCP_SERVER_HOST, $port1, SWOOLE_SOCK_TCP); assert($serv1 !== false); $serv1->set([ 'open_eof_split' => true, 'package_eof' => "\r", ]); $serv1->on("receive", function(Swoole\Server $server, $fd, $fromReactorId, $recv) use($port1) { Assert::same(intval($recv), $port1); $r = $server->send($fd, opcode_encode("return", $port1)); assert($r !== false); }); $serv2 = $this->swooleServer->listen(TCP_SERVER_HOST, $port2, SWOOLE_SOCK_TCP); assert($serv2 !== false); $serv2->set([ 'open_eof_split' => true, 'package_eof' => "\n", ]); $serv2->on("receive", function(Swoole\Server $server, $fd, $fromReactorId, $recv) use($port2) { Assert::same(intval($recv), $port2); $r = $server->send($fd, opcode_encode("return", $port2)); assert($r !== false); }); } public function start($lifetime = 1000) { $this->lifetime = $lifetime; $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { } public function onClose() { } public function onStart(Swoole\Server $swooleServer) { } public function onShutdown(Swoole\Server $swooleServer) { } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { if ($workerId === 0) { Swoole\Timer::after($this->lifetime, function() { $this->swooleServer->shutdown(); kill_self_and_descendant(getmypid()); /* \Swoole\Process::signal(SIGTERM, swoole_function() { $this->swooleServer->shutdown(); }); \Swoole\Process::kill(0, SIGTERM); */ }); } } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { } } ================================================ FILE: tests/include/api/swoole_server/opcode_server.php ================================================ start(PHP_INT_MAX); $host = isset($argv[1]) ? $argv[1] : HTTP_SERVER_HOST; $port = isset($argv[2]) ? $argv[2] : HTTP_SERVER_PORT; $port1 = isset($argv[3]) ? $argv[3] : null; $port2 = isset($argv[4]) ? $argv[4] : null; (new OpcodeServer($host, $port, $port1, $port2))->start(); class OpcodeServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct($host, $port, $port1 = null, $port2 = null) { $this->swooleServer = new Swoole\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ 'dispatch_mode' => 3, 'worker_num' => 2, 'task_worker_num' => 2, 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, 'heartbeat_idle_time' => 20, ]); if ($port1) { $serv1 = $this->swooleServer->addListener(TCP_SERVER_HOST, $port1, SWOOLE_SOCK_TCP); assert($serv1 !== false); } if ($port2) { $serv2 = $this->swooleServer->addListener(TCP_SERVER_HOST, $port2, SWOOLE_SOCK_TCP); assert($serv2 !== false); } } public function start($lifetime = 1000) { $this->lifetime = $lifetime; $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->on('task', [$this, 'onTask']); $this->swooleServer->on('finish', [$this, 'onFinish']); $this->swooleServer->on('pipeMessage', [$this, 'onPipeMessage']); $this->swooleServer->on('packet', [$this, 'onPacket']); $this->swooleServer->start(); } public function onConnect() { } public function onClose() { } public function onStart(Swoole\Server $swooleServer) { } public function onShutdown(Swoole\Server $swooleServer) { } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { if ($workerId === 0) { Swoole\Timer::after($this->lifetime, function() { $this->swooleServer->shutdown(); kill_self_and_descendant(getmypid()); /* \Swoole\Process::signal(SIGTERM, swoole_function() { $this->swooleServer->shutdown(); }); \Swoole\Process::kill(0, SIGTERM); */ }); } } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { } public function onReceive(Swoole\Server $swooleServer, $fd, $fromReactorId, $recv) { list($op, $args) = opcode_decode($recv); switch($op) { case "sendMessage": list($msg, $toWorkerId) = $args; $r = $swooleServer->sendMessage(json_encode([ "fd" => $fd, "msg" => $msg, ]), $toWorkerId); Assert::true($r); return; case "sendfile": $len = filesize(__FILE__); $r = $swooleServer->send($fd, pack("N", $len + 4)); Assert::true($r); assert($r !== false); $r = $swooleServer->sendfile($fd, __FILE__); Assert::true($r); return; case "shutdown": $r = $swooleServer->shutdown(); assert($r !== false); Assert::true($r); $r = $swooleServer->send($fd, opcode_encode("return", $r)); Assert::true($r); return; case "stop": $r = $swooleServer->stop(); assert($r !== false); Assert::true($r); $r = $swooleServer->send($fd, opcode_encode("return", $r)); Assert::true($r); return; default: if (method_exists($swooleServer, $op)) { $r = call_user_func_array([$swooleServer, $op], $args); if (is_resource($r)) { $r = true; } $r = $swooleServer->send($fd, opcode_encode("return", $r)); Assert::true($r); return; } else { } } } public function onTask(Swoole\Server $swooleServer, $taskId, $fromWorkerId, $recv) { $recv = json_decode($recv); Assert::same(json_last_error(), JSON_ERROR_NONE); return json_encode($recv); } public function onFinish(Swoole\Server $swooleServer, $taskId, $recv) { $recv = json_decode($recv); Assert::same(json_last_error(), JSON_ERROR_NONE); Assert::true(isset($recv["fd"]) && isset($recv["data"])); $this->swooleServer->send($recv["fd"], opcode_encode("return", $recv["data"])); } public function onPipeMessage(Swoole\Server $swooleServer, $fromWorkerId, $recv) { $recv = json_decode($recv, true); Assert::same(json_last_error(), JSON_ERROR_NONE); Assert::true(isset($recv["fd"]) && isset($recv["msg"])); $this->swooleServer->send($recv["fd"], opcode_encode("return", $recv["msg"])); } public function onPacket(Swoole\Server $swooleServer, $data, array $clientInfo) { } } ================================================ FILE: tests/include/api/swoole_server/reconnect_fail/tcp_client.php ================================================ on("connect", function(Swoole\Client $cli) { // client 发送 大包数据 $cli->send(str_repeat("\0", 1024 * 1024 * 1.9)); }); $cli->on("receive", function(Swoole\Client $cli, $data) { $cli->send($data); }); $cli->on("error", function(Swoole\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Client $cli) { echo "close\n"; reconn(); }); $cli->connect('127.0.0.1', 9001); } reconn(); ================================================ FILE: tests/include/api/swoole_server/reconnect_fail/tcp_serv.php ================================================ start(); class TcpServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct() { $this->swooleServer = new Swoole\Server('127.0.0.1', 9001, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ // "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 'max_connection' => 10240, 'pipe_buffer_size' => 1024 * 1024 * 2, // 'pipe_buffer_size' => 1024, 'dispatch_mode' => 3, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, 'daemonize' => 0, 'reactor_num' => 1, 'worker_num' => 2, 'max_request' => 100000, ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { echo "close $fd\n"; // var_dump($swooleServer->shutdown()); // swoole_server 接受数据之后立马关闭连接 var_dump($swooleServer->close($fd)); // $swooleServer->send($fd, $data); } } ================================================ FILE: tests/include/api/swoole_server/server_manager_process_exit.php ================================================ set([ "socket_buffer_size" => 1024, ])); $cli->on("connect", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); Assert::true($cli->isConnected()); $cli->send(str_repeat("\0", 1024)); }); $cli->on("receive", function(Swoole\Client $cli, $data){ $recv_len = strlen($data); debug_log("receive: len $recv_len"); $cli->send(str_repeat("\0", $recv_len)); }); $cli->on("error", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("error"); }); $cli->on("close", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("close"); }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT); $cli->timeo_id = Swoole\Timer::after(1000, function() use($cli) { debug_log("connect timeout"); $cli->close(); Assert::false($cli->isConnected()); }); exit(); } (new TcpServer())->start(); class TcpServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct() { $this->swooleServer = new Swoole\Server(TCP_SERVER_HOST, TCP_SERVER_PORT, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 "max_connection" => 10240, "pipe_buffer_size" => 1024 * 1024 * 1024, 'daemonize' => 0, 'worker_num' => 2, 'max_request' => 100000, 'reactor_num' => 1, // 'package_max_length' => 1024 * 1024 * 2 ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { $recv_len = strlen($data); debug_log("receive: len $recv_len"); $swooleServer->send($fd, str_repeat("\0", $recv_len)); } } ================================================ FILE: tests/include/api/swoole_server/server_send_fast_recv_slow.php ================================================ set([ "socket_buffer_size" => 1, ])); $cli->on("connect", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); // TODO getSocket BUG // assert(is_resource($cli->getSocket())); /* $cli->getSocket(); // Warning: swoole_client_async::getSocket(): unable to obtain socket family Error: Bad file descriptor[9]. $cli->getSocket(); */ Assert::true($cli->isConnected()); $cli->send(str_repeat("\0", 1024)); // $cli->sendfile(__DIR__.'/test.txt'); }); $cli->on("receive", function(Swoole\Client $cli, $data){ $recv_len = strlen($data); debug_log("receive: len $recv_len"); $cli->send(str_repeat("\0", 1024)); }); $cli->on("error", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("error"); }); $cli->on("close", function(Swoole\Client $cli) { Swoole\Timer::clear($cli->timeo_id); debug_log("close"); }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT); $cli->timeo_id = Swoole\Timer::after(1000, function() use($cli) { debug_log("connect timeout"); $cli->close(); Assert::false($cli->isConnected()); }); exit(); } (new TcpServer())->start(); class TcpServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct() { $this->swooleServer = new Swoole\Server(TCP_SERVER_HOST, TCP_SERVER_PORT, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 "max_connection" => 10240, "pipe_buffer_size" => 1024 * 1024 * 1024, // 'log_file' => __DIR__ . '/send_fast_recv_slow.log', 'daemonize' => 0, 'worker_num' => 2, 'max_request' => 100000, 'reactor_num' => 1, // 'package_max_length' => 1024 * 1024 * 2 ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { $recv_len = strlen($data); debug_log("receive: len $recv_len"); $swooleServer->send($fd, str_repeat("\0", $recv_len)); } } ================================================ FILE: tests/include/api/swoole_server/simple_server.php ================================================ start(); class TcpServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct() { $this->swooleServer = new Swoole\Server(TCP_SERVER_HOST, TCP_SERVER_PORT, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ // "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 "max_connection" => 10240, "pipe_buffer_size" => 1024 * 1024 * 1024, // 'enable_port_reuse' => true, // 'user' => 'www-data', // 'group' => 'www-data', // 'log_file' => __DIR__ . '/simple_server.log', 'dispatch_mode' => 2, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, 'daemonize' => 0, 'reactor_num' => 1, 'worker_num' => 1, 'max_request' => 100000, // 'package_max_length' => 1024 * 1024 * 2 /* 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, 'open_nova_protocol' => 1, */ ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { if (trim($data) == 'shutdown') { $swooleServer->shutdown(); return; } $recv_len = strlen($data); debug_log("receive: len $recv_len"); $swooleServer->send($fd, RandStr::gen($recv_len, RandStr::ALL)); // $swooleServer->close($fd); } } ================================================ FILE: tests/include/api/swoole_server/simple_tcp_server.php ================================================ start(); $host = isset($argv[1]) ? $argv[1] : TCP_SERVER_HOST; $port = isset($argv[2]) ? $argv[2] : TCP_SERVER_PORT; $server = new TcpServer($host, $port); $server->start(); class TcpServer { /** * @var Swoole\Server */ public $swooleServer; public function __construct($host, $port) { echo "swoole_server host:$host, port:$port\n"; $this->swooleServer = new Swoole\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ "pipe_buffer_size" => 1024 * 1024 * 1024, 'dispatch_mode' => 3, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, //'daemonize' => 1, 'reactor_num' => 2, 'worker_num' => 4, 'max_request' => 100000, ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); if ($workerId == 0) { Swoole\Timer::after(5000, function () { $this->swooleServer->shutdown(); }); } } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { //echo "swoole_server receive data: $data\n"; $recv_len = strlen($data); debug_log("receive: len $recv_len"); //$swooleServer->send($fd, RandStr::gen($recv_len, RandStr::ALL)); $filename = __DIR__ . "/testsendfile.txt"; $swooleServer->sendfile($fd, $filename); } } ================================================ FILE: tests/include/api/swoole_server/simple_udp_server.php ================================================ start(); class UdpServer { public $swooleServer; public function __construct() { $this->swooleServer = new Swoole\Server(UDP_SERVER_HOST, UDP_SERVER_PORT, SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $this->swooleServer->set([ "max_connection" => 1000, 'dispatch_mode' => 3, 'daemonize' => 0, 'reactor_num' => 4, 'worker_num' => 8, 'max_request' => 1000, ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('Packet', [$this, 'onPacket']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); Swoole\Timer::after(3000, function() { $this->swooleServer->shutdown(); }); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } //UDP: 收到数据帧事件 public function onPacket(Swoole\Server $swooleServer, $data, $clientInfo) { if (trim($data) == 'shutdown') { $swooleServer->shutdown(); return; } //echo "clientInfo: $clientInfo, receive: $data\n"; $swooleServer->sendto($clientInfo['address'], $clientInfo['port'], $data); } } ================================================ FILE: tests/include/api/swoole_server/tcp_serv.php ================================================ swooleServer = new Swoole\Server($argv[1], $argv[2], SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ // "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 'log_file' => TEST_LOG_FILE, 'max_connection' => 10240, 'pipe_buffer_size' => 1024 * 1024 * 1024, 'dispatch_mode' => 3, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, 'daemonize' => 0, 'reactor_num' => 1, 'worker_num' => 2, 'max_request' => 100000, ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { // print("connecting ......"); } public function onClose() { // print("closing ....."); } public function onStart(Swoole\Server $swooleServer) { // print("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { // print("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { // print("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { // print("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { // print("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { static $i; if ($i > USE_VALGRIND ? 200 : 20000) { $swooleServer->close($fd); $swooleServer->shutdown(); @unlink(__DIR__ . '/swoole.log'); } else { $swooleServer->send($fd, $data); } $i++; } } (new TcpServer())->start(); ================================================ FILE: tests/include/api/swoole_server/tcp_task_server.php ================================================ start(); class TcpServer { public $swooleServer; public function __construct($host, $port) { echo "swoole_server host:$host, port:$port\n"; $this->swooleServer = new Swoole\Server($host, $port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ "pipe_buffer_size" => 1024 * 1024 * 1024, 'dispatch_mode' => 3, 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, //'daemonize' => 1, 'reactor_num' => 2, 'worker_num' => 4, 'task_worker_num' => 8, 'max_request' => 100000, 'log_file' => '/tmp/swoole_server.log', ]); } public function start() { $this->swooleServer->on('start', [$this, 'onStart']); $this->swooleServer->on('shutdown', [$this, 'onShutdown']); $this->swooleServer->on('workerStart', [$this, 'onWorkerStart']); $this->swooleServer->on('workerStop', [$this, 'onWorkerStop']); $this->swooleServer->on('workerError', [$this, 'onWorkerError']); $this->swooleServer->on('connect', [$this, 'onConnect']); $this->swooleServer->on('receive', [$this, 'onReceive']); $this->swooleServer->on('task', [$this, 'onTask']); $this->swooleServer->on('finish', [$this, 'onFinish']); $this->swooleServer->on('close', [$this, 'onClose']); $this->swooleServer->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); if ($workerId == 0) { //Swoole\Timer::after(5000, function () { // $this->swooleServer->shutdown(); //}); } } public function onWorkerStop(Swoole\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onReceive(Swoole\Server $swooleServer, $fd, $fromId, $data) { //echo "swoole_server receive data: $data\n"; $param = array( 'fd' => $fd, 'data' => $data, ); $swooleServer->task(json_encode($param)); //echo "send data to task worker.\n"; } public function onTask(Swoole\Server $swooleServer, $task_id, $fromId, $data) { $task_data = json_decode($data, true); $swooleServer->finish($task_data); } public function onFinish(Swoole\Server $swooleServer, $worker_task_id, $task_data) { $swooleServer->send($task_data['fd'], "OK"); } } ================================================ FILE: tests/include/api/swoole_server/testsendfile.txt ================================================ testsendfile.txt ================================================ FILE: tests/include/api/swoole_thread/putenv.php ================================================ connect()) { echo "send failed, errCode={$client->errCode}\n"; return false; } $data = str_repeat("data", 40000); for ($i = 0; $i < 100; $i++) { if (!$client->send($data)) { echo "send failed, errCode={$client->errCode}\n"; return false; } $response = $client->recv(); Assert::same($response, "SUCCESS", "response failed"); } return true; } ================================================ FILE: tests/include/api/swoole_websocket_server/send_small_request_data.php ================================================ connect(); $data = ""; for ($i = 0; $i < 100; $i++) { $client->send($data); $response = $client->recv(); Assert::same($response, "SUCCESS", "response failed"); } } ================================================ FILE: tests/include/api/swoole_websocket_server/swoole_websocket_server.php ================================================ webSocketServ = new Swoole\WebSocket\Server($host, $port, SWOOLE_PROCESS); $this->webSocketServ->set([ // 输出限制 "output_buffer_size" => 1024 * 1024 * 1024, "max_connection" => 10240, "pipe_buffer_size" => 1024 * 1024 * 1024, // 'enable_port_reuse' => true, 'user' => 'www-data', 'group' => 'www-data', // 'log_file' => __DIR__.'/swoole.log', 'open_tcp_nodelay' => 1, 'open_cpu_affinity' => 1, 'daemonize' => 0, 'reactor_num' => 1, 'worker_num' => 2, 'max_request' => 100000, ]); } public function start() { $this->webSocketServ->on('start', [$this, 'onStart']); $this->webSocketServ->on('shutdown', [$this, 'onShutdown']); $this->webSocketServ->on('workerStart', [$this, 'onWorkerStart']); $this->webSocketServ->on('workerStop', [$this, 'onWorkerStop']); $this->webSocketServ->on('workerError', [$this, 'onWorkerError']); $this->webSocketServ->on('connect', [$this, 'onConnect']); $this->webSocketServ->on('request', [$this, 'onRequest']); $this->webSocketServ->on('open', [$this, 'onOpen']); $this->webSocketServ->on('message', [$this, 'onMessage']); $this->webSocketServ->on('close', [$this, 'onClose']); $this->webSocketServ->start(); } public function onConnect() { debug_log("connecting ......"); } public function onClose() { debug_log("closing ....."); } public function onStart(Swoole\WebSocket\Server $swooleServer) { debug_log("swoole_server starting ....."); } public function onShutdown(Swoole\WebSocket\Server $swooleServer) { debug_log("swoole_server shutdown ....."); } public function onWorkerStart(Swoole\WebSocket\Server $swooleServer, $workerId) { debug_log("worker #$workerId starting ....."); } public function onWorkerStop(Swoole\WebSocket\Server $swooleServer, $workerId) { debug_log("worker #$workerId stopping ...."); } public function onWorkerError(Swoole\WebSocket\Server $swooleServer, $workerId, $workerPid, $exitCode, $sigNo) { debug_log("worker error happening [workerId=$workerId, workerPid=$workerPid, exitCode=$exitCode, signalNo=$sigNo]..."); } public function onRequest(\Swoole\Http\Request $request, \Swoole\Http\Response $response) { $response->end("Hello World!"); } public function onOpen(Swoole\WebSocket\Server $server, $request) { debug_log("{$request->fd} opened"); } public function onMessage(Swoole\WebSocket\Server $server, $frame) { $server->push($frame->fd, "SUCCESS"); } } $host = isset($argv[1]) ? $argv[1] : WEBSOCKET_SERVER_HOST; $port = isset($argv[2]) ? $argv[2] : WEBSOCKET_SERVER_PORT; $wsServer = new WebSocketServer($host, $port); $wsServer->start(); ================================================ FILE: tests/include/api/swoole_websocket_server/websocket_client.php ================================================ host = $host; $this->port = $port; $this->path = $path; $this->origin = $origin; $this->key = $this->generateToken(self::TOKEN_LENGHT); } /** * Disconnect on destruct */ function __destruct() { $this->disconnect(); } /** * Connect client to swoole_server * * @return $this */ public function connect() { $this->socket = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$this->socket->connect($this->host, $this->port)) { return false; } $this->socket->send($this->createHeader()); return $this->recv(); } public function getSocket() { return $this->socket; } /** * Disconnect from swoole_server */ public function disconnect() { $this->connected = false; $this->socket->close(); } public function recv() { $data = $this->socket->recv(); if ($data === false) { echo "Error: {$this->socket->errMsg}"; return false; } $this->buffer .= $data; $recv_data = $this->parseData($this->buffer); if ($recv_data) { $this->buffer = ''; return $recv_data; } else { return false; } } /** * @param $data * @param string $type * @param bool $masked */ public function send($data, $type = 'text', $masked = true) { return $this->socket->send($this->hybi10Encode($data, $type, $masked)); } /** * Parse received data * * @param $response */ private function parseData($response) { if (!$this->connected && isset($response['Sec-Websocket-Accept'])) { if (base64_encode(pack('H*', sha1($this->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) === $response['Sec-Websocket-Accept'] ) { $this->connected = true; } else { throw new \Exception("error response key."); } } return $this->hybi10Decode($response); } /** * Create header for websocket client * * @return string */ private function createHeader() { $host = $this->host; if ($host === '127.0.0.1' || $host === '0.0.0.0') { $host = 'localhost'; } return "GET {$this->path} HTTP/1.1" . "\r\n" . "Origin: {$this->origin}" . "\r\n" . "Host: {$host}:{$this->port}" . "\r\n" . "Sec-WebSocket-Key: {$this->key}" . "\r\n" . "User-Agent: PHPWebSocketClient/" . self::VERSION . "\r\n" . "Upgrade: websocket" . "\r\n" . "Connection: Upgrade" . "\r\n" . "Sec-WebSocket-Protocol: wamp" . "\r\n" . "Sec-WebSocket-Version: 13" . "\r\n" . "\r\n"; } /** * Parse raw incoming data * * @param $header * * @return array */ private function parseIncomingRaw($header) { $retval = array(); $content = ""; $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); foreach ($fields as $field) { if (preg_match('/([^:]+): (.+)/m', $field, $match)) { $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function ($matches) { return strtoupper($matches[0]); }, strtolower(trim($match[1]))); if (isset($retval[$match[1]])) { $retval[$match[1]] = array($retval[$match[1]], $match[2]); } else { $retval[$match[1]] = trim($match[2]); } } else { if (preg_match('!HTTP/1\.\d (\d)* .!', $field)) { $retval["status"] = $field; } else { $content .= $field . "\r\n"; } } } $retval['content'] = $content; return $retval; } /** * Generate token * * @param int $length * * @return string */ private function generateToken($length) { $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"§$%&/()=[]{}'; $useChars = array(); // select some random chars: for ($i = 0; $i < $length; $i++) { $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; } // Add numbers array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); shuffle($useChars); $randomString = trim(implode('', $useChars)); $randomString = substr($randomString, 0, self::TOKEN_LENGHT); return base64_encode($randomString); } /** * Generate token * * @param int $length * * @return string */ public function generateAlphaNumToken($length) { $characters = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); srand((float)microtime() * 1000000); $token = ''; do { shuffle($characters); $token .= $characters[mt_rand(0, (count($characters) - 1))]; } while (strlen($token) < $length); return $token; } /** * @param $payload * @param string $type * @param bool $masked * * @return bool|string */ private function hybi10Encode($payload, $type = 'text', $masked = true) { $frameHead = array(); $frame = ''; $payloadLength = strlen($payload); switch ($type) { case 'text': // first byte indicates FIN, Text-Frame (10000001): $frameHead[0] = 129; break; case 'close': // first byte indicates FIN, Close Frame(10001000): $frameHead[0] = 136; break; case 'ping': // first byte indicates FIN, Ping frame (10001001): $frameHead[0] = 137; break; case 'pong': // first byte indicates FIN, Pong frame (10001010): $frameHead[0] = 138; break; } // set mask and payload length (using 1, 3 or 9 bytes) if ($payloadLength > 65535) { $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 255 : 127; for ($i = 0; $i < 8; $i++) { $frameHead[$i + 2] = bindec($payloadLengthBin[$i]); } // most significant bit MUST be 0 (close connection if frame too big) if ($frameHead[2] > 127) { $this->close(1004); return false; } } elseif ($payloadLength > 125) { $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 254 : 126; $frameHead[2] = bindec($payloadLengthBin[0]); $frameHead[3] = bindec($payloadLengthBin[1]); } else { $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength; } // convert frame-head to string: foreach (array_keys($frameHead) as $i) { $frameHead[$i] = chr($frameHead[$i]); } if ($masked === true) { // generate a random mask: $mask = array(); for ($i = 0; $i < 4; $i++) { $mask[$i] = chr(rand(0, 255)); } $frameHead = array_merge($frameHead, $mask); } $frame = implode('', $frameHead); // append payload to frame: for ($i = 0; $i < $payloadLength; $i++) { $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; } return $frame; } /** * @param $data * * @return null|string */ private function hybi10Decode($data) { if (empty($data)) { return null; } $bytes = $data; $dataLength = ''; $mask = ''; $coded_data = ''; $decodedData = ''; $secondByte = sprintf('%08b', ord($bytes[1])); $masked = ($secondByte[0] == '1') ? true : false; $dataLength = ($masked === true) ? ord($bytes[1]) & 127 : ord($bytes[1]); if ($masked === true) { if ($dataLength === 126) { $mask = substr($bytes, 4, 4); $coded_data = substr($bytes, 8); } elseif ($dataLength === 127) { $mask = substr($bytes, 10, 4); $coded_data = substr($bytes, 14); } else { $mask = substr($bytes, 2, 4); $coded_data = substr($bytes, 6); } for ($i = 0; $i < strlen($coded_data); $i++) { $decodedData .= $coded_data[$i] ^ $mask[$i % 4]; } } else { if ($dataLength === 126) { $decodedData = substr($bytes, 4); } elseif ($dataLength === 127) { $decodedData = substr($bytes, 10); } else { $decodedData = substr($bytes, 2); } } return $decodedData; } } ================================================ FILE: tests/include/api/syntax_error.txt ================================================ getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ // 'log_file' => __DIR__ . '/simple_server.log', 'dispatch_mode' => 2, 'daemonize' => 0, 'worker_num' => 1, ]); $serv->on('workerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } $recv_len = strlen($data); $serv->send($fd, RandStr::gen($recv_len, RandStr::ALL)); }); $serv->start(); ================================================ FILE: tests/include/api/test_classes/A.php ================================================ true, 'dns_lookup_random' => true, ]); // Run default remote object server swoole_library_set_option('default_remote_object_server_worker_num', 2); swoole_init_default_remote_object_server(); Co::set([ 'socket_timeout' => 5 ]); if (empty(getenv('SWOOLE_DEBUG'))) { Co::set([ 'log_level' => SWOOLE_LOG_INFO, 'trace_flags' => 0, 'enable_deadlock_check' => false, ]); } $traceFlags = getenv('SWOOLE_TRACE_FLAGS'); if ($traceFlags) { $_traceFlags = 0; if (is_numeric($traceFlags)) { $_traceFlags = intval($traceFlags); } else { eval('$_traceFlags = ' . $traceFlags . ';'); } Co::set([ 'log_level' => 0, 'trace_flags' => $_traceFlags ]); } // Components require __DIR__ . '/lib/vendor/autoload.php'; class_alias(SwooleTest\ProcessManager::class, ProcessManager::class); class_alias(SwooleTest\ServerManager::class, ServerManager::class); class_alias(SwooleTest\RandStr::class, RandStr::class); class_alias(SwooleTest\TcpStat::class, TcpStat::class); class Assert extends SwooleTest\Assert { protected static $throwException = false; } ================================================ FILE: tests/include/config.php ================================================ &1 | grep httpbin 2>&1')) && preg_match('/\s+?[^:]+:(\d+)->\d+\/tcp\s+/', $info, $matches) && is_numeric($matches[1])) { define('HTTPBIN_SERVER_PORT_IN_DOCKER', (int) $matches[1]); } } /* ============== ODBC ============== */ if (IS_IN_CI) { define('ODBC_DSN', 'odbc:mysql-test'); } else { define('ODBC_DSN', 'odbc:mysql-test'); } define('SWOOLE_TEST_ECHO', empty(getenv('SWOOLE_TEST_NO_ECHO'))); /* ============== Http ============== */ if (IS_IN_CI && !IS_MAC_OS) { define('HTTPBIN_SERVER_HOST', 'httpbin'); define('HTTPBIN_SERVER_PORT', 80); define('HTTPBIN_LOCALLY', true); } elseif (defined('HTTPBIN_SERVER_PORT_IN_DOCKER')) { define('HTTPBIN_SERVER_HOST', '127.0.0.1'); define('HTTPBIN_SERVER_PORT', HTTPBIN_SERVER_PORT_IN_DOCKER); define('HTTPBIN_LOCALLY', true); } elseif (getenv('HTTPBIN_SERVER_HOST')) { define('HTTPBIN_SERVER_HOST', getenv('HTTPBIN_SERVER_HOST')); define('HTTPBIN_SERVER_PORT', (int) getenv('HTTPBIN_SERVER_PORT')); define('HTTPBIN_LOCALLY', true); } else { define('HTTPBIN_SERVER_HOST', 'httpbin.org'); define('HTTPBIN_SERVER_PORT', 80); } if (IS_IN_CI) { define('TEST_HTTP2_SERVERPUSH_URL', 'https://golang-h2demo:4430/serverpush'); define('TEST_NAME_RESOLVER', [ 'class' => Redis::class, 'server_url' => 'tcp://' . REDIS_SERVER_HOST . ':' . REDIS_SERVER_PORT, ]); } else { define('TEST_HTTP2_SERVERPUSH_URL', 'https://127.0.0.1:4430/serverpush'); define('TEST_NAME_RESOLVER', [ 'class' => Consul::class, 'server_url' => 'http://127.0.0.1:8500', ]); } if (IS_IN_CI) { define('TEST_DOMAIN_1', 'www.google.com'); define('TEST_DOMAIN_2', 'www.yahoo.com'); } else { define('TEST_DOMAIN_1', 'www.baidu.com'); define('TEST_DOMAIN_2', 'www.qq.com'); } define('TEST_DOMAIN_3', 'www.gov.cn'); define('TEST_MAX_CPU_EXEC_DURATION', 12); // msec /* =============== IP ================ */ define('IP_REGEX', '/^(?:[\d]{1,3}\.){3}[\d]{1,3}$/'); /* ============= Proxy ============== */ define('HTTP_PROXY_HOST', IS_IN_CI ? 'tinyproxy' : '127.0.0.1'); define('HTTP_PROXY_PORT', IS_IN_CI ? 8888 : 1080); define('SOCKS5_PROXY_HOST', IS_IN_CI ? 'socks5' : '127.0.0.1'); define('SOCKS5_PROXY_PORT', 1080); /* ============== Pressure ============== */ define('PRESSURE_LOW', 1); define('PRESSURE_MID', 2); define('PRESSURE_NORMAL', 3); if (IS_MAC_OS) { define('PRESSURE_LEVEL', 1); } else { define( 'PRESSURE_LEVEL', USE_VALGRIND ? (IS_IN_CI ? PRESSURE_LOW - 1 : PRESSURE_LOW) : ((IS_IN_CI || swoole_cpu_num() === 1) ? PRESSURE_MID : PRESSURE_NORMAL) ); } /* ============== Time ============== */ define('SERVER_PREHEATING_TIME', 0.1); define('REQUESTS_WAIT_TIME', [0.005, 0.005, 0.05, 0.1][PRESSURE_LEVEL]); /* ============== Times ============== */ define('MAX_CONCURRENCY', [16, 32, 64, 256][PRESSURE_LEVEL]); define('MAX_CONCURRENCY_MID', [8, 16, 32, 128][PRESSURE_LEVEL]); define('MAX_CONCURRENCY_LOW', [4, 8, 16, 64][PRESSURE_LEVEL]); define('MAX_REQUESTS', [12, 24, 50, 100][PRESSURE_LEVEL]); define('MAX_REQUESTS_MID', [8, 16, 32, 64][PRESSURE_LEVEL]); define('MAX_REQUESTS_LOW', [4, 8, 10, 25][PRESSURE_LEVEL]); define('MAX_LOOPS', [12, 24, 100, 1000][PRESSURE_LEVEL] * 1000); define('MAX_PROCESS_NUM', [2, 4, 6, 8][PRESSURE_LEVEL]); define('MAX_PACKET_NUM', [1024, 2048, 4096, 10000][PRESSURE_LEVEL]); /* ============== FTP ============== */ define('FTP_HOST', IS_IN_CI ? 'ftp' : '127.0.0.1'); define('FTP_PORT', 21); define('FTP_USER', 'admin'); define('FTP_PASS', 'admin'); define('FTP_TEST_FILE', IS_IN_CI ? 'test.txt' : 'work/test.txt'); ================================================ FILE: tests/include/functions.php ================================================ /dev/null 2>&1"); } function puts($msg) { echo $msg . "\n"; } function top(int $pid) { static $available; $available = $available ?? !(IS_MAC_OS || empty(shell_exec('top help 2>&1 | grep -i usage'))); if (!$available) { return false; } while (true) { $top = @shell_exec("top -b -n 1 -p {$pid}"); if (empty($top)) { trigger_error("top {$pid} failed: " . swoole_strerror(swoole_errno()), E_USER_WARNING); return false; } break; } $top = explode("\n", $top); $top = array_combine(preg_split('/\s+/', trim($top[6])), preg_split('/\s+/', trim($top[7]))); return $top; } function is_busybox_ps(): bool { static $bool; $bool = $bool ?? !empty(shell_exec('ps --help 2>&1 | grep -i busybox')); return $bool; } function kill_process_by_name(string $name) { shell_exec('ps aux | grep "' . $name . '" | grep -v grep | awk \'{ print $' . (is_busybox_ps() ? '1' : '2') . '}\' | xargs kill'); } function get_process_pid_by_name(string $name): int { return (int) shell_exec('ps aux | grep "' . $name . '" | grep -v grep | awk \'{ print $' . (is_busybox_ps() ? '1' : '2') . '}\''); } function is_musl_libc(): bool { static $bool; $bool = $bool ?? !empty(shell_exec('ldd 2>&1 | grep -i musl')); return $bool; } function get_one_free_port(): int { /** * The Swoole coroutine socket delays releasing file descriptors (fd), * which prevents ports from being released immediately. * Therefore, it is essential to disable runtime hooks. */ $flags = Runtime::getHookFlags(); Runtime::enableCoroutine(0); $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or exit('Unable to create socket: ' . socket_strerror(socket_last_error()) . PHP_EOL); socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) or exit('Unable to set socket option: ' . socket_strerror(socket_last_error()) . PHP_EOL); socket_set_option($socket, SOL_SOCKET, SO_REUSEPORT, 1) or exit('Unable to set socket option: ' . socket_strerror(socket_last_error()) . PHP_EOL); socket_bind($socket, '127.0.0.1', 0) or exit('Unable to bind socket: ' . socket_strerror(socket_last_error()) . PHP_EOL); socket_getsockname($socket, $addr, $port); socket_close($socket); Runtime::enableCoroutine($flags); return $port; } function get_constant_port(string $str, int $base = 9500): int { return $base + crc32($str) % 10000; } function get_one_free_port_ipv6(): int { $hookFlags = Runtime::getHookFlags(); Runtime::enableCoroutine(0); $server = @stream_socket_server('tcp://[::1]:0'); if (!$server) { $port = -1; } else { $name = stream_socket_get_name($server, false); if (empty($name)) { $port = -1; } else { $port = explode(']:', $name)[1]; } } Runtime::enableCoroutine($hookFlags); return $port; } function set_socket_coro_buffer_size(Socket $cosocket, int $size) { $cosocket->setOption(SOL_SOCKET, SO_SNDBUF, $size); $cosocket->setOption(SOL_SOCKET, SO_RCVBUF, $size); } function approximate($expect, $actual, float $ratio = 0.1): bool { $ret = $actual * (1 - $ratio) < $expect && $actual * (1 + $ratio) > $expect; if (!$ret) { trigger_error("approximate: expect {$expect}, but got {$actual}\n", E_USER_WARNING); } return $ret; } function time_approximate($expect, $actual, float $ratio = 0.1) { return USE_VALGRIND || approximate($expect, $actual, $ratio); } function ms_random(float $a, float $b): float { return mt_rand(intval($a * 1000), intval($b * 1000)) / 1000; } function string_pop_front(string &$s, int $length): string { $r = substr($s, 0, $length); $s = substr($s, $length); return $r; } function array_random(array $array) { return $array[mt_rand(0, count($array) - 1)]; } function phpt_echo(...$args) { if (!SWOOLE_TEST_ECHO) { return; } global $argv; if (substr($argv[0], -5) === '.phpt') { foreach ($args as $arg) { if (!is_string($arg)) { var_export($arg); echo PHP_EOL; } else { echo $arg; } } } } function phpt_var_dump(...$args) { global $argv; if (str_ends_with($argv[0], '.phpt')) { var_dump(...$args); } } function phpt_show_usage() { global $argv; if (str_ends_with($argv[0], '.phpt')) { var_dump('memory:' . memory_get_usage()); var_dump('coroutine:' . var_export(co::stats(), true)); } } function httpPost($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); curl_close($ch); return $res; } function httpRequest(string $uri, array $options = []) { $url_info = parse_url($uri); $scheme = $url_info['scheme'] ?? 'http'; $domain = $url_info['host'] ?? '127.0.0.1'; $path = $url_info['path'] ?? null ?: '/'; $query = $url_info['query'] ?? null ? "?{$url_info['query']}" : ''; $port = (int) ($url_info['port'] ?? null ?: 80); $http2 = $options['http2'] ?? false; $connect_args = [$domain, $port, $scheme === 'https' || $port === 443]; if ($http2) { $cli = new Swoole\Coroutine\Http2\Client(...$connect_args); $request = new Request(); } else { $cli = new Swoole\Coroutine\Http\Client(...$connect_args); $request = null; } $cli->set($options + ['timeout' => 5]); if (isset($options['method'])) { if ($http2) { $request->method = $options['method']; } else { $cli->setMethod($options['method']); } } if (isset($options['headers'])) { if ($http2) { $request->headers = $options['headers']; } else { $cli->setHeaders($options['headers']); } } if (isset($options['data'])) { if ($http2) { $request->data = $options['data']; } else { $cli->setData($options['data']); } } if (is_array($options['download'] ?? null)) { if ($http2) { throw new RuntimeException('HTTP2 not support download'); } $cli->download(...array_values($options['download'])); return $cli; } if ($http2) { if (!$cli->connect()) { throw new RuntimeException("HTTP2 connect {$domain}:{$port} failed: {$cli->errMsg}"); } $request->path = "{$path}{$query}"; if (!$cli->send($request)) { throw new RuntimeException("HTTP2 send request to {$uri} failed: {$cli->errMsg}"); } if (!($response = $cli->recv())) { throw new RuntimeException("HTTP2 recv from {$uri} failed: {$cli->errMsg}"); } return [ 'statusCode' => $response->statusCode, 'headers' => $response->headers, 'set_cookie_headers' => $response->set_cookie_headers, 'body' => $response->data, ]; } $redirect_times = $options['redirect'] ?? 3; while (true) { if (!$cli->execute($path . $query)) { throw new RuntimeException("HTTP execute {$uri} failed: {$cli->errMsg}"); } if ($redirect_times-- && ($cli->headers['location'] ?? null) && $cli->headers['location'][0] === '/') { $path = $cli->headers['location']; $query = ''; continue; } break; } return [ 'statusCode' => $cli->statusCode, 'headers' => $cli->headers, 'set_cookie_headers' => $cli->set_cookie_headers, 'body' => $cli->body, ]; } function httpGetStatusCode(string $uri, array $options = []) { return httpRequest($uri, $options)['statusCode']; } function httpGetHeaders(string $uri, array $options = []) { return httpRequest($uri, $options)['headers']; } function httpGetBody(string $uri, array $options = []) { return httpRequest($uri, $options)['body']; } function content_hook_replace(string $content, array $kv_map): string { foreach ($kv_map as $key => $val) { $content = str_replace("{{{$key}}}", (string) $val, $content); } return $content; } function tcp_length_types(): array { return [ 'c' => 1, 'C' => 1, 's' => 2, 'S' => 2, 'n' => 2, 'v' => 2, 'l' => 4, 'L' => 4, 'N' => 4, 'V' => 4, ]; } function tcp_type_length(string $type = 'n'): int { $map = tcp_length_types(); if (strlen($type) === 1) { return $map[$type] ?? 0; } $len = 0; for ($n = 0; $n < strlen($type); $n++) { $len += $map[$type[$n]] ?? 0; } return $len; } function tcp_head(int $length, string $type = 'n'): string { return pack($type, $length); } function tcp_pack(string $data, string $type = 'n'): string { return pack($type, strlen($data)) . $data; } function tcp_length(string $head, string $type = 'n'): int { return unpack($type, $head)[1]; } function tcp_unpack(string $data, string $type = 'n'): string { $type_length = tcp_type_length($type); return substr($data, $type_length, unpack($type, substr($data, 0, $type_length))[1]); } function var_dump_return(...$data): string { ob_start(); foreach ($data as $d) { var_dump($d); } return ob_get_clean(); } function get_safe_random(int $length = 32, $original = false): string { $raw = base64_encode(RandStr::getBytes($original ? $length : $length * 2)); if (!$original) { $raw = substr(str_replace(['/', '+', '='], '', $raw), 0, $length); } return $raw; } function get_big_random(int $length = 1024 * 1024) { if ($length < 1024 * 1024 || $length % 1024 !== 0) { throw new InvalidArgumentException('Invalid length ' . $length); } return str_repeat(get_safe_random(1024), $length / 1024); } function makeCoTcpClient($host, $port, ?callable $onConnect = null, ?callable $onReceive = null) { go(function () use ($host, $port, $onConnect, $onReceive) { $cli = new Client(SWOOLE_SOCK_TCP); assert($cli->set([ 'open_length_check' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ])); $r = $cli->connect($host, $port, 1); Assert::assert($r); if ($onConnect) { $onConnect($cli); } $recv = $cli->recv(); if ($onReceive) { $onReceive($cli, $recv); } }); } function opcode_encode($op, $data) { $r = json_encode([$op, $data]); Assert::same(json_last_error(), JSON_ERROR_NONE); return pack('N', strlen($r) + 4) . $r; } function opcode_decode($raw) { $json = substr($raw, 4); $r = json_decode($json, true); Assert::same(json_last_error(), JSON_ERROR_NONE); assert(is_array($r) && count($r) === 2); return $r; } function kill_self_and_descendant($pid) { if (PHP_OS === 'Darwin') { return; } $pids = findDescendantPids($pid); foreach ($pids as $pid) { posix_kill($pid, SIGKILL); } posix_kill($pid, SIGKILL); } /** * fork 一个进程把父进程pid通过消息队列传给子进程,延时把父进程干掉 */ function killself_in_syncmode(int $lifetime = 1000, int $sig = SIGKILL): void { $proc = new Process(function (Process $proc) use ($lifetime, $sig) { $pid = $proc->pop(); $proc->freeQueue(); usleep($lifetime * 1000); Process::kill((int) $pid, $sig); $proc->exit(); }, true); $proc->useQueue(); $proc->push((string) posix_getpid()); $proc->start(); } /** * 异步模式用定时器干掉自己 * @param int $lifetime * @param int $sig * @return mixed */ function suicide($lifetime, $sig = SIGKILL, ?callable $cb = null) { return Timer::after($lifetime, function () use ($lifetime, $sig, $cb) { if ($cb) { $cb(); } echo "suicide after {$lifetime} ms\n"; posix_kill(posix_getpid(), $sig); }); } // 查找某pid的所有子孙pid function findDescendantPids($pid) { [$pinfo] = pstree(); $y = function ($pid) use (&$y, $pinfo) { if (isset($pinfo[$pid])) { [, $childs] = $pinfo[$pid]; $pids = $childs; foreach ($childs as $child) { $pids = array_merge($pids, $y($child)); } return $pids; } return []; }; return $y($pid); } /** * @return array [pinfo, tree] * tree [ * ppid * [...child pids] * ] * list(ppid, array childs) = tree[pid] */ function pstree() { $pinfo = []; $iter = new DirectoryIterator('/proc'); foreach ($iter as $item) { $pid = $item->getFilename(); if ($item->isDir() && ctype_digit($pid)) { $stat = file_get_contents("/proc/{$pid}/stat"); $info = explode(' ', $stat); $pinfo[$pid] = [intval($info[3]), []/* , $info */]; } } foreach ($pinfo as $pid => $info) { [$ppid] = $info; $ppid = intval($ppid); $pinfo[$ppid][1][] = $pid; } $y = function ($pid, $path = []) use (&$y, $pinfo) { if (isset($pinfo[$pid])) { if (isset($pinfo[$pid][0])) { [$ppid] = $pinfo[$pid]; } else { $ppid = null; } $path[] = $pid; return $y($ppid, $path); } return array_reverse($path); }; $tree = []; foreach ($pinfo as $pid => $info) { $path = $y($pid); $node = &$tree; foreach ($path as $id) { if (!isset($node[$id])) { $node[$id] = []; } $node = &$node[$id]; } } return [$pinfo, $tree]; } function debug_log($str, $handle = STDERR) { if ($handle === STDERR) { $tpl = "\033[31m[%d %s] %s\033[0m\n"; } else { $tpl = "[%d %s] %s\n"; } if (is_resource($handle)) { fprintf($handle, $tpl, posix_getpid(), date('Y-m-d H:i:s', time()), $str); } else { printf($tpl, posix_getpid(), date('Y-m-d H:i:s', time()), $str); } } function arrayEqual(array $a, array $b, $strict = true) { if (($a && !$b) || (!$a && $b)) { return false; } if ($strict) { foreach ($a as $k => $v) { if (!array_key_exists($k, $b)) { return false; } if (gettype($v) !== gettype($b[$k])) { return false; } if (is_array($v) && arrayEqual($v, $b[$k]) === false) { return false; } } return true; } $aks = array_keys($a); $bks = array_keys($b); sort($aks); sort($bks); return $aks === $bks; } function check_tcp_port(string $host, int $port): bool { return (bool) @fsockopen($host, $port); } function start_server($file, $host, $port, $redirect_file = '/dev/null', $ext1 = null, $ext2 = null, $debug = false) { $php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; $cmd_args = getenv('TEST_PHP_ARGS'); $fdSpec = [ 0 => STDIN, 1 => STDOUT, 2 => STDERR, ]; /*if (substr(PHP_OS, 0, 3) == 'WIN') { $cmd = "$php_executable $cmd_args $file"; $opts = ["bypass_shell" => true, "suppress_errors" => true]; $handle = proc_open(addslashes($cmd), $fdSpec, $pipes, null, null, $opts); } else { $cmd = "exec $php_executable $file > $redirect_file 2>&1"; $handle = proc_open($cmd, $fdSpec, $pipes); }*/ // 必须加exec, 否咋proc_terminate结束不了server进程 !!!!!! if ($debug) { $cmd = "exec {$php_executable} {$file} {$host} {$port} {$ext1} {$ext2}"; echo '[SHELL_EXEC]' . $cmd . "\n"; } else { $cmd = "exec {$php_executable} {$file} {$host} {$port} {$ext1} {$ext2} > {$redirect_file} 2>&1"; } // $cmd = "exec $php_executable $file $host $port"; $handle = proc_open($cmd, $fdSpec, $pipes); if ($handle === false) { exit(__FUNCTION__ . ' fail'); } make_sure_server_listen_success: $i = 0; $fp = null; while (($i++ < 30) && !($fp = @fsockopen($host, $port))) { usleep(10000); } if ($fp) { fclose($fp); } // linux上有问题,client端事件循环还没起起来就会先调用这个shutdown回调, 结束了子进程 // 第二个shutdown_function swoole才会把子进程的事件循环起来 // register_shutdown_function(function() use($handle, $redirect_file) { // proc_terminate($handle, SIGTERM); // @unlink($redirect_file); // }); swoole_async_set(['enable_coroutine' => false]); // need use exit return function () use ($handle, $redirect_file) { // @unlink($redirect_file); proc_terminate($handle, SIGTERM); Event::exit(); exit; }; } function swoole_fork_exec(callable $fn, bool $redirect_stdin_and_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false) { $process = new Process(...func_get_args()); if (!$process->start()) { return false; } return $process::wait(); } function php_fork_exec(callable $fn, $f_stdout = '/dev/null', $f_stderr = null) { $pid = pcntl_fork(); if ($pid < 0) { exit('fork fail'); } if ($pid === 0) { fclose(STDOUT); $STDOUT = fopen($f_stdout, 'w'); if ($f_stderr !== null) { fclose(STDERR); $STDERR = fopen($f_stderr, 'w'); } $fn(); exit; } pcntl_waitpid($pid, $status); return ['pid' => $pid, 'status', $status]; } /** * spawn_exec * @param null|string $cmd command * @param null|string $input code * @param null|int $tv_sec timeout sec * @param null|int $tv_usec timeout usec * @param null|string $cwd change work dir * @param null|array $env env * @return array [out, err] */ function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, ?array $env = null) { $out = $err = null; $winOpt = ['suppress_errors' => true, 'binary_pipes' => true]; $proc = proc_open($cmd, [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ], $pipes, $cwd, $env, $winOpt); assert($proc !== false); if ($input !== null) { $n = fwrite($pipes[0], $input); if (strlen($input) !== $n) { goto closePipes; } } // 必须关闭 assert(fclose($pipes[0])); unset($pipes[0]); // 防止select立即返回, 消耗cpu assert(!($tv_sec === 0 && $tv_usec === 0)); while (true) { $r = $pipes; $w = null; $e = null; /* 隐藏被信号或者其他系统调用打断 产生的错误 */ set_error_handler(function () {}); $n = @stream_select($r, $w, $e, $tv_sec, $tv_usec); restore_error_handler(); if ($n === false) { break; } if ($n === 0) { // 超时kill -9 assert(proc_terminate($proc, SIGKILL)); throw new RuntimeException("exec {$cmd} time out"); } if ($n > 0) { foreach ($r as $handle) { if ($handle === $pipes[1]) { $_ = &$out; } else { if ($handle === $pipes[2]) { $_ = &$err; } else { $_ = ''; } } $line = fread($handle, 8192); $isEOF = $line === ''; if ($isEOF) { break 2; } $_ .= $line; } } } closePipes: foreach ($pipes as $fd => $pipe) { if (is_resource($pipe)) { @fclose($pipe); } unset($pipes[$fd]); } return [$out, $err]; } function parent_child($parentFunc, $childFunc) { $pid = pcntl_fork(); if ($pid < 0) { echo 'ERROR'; exit; } if ($pid === 0) { $childFunc(); exit; } $parentFunc($pid); } function readfile_with_lock($file): string { $fp = fopen($file, 'r+'); flock($fp, LOCK_SH); $data = ''; while (!feof($fp)) { $data .= fread($fp, 8192); } fclose($fp); return $data; } function dump_to_file($file, $data) { $fp = fopen($file, 'w+'); $out = bin2hex($data); $lines = str_split($out, 160); foreach ($lines as $l) { fwrite($fp, $l . "\n"); } fclose($fp); } function curl_type_assert($ch, $resource_type, $class_type): void { if (PHP_VERSION_ID >= 80000) { Assert::isInstanceOf($ch, $class_type); } else { Assert::eq(get_resource_type($ch), $resource_type); } } function swoole_get_variance($avg, $array, $is_swatch = false): bool|float { $count = count($array); if ($count == 1 && $is_swatch) { return false; } if ($count > 0) { $total_var = 0; foreach ($array as $lv) { $total_var += pow($lv - $avg, 2); } if ($count == 1 && $is_swatch) { return false; } return $is_swatch ? sqrt($total_var / (count($array) - 1)) : sqrt($total_var / count($array)); } return false; } function swoole_get_average($array): float|int { return array_sum($array) / count($array); } function assert_server_stats($stats): void { Assert::keyExists($stats, 'connection_num'); Assert::keyExists($stats, 'request_count'); } function assert_upload_file($file, $tmp_name, $name, $type, $size, $error = 0): void { Assert::notEmpty($file); Assert::eq($file['tmp_name'], $tmp_name); Assert::eq($file['name'], $name); Assert::eq($file['type'], $type); Assert::eq($file['size'], $size); Assert::eq($file['error'], $error); } function swoole_loop_n($n, $fn): void { for ($i = 0; $i < $n; $i++) { $fn($i); } } function swoole_loop($fn) { $i = 0; while (true) { $fn($i++); } } function build_ftp_url(string $path = ''): string { return 'ftp://' . FTP_USER . ':' . FTP_PASS . '@' . FTP_HOST . ':' . FTP_PORT . '/' . $path; } function get_thread_name(): string { return trim(file_get_contents('/proc/' . posix_getpid() . '/task/' . Thread::getNativeId() . '/comm')); } function mkdir_if_not_exists(string $string): void { if (!is_dir($string)) { mkdir($string, 0777, true); } } function array_arrange($array): array { $list = array_keys(array_flip($array)); sort($list); return $list; } ================================================ FILE: tests/include/lib/composer.json ================================================ { "name": "swoole_tests/lib", "description": "for swoole tests", "authors": [ { "name": "tianfeng.han", "email": "rango@swoole.com" } ], "autoload": { "psr-4": { "SwooleTest\\": "src" } }, "require": { "guzzlehttp/guzzle": "^7.3", "guzzlehttp/promises": "^1.4", "guzzlehttp/psr7": "^1.7", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", "php-http/discovery": "^1.6.1", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", "psr/http-factory": "^1.0", "symfony/http-client": "^5.3", "nyholm/psr7": "^1.4", "friendsofphp/php-cs-fixer": "^3.3", "php-http/message-factory": "^1.1", "predis/predis": "^2.2" }, "config": { "allow-plugins": { "php-http/discovery": true } } } ================================================ FILE: tests/include/lib/src/Assert.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SwooleTest; use ArrayAccess; use BadMethodCallException; use Closure; use Countable; use Exception; use RuntimeException; use Throwable; use Traversable; /** * Efficient assertions to validate the input/output of your methods. * * @method static bool nullOrString($value, $message = '') * @method static bool nullOrStringNotEmpty($value, $message = '') * @method static bool nullOrInteger($value, $message = '') * @method static bool nullOrIntegerish($value, $message = '') * @method static bool nullOrFloat($value, $message = '') * @method static bool nullOrNumeric($value, $message = '') * @method static bool nullOrNatural($value, $message = '') * @method static bool nullOrBoolean($value, $message = '') * @method static bool nullOrScalar($value, $message = '') * @method static bool nullOrObject($value, $message = '') * @method static bool nullOrResource($value, $type = null, $message = '') * @method static bool nullOrIsCallable($value, $message = '') * @method static bool nullOrIsArray($value, $message = '') * @method static bool nullOrIsTraversable($value, $message = '') * @method static bool nullOrIsArrayAccessible($value, $message = '') * @method static bool nullOrIsCountable($value, $message = '') * @method static bool nullOrIsIterable($value, $message = '') * @method static bool nullOrIsInstanceOf($value, $class, $message = '') * @method static bool nullOrNotInstanceOf($value, $class, $message = '') * @method static bool nullOrIsInstanceOfAny($value, $classes, $message = '') * @method static bool nullOrIsEmpty($value, $message = '') * @method static bool nullOrNotEmpty($value, $message = '') * @method static bool nullOrTrue($value, $message = '') * @method static bool nullOrFalse($value, $message = '') * @method static bool nullOrIp($value, $message = '') * @method static bool nullOrIpv4($value, $message = '') * @method static bool nullOrIpv6($value, $message = '') * @method static bool nullOrUniqueValues($values, $message = '') * @method static bool nullOrEq($value, $expect, $message = '') * @method static bool nullOrNotEq($value, $expect, $message = '') * @method static bool nullOrSame($value, $expect, $message = '') * @method static bool nullOrNotSame($value, $expect, $message = '') * @method static bool nullOrGreaterThan($value, $limit, $message = '') * @method static bool nullOrGreaterThanEq($value, $limit, $message = '') * @method static bool nullOrLessThan($value, $limit, $message = '') * @method static bool nullOrLessThanEq($value, $limit, $message = '') * @method static bool nullOrRange($value, $min, $max, $message = '') * @method static bool nullOrOneOf($value, $values, $message = '') * @method static bool nullOrContains($value, $subString, $message = '') * @method static bool nullOrNotContains($value, $subString, $message = '') * @method static bool nullOrNotWhitespaceOnly($value, $message = '') * @method static bool nullOrStartsWith($value, $prefix, $message = '') * @method static bool nullOrStartsWithLetter($value, $message = '') * @method static bool nullOrEndsWith($value, $suffix, $message = '') * @method static bool nullOrRegex($value, $pattern, $message = '') * @method static bool nullOrNotRegex($value, $pattern, $message = '') * @method static bool nullOrAlpha($value, $message = '') * @method static bool nullOrDigits($value, $message = '') * @method static bool nullOrAlnum($value, $message = '') * @method static bool nullOrLower($value, $message = '') * @method static bool nullOrUpper($value, $message = '') * @method static bool nullOrLength($value, $length, $message = '') * @method static bool nullOrMinLength($value, $min, $message = '') * @method static bool nullOrMaxLength($value, $max, $message = '') * @method static bool nullOrLengthBetween($value, $min, $max, $message = '') * @method static bool nullOrFileExists($value, $message = '') * @method static bool nullOrFile($value, $message = '') * @method static bool nullOrDirectory($value, $message = '') * @method static bool nullOrReadable($value, $message = '') * @method static bool nullOrWritable($value, $message = '') * @method static bool nullOrClassExists($value, $message = '') * @method static bool nullOrSubclassOf($value, $class, $message = '') * @method static bool nullOrInterfaceExists($value, $message = '') * @method static bool nullOrImplementsInterface($value, $interface, $message = '') * @method static bool nullOrPropertyExists($value, $property, $message = '') * @method static bool nullOrPropertyNotExists($value, $property, $message = '') * @method static bool nullOrMethodExists($value, $method, $message = '') * @method static bool nullOrMethodNotExists($value, $method, $message = '') * @method static bool nullOrKeyExists($value, $key, $message = '') * @method static bool nullOrKeyNotExists($value, $key, $message = '') * @method static bool nullOrCount($value, $key, $message = '') * @method static bool nullOrMinCount($value, $min, $message = '') * @method static bool nullOrMaxCount($value, $max, $message = '') * @method static bool nullOrIsList($value, $message = '') * @method static bool nullOrIsMap($value, $message = '') * @method static bool nullOrCountBetween($value, $min, $max, $message = '') * @method static bool nullOrUuid($values, $message = '') * @method static bool nullOrThrows($expression, $class = 'Exception', $message = '') * @method static bool allString($values, $message = '') * @method static bool allStringNotEmpty($values, $message = '') * @method static bool allInteger($values, $message = '') * @method static bool allIntegerish($values, $message = '') * @method static bool allFloat($values, $message = '') * @method static bool allNumeric($values, $message = '') * @method static bool allNatural($values, $message = '') * @method static bool allBoolean($values, $message = '') * @method static bool allScalar($values, $message = '') * @method static bool allObject($values, $message = '') * @method static bool allResource($values, $type = null, $message = '') * @method static bool allIsCallable($values, $message = '') * @method static bool allIsArray($values, $message = '') * @method static bool allIsTraversable($values, $message = '') * @method static bool allIsArrayAccessible($values, $message = '') * @method static bool allIsCountable($values, $message = '') * @method static bool allIsIterable($values, $message = '') * @method static bool allIsInstanceOf($values, $class, $message = '') * @method static bool allNotInstanceOf($values, $class, $message = '') * @method static bool allIsInstanceOfAny($values, $classes, $message = '') * @method static bool allNull($values, $message = '') * @method static bool allNotNull($values, $message = '') * @method static bool allIsEmpty($values, $message = '') * @method static bool allNotEmpty($values, $message = '') * @method static bool allTrue($values, $message = '') * @method static bool allFalse($values, $message = '') * @method static bool allIp($values, $message = '') * @method static bool allIpv4($values, $message = '') * @method static bool allIpv6($values, $message = '') * @method static bool allUniqueValues($values, $message = '') * @method static bool allEq($values, $expect, $message = '') * @method static bool allNotEq($values, $expect, $message = '') * @method static bool allSame($values, $expect, $message = '') * @method static bool allNotSame($values, $expect, $message = '') * @method static bool allGreaterThan($values, $limit, $message = '') * @method static bool allGreaterThanEq($values, $limit, $message = '') * @method static bool allLessThan($values, $limit, $message = '') * @method static bool allLessThanEq($values, $limit, $message = '') * @method static bool allRange($values, $min, $max, $message = '') * @method static bool allOneOf($values, $values, $message = '') * @method static bool allContains($values, $subString, $message = '') * @method static bool allNotContains($values, $subString, $message = '') * @method static bool allNotWhitespaceOnly($values, $message = '') * @method static bool allStartsWith($values, $prefix, $message = '') * @method static bool allStartsWithLetter($values, $message = '') * @method static bool allEndsWith($values, $suffix, $message = '') * @method static bool allRegex($values, $pattern, $message = '') * @method static bool allNotRegex($values, $pattern, $message = '') * @method static bool allAlpha($values, $message = '') * @method static bool allDigits($values, $message = '') * @method static bool allAlnum($values, $message = '') * @method static bool allLower($values, $message = '') * @method static bool allUpper($values, $message = '') * @method static bool allLength($values, $length, $message = '') * @method static bool allMinLength($values, $min, $message = '') * @method static bool allMaxLength($values, $max, $message = '') * @method static bool allLengthBetween($values, $min, $max, $message = '') * @method static bool allFileExists($values, $message = '') * @method static bool allFile($values, $message = '') * @method static bool allDirectory($values, $message = '') * @method static bool allReadable($values, $message = '') * @method static bool allWritable($values, $message = '') * @method static bool allClassExists($values, $message = '') * @method static bool allSubclassOf($values, $class, $message = '') * @method static bool allInterfaceExists($values, $message = '') * @method static bool allImplementsInterface($values, $interface, $message = '') * @method static bool allPropertyExists($values, $property, $message = '') * @method static bool allPropertyNotExists($values, $property, $message = '') * @method static bool allMethodExists($values, $method, $message = '') * @method static bool allMethodNotExists($values, $method, $message = '') * @method static bool allKeyExists($values, $key, $message = '') * @method static bool allKeyNotExists($values, $key, $message = '') * @method static bool allCount($values, $key, $message = '') * @method static bool allMinCount($values, $min, $message = '') * @method static bool allMaxCount($values, $max, $message = '') * @method static bool allCountBetween($values, $min, $max, $message = '') * @method static bool allIsList($values, $message = '') * @method static bool allIsMap($values, $message = '') * @method static bool allUuid($values, $message = '') * @method static bool allThrows($expressions, $class = 'Exception', $message = '') * * @since 2.0 * * @author Bernhard Schussek */ class Assert { protected static $throwException = true; protected static $maxStringLength = 1024; public static function setThrowException(bool $b) { static::$throwException = $b; } public static function assert($value, $message = ''): bool { if (!$value) { static::reportInvalidArgument($message); return false; } return true; } public static function string($value, $message = ''): bool { if (!is_string($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a string. Got: %s', static::typeToString($value) )); return false; } return true; } public static function stringNotEmpty($value, $message = ''): bool { return static::string($value, $message) && static::notEq($value, '', $message); } public static function integer($value, $message = ''): bool { if (!is_int($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an integer. Got: %s', static::typeToString($value) )); return false; } return true; } public static function integerish($value, $message = ''): bool { if (!is_numeric($value) || $value != (int)$value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an integerish value. Got: %s', static::typeToString($value) )); return false; } return true; } public static function float($value, $message = ''): bool { if (!is_float($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a float. Got: %s', static::typeToString($value) )); return false; } return true; } public static function numeric($value, $message = ''): bool { if (!is_numeric($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a numeric. Got: %s', static::typeToString($value) )); return false; } return true; } public static function natural($value, $message = ''): bool { if (!is_int($value) || $value < 0) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a non-negative integer. Got %s', static::valueToString($value) )); return false; } return true; } public static function boolean($value, $message = ''): bool { if (!is_bool($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a boolean. Got: %s', static::typeToString($value) )); return false; } return true; } public static function scalar($value, $message = ''): bool { if (!is_scalar($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a scalar. Got: %s', static::typeToString($value) )); return false; } return true; } public static function object($value, $message = ''): bool { if (!is_object($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an object. Got: %s', static::typeToString($value) )); return false; } return true; } public static function resource($value, $type = null, $message = ''): bool { if (!is_resource($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a resource. Got: %s', static::typeToString($value) )); return false; } if ($type && $type !== get_resource_type($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a resource of type %2$s. Got: %s', static::typeToString($value), $type )); return false; } return true; } public static function isCallable($value, $message = ''): bool { if (!is_callable($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a callable. Got: %s', static::typeToString($value) )); return false; } return true; } public static function isArray($value, $message = ''): bool { if (!is_array($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an array. Got: %s', static::typeToString($value) )); return false; } return true; } public static function isArrayAccessible($value, $message = ''): bool { if (!is_array($value) && !($value instanceof ArrayAccess)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an array accessible. Got: %s', static::typeToString($value) )); return false; } return true; } public static function isCountable($value, $message = ''): bool { if (!is_array($value) && !($value instanceof Countable)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a countable. Got: %s', static::typeToString($value) )); return false; } return true; } public static function isIterable($value, $message = ''): bool { if (!is_array($value) && !($value instanceof Traversable)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an iterable. Got: %s', static::typeToString($value) )); return false; } return true; } public static function isInstanceOf($value, $class, $message = ''): bool { if (!($value instanceof $class)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance of %2$s. Got: %s', static::typeToString($value), $class )); return false; } return true; } public static function notInstanceOf($value, $class, $message = ''): bool { if ($value instanceof $class) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance other than %2$s. Got: %s', static::typeToString($value), $class )); return false; } return true; } public static function isInstanceOfAny($value, array $classes, $message = ''): bool { foreach ($classes as $class) { if ($value instanceof $class) { return true; } } static::reportInvalidArgument(sprintf( $message ?: 'Expected an instance of any of %2$s. Got: %s', static::typeToString($value), implode(', ', array_map(['static', 'valueToString'], $classes)) )); return false; } public static function isEmpty($value, $message = ''): bool { if (!empty($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an empty value. Got: %s', static::valueToString($value) )); return false; } return true; } public static function notEmpty($value, $message = ''): bool { if (empty($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a non-empty value. Got: %s', static::valueToString($value) )); return false; } return true; } public static function null($value, $message = ''): bool { if (null !== $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected null. Got: %s', static::valueToString($value) )); return false; } return true; } public static function notNull($value, $message = ''): bool { if (null === $value) { static::reportInvalidArgument( $message ?: 'Expected a value other than null.' ); return false; } return true; } public static function true($value, $message = ''): bool { if (true !== $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to be true. Got: %s', static::valueToString($value) )); return false; } return true; } public static function false($value, $message = ''): bool { if (false !== $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to be false. Got: %s', static::valueToString($value) )); return false; } return true; } public static function ip($value, $message = ''): bool { if (false === filter_var($value, FILTER_VALIDATE_IP)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to be an IP. Got: %s', static::valueToString($value) )); return false; } return true; } public static function ipv4($value, $message = ''): bool { if (false === filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to be an IPv4. Got: %s', static::valueToString($value) )); return false; } return true; } public static function ipv6($value, $message = ''): bool { if (false === filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to be an IPv6. Got %s', static::valueToString($value) )); return false; } return true; } public static function uniqueValues(array $values, $message = ''): bool { $allValues = count($values); $uniqueValues = count(array_unique($values)); if ($allValues !== $uniqueValues) { $difference = $allValues - $uniqueValues; static::reportInvalidArgument(sprintf( $message ?: 'Expected an array of unique values, but %s of them %s duplicated', $difference, (1 === $difference ? 'is' : 'are') )); return false; } return true; } public static function eq($value, $expect, $message = ''): bool { if ($expect != $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect) )); return false; } return true; } public static function notEq($value, $expect, $message = ''): bool { if ($expect == $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a different value than %s.', static::valueToString($expect) )); return false; } return true; } public static function same($value, $expect, $message = ''): bool { if ($expect !== $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value identical to %2$s. Got: %s', static::valueToString($value), static::valueToString($expect) )); return false; } return true; } public static function notSame($value, $expect, $message = ''): bool { if ($expect === $value) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value not identical to %s.', static::valueToString($expect) )); return false; } return true; } public static function greaterThan($value, $limit, $message = ''): bool { if ($value <= $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value greater than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); return false; } return true; } public static function greaterThanEq($value, $limit, $message = ''): bool { if ($value < $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); return false; } return true; } public static function lessThan($value, $limit, $message = ''): bool { if ($value >= $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value less than %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); return false; } return true; } public static function lessThanEq($value, $limit, $message = ''): bool { if ($value > $limit) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value less than or equal to %2$s. Got: %s', static::valueToString($value), static::valueToString($limit) )); return false; } return true; } public static function range($value, $min, $max, $message = ''): bool { if ($value < $min || $value > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value between %2$s and %3$s. Got: %s', static::valueToString($value), static::valueToString($min), static::valueToString($max) )); return false; } return true; } public static function approximate($value, $actual, float $ratio = 0.1): bool { $ret = $actual * (1 - $ratio) < $value && $actual * (1 + $ratio) > $value; if (!$ret) { static::reportInvalidArgument( "Expected a value approximate {$value}, but got {$actual}\n" ); } return $ret; } public static function oneOf($value, array $values, $message = ''): bool { if (!in_array($value, $values, true)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected one of: %2$s. Got: %s', static::valueToString($value), implode(', ', array_map(['static', 'valueToString'], $values)) )); return false; } return true; } public static function contains($value, $subString, $message = ''): bool { if (false === strpos($value, $subString)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain %2$s. Got: %s', static::valueToString($value), static::valueToString($subString) )); return false; } return true; } public static function notContains($value, $subString, $message = ''): bool { if (false !== strpos($value, $subString)) { static::reportInvalidArgument(sprintf( $message ?: '%2$s was not expected to be contained in a value. Got: %s', static::valueToString($value), static::valueToString($subString) )); return false; } return true; } public static function notWhitespaceOnly($value, $message = ''): bool { if (preg_match('/^\s*$/', $value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a non-whitespace string. Got: %s', static::valueToString($value) )); return false; } return true; } public static function startsWith($value, $prefix, $message = ''): bool { if (0 !== strpos($value, $prefix)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to start with %2$s. Got: %s', static::valueToString($value), static::valueToString($prefix) )); return false; } return true; } public static function startsWithLetter($value, $message = ''): bool { $valid = isset($value[0]); if ($valid) { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = ctype_alpha($value[0]); setlocale(LC_CTYPE, $locale); } if (!$valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to start with a letter. Got: %s', static::valueToString($value) )); return false; } return true; } public static function endsWith($value, $suffix, $message = ''): bool { if ($suffix !== substr($value, -static::strlen($suffix))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to end with %2$s. Got: %s', static::valueToString($value), static::valueToString($suffix) )); return false; } return true; } public static function regex($value, $pattern, $message = ''): bool { if (!preg_match($pattern, $value)) { static::reportInvalidArgument(sprintf( $message ?: 'The value %s does not match the expected pattern.', static::valueToString($value) )); return false; } return true; } public static function notRegex($value, $pattern, $message = ''): bool { if (preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { static::reportInvalidArgument(sprintf( $message ?: 'The value %s matches the pattern %s (at offset %d).', static::valueToString($value), static::valueToString($pattern), $matches[0][1] )); return false; } return true; } public static function alpha($value, $message = ''): bool { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_alpha($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain only letters. Got: %s', static::valueToString($value) )); return false; } return true; } public static function digits($value, $message = ''): bool { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_digit($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain digits only. Got: %s', static::valueToString($value) )); return false; } return true; } public static function alnum($value, $message = ''): bool { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_alnum($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain letters and digits only. Got: %s', static::valueToString($value) )); return false; } return true; } public static function lower($value, $message = ''): bool { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_lower($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain lowercase characters only. Got: %s', static::valueToString($value) )); return false; } return true; } public static function upper($value, $message = ''): bool { $locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'C'); $valid = !ctype_upper($value); setlocale(LC_CTYPE, $locale); if ($valid) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain uppercase characters only. Got: %s', static::valueToString($value) )); return false; } return true; } public static function length($value, $length, $message = ''): bool { if ($length !== static::strlen($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain %2$s characters. Got: %s', static::valueToString($value), $length )); return false; } return true; } public static function minLength($value, $min, $message = ''): bool { if (static::strlen($value) < $min) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', static::valueToString($value), $min )); return false; } return true; } public static function maxLength($value, $max, $message = ''): bool { if (static::strlen($value) > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', static::valueToString($value), $max )); return false; } return true; } public static function lengthBetween($value, $min, $max, $message = ''): bool { $length = static::strlen($value); if ($length < $min || $length > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', static::valueToString($value), $min, $max )); return false; } return true; } public static function fileExists($value, $message = ''): bool { static::string($value); if (!file_exists($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The file %s does not exist.', static::valueToString($value) )); return false; } return true; } public static function file($value, $message = ''): bool { static::fileExists($value, $message); if (!is_file($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not a file.', static::valueToString($value) )); return false; } return true; } public static function directory($value, $message = ''): bool { static::fileExists($value, $message); if (!is_dir($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is no directory.', static::valueToString($value) )); return false; } return true; } public static function readable($value, $message = ''): bool { if (!is_readable($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not readable.', static::valueToString($value) )); return false; } return true; } public static function writable($value, $message = ''): bool { if (!is_writable($value)) { static::reportInvalidArgument(sprintf( $message ?: 'The path %s is not writable.', static::valueToString($value) )); return false; } return true; } public static function classExists($value, $message = ''): bool { if (!class_exists($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an existing class name. Got: %s', static::valueToString($value) )); return false; } return true; } public static function subclassOf($value, $class, $message = ''): bool { if (!is_subclass_of($value, $class)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected a sub-class of %2$s. Got: %s', static::valueToString($value), static::valueToString($class) )); return false; } return true; } public static function interfaceExists($value, $message = ''): bool { if (!interface_exists($value)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an existing interface name. got %s', static::valueToString($value) )); return false; } return true; } public static function implementsInterface($value, $interface, $message = ''): bool { if (!in_array($interface, class_implements($value))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an implementation of %2$s. Got: %s', static::valueToString($value), static::valueToString($interface) )); return false; } return true; } public static function propertyExists($classOrObject, $property, $message = ''): bool { if (!property_exists($classOrObject, $property)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the property %s to exist.', static::valueToString($property) )); return false; } return true; } public static function propertyNotExists($classOrObject, $property, $message = ''): bool { if (property_exists($classOrObject, $property)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the property %s to not exist.', static::valueToString($property) )); return false; } return true; } public static function methodExists($classOrObject, $method, $message = ''): bool { if (!method_exists($classOrObject, $method)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the method %s to exist.', static::valueToString($method) )); return false; } return true; } public static function methodNotExists($classOrObject, $method, $message = ''): bool { if (method_exists($classOrObject, $method)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the method %s to not exist.', static::valueToString($method) )); return false; } return true; } public static function keyExists($array, $key, $message = ''): bool { if (!(isset($array[$key]) || array_key_exists($key, $array))) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the key %s to exist.', static::valueToString($key) )); return false; } return true; } public static function keyNotExists($array, $key, $message = ''): bool { if (isset($array[$key]) || array_key_exists($key, $array)) { static::reportInvalidArgument(sprintf( $message ?: 'Expected the key %s to not exist.', static::valueToString($key) )); return false; } return true; } public static function count($array, $number, $message = ''): bool { return static::eq( count($array), $number, $message ?: sprintf('Expected an array to contain %d elements. Got: %d.', $number, count($array)) ); } public static function minCount($array, $min, $message = ''): bool { if (count($array) < $min) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', count($array), $min )); return false; } return true; } public static function maxCount($array, $max, $message = ''): bool { if (count($array) > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', count($array), $max )); return false; } return true; } public static function countBetween($array, $min, $max, $message = ''): bool { $count = count($array); if ($count < $min || $count > $max) { static::reportInvalidArgument(sprintf( $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', $count, $min, $max )); return false; } return true; } public static function isList($array, $message = ''): bool { if (!is_array($array) || !$array || array_keys($array) !== range(0, count($array) - 1)) { static::reportInvalidArgument( $message ?: 'Expected list - non-associative array.' ); return false; } return true; } public static function isMap($array, $message = ''): bool { if ( !is_array($array) || !$array || array_keys($array) !== array_filter(array_keys($array), function ($key) { return is_string($key); }) ) { static::reportInvalidArgument( $message ?: 'Expected map - associative array with string keys.' ); return false; } return true; } public static function uuid($value, $message = ''): bool { $value = str_replace(['urn:', 'uuid:', '{', '}'], '', $value); // The nil UUID is special form of UUID that is specified to have all // 128 bits set to zero. if ('00000000-0000-0000-0000-000000000000' === $value) { return true; } if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { static::reportInvalidArgument(sprintf( $message ?: 'Value %s is not a valid UUID.', static::valueToString($value) )); return false; } return true; } public static function throws(Closure $expression, $class = 'Exception', $message = ''): bool { static::string($class); $actual = 'none'; try { $expression(); } catch (Exception $e) { $actual = get_class($e); if ($e instanceof $class) { return true; } } catch (Throwable $e) { $actual = get_class($e); if ($e instanceof $class) { return true; } } static::reportInvalidArgument($message ?: sprintf( 'Expected to throw "%s", got "%s"', $class, $actual )); return false; } public static function __callStatic($name, $arguments): bool { if ('nullOr' === substr($name, 0, 6)) { if (null !== $arguments[0]) { $method = lcfirst(substr($name, 6)); if (!call_user_func_array(['static', $method], $arguments)) { return false; } } return true; } if ('all' === substr($name, 0, 3)) { if (!static::isIterable($arguments[0])) { return false; } $method = lcfirst(substr($name, 3)); $args = $arguments; foreach ($arguments[0] as $entry) { $args[0] = $entry; if (!call_user_func_array(['static', $method], $args)) { return false; } } return true; } throw new BadMethodCallException('No such method: ' . $name); } protected static function valueToString($value): string { if (null === $value) { return 'null'; } if (true === $value) { return 'true'; } if (false === $value) { return 'false'; } if (is_array($value)) { return 'array(' . count($value) . ')'; } if (is_object($value)) { if (method_exists($value, '__toString')) { return get_class($value) . ': ' . self::valueToString($value->__toString()); } return get_class($value); } if (is_resource($value)) { return 'resource'; } if (is_string($value)) { $length = strlen($value); if ($length > static::$maxStringLength) { $value = substr($value, 0, static::$maxStringLength) . '...'; } return 'string(' . $length . ') "' . $value . '"'; } return (string)$value; } protected static function typeToString($value): string { return is_object($value) ? get_class($value) : gettype($value); } protected static function strlen($value): int { if (!function_exists('mb_detect_encoding')) { return strlen($value); } if (false === $encoding = mb_detect_encoding($value)) { return strlen($value); } return mb_strwidth($value, $encoding); } protected static function reportInvalidArgument(string $message = '') { $e = new RuntimeException($message); if (static::$throwException) { throw $e; } else { $file = $e->getFile(); $line = $e->getLine(); $msg = $e->getMessage(); $trace = $e->getTraceAsString(); foreach ($e->getTrace() as $call) { $file = $call['file'] ?? 'Unknown'; $line = $call['line'] ?? 0; if ($file !== __FILE__) { break; } } echo "\nAssert failed: " . (empty($msg) ? '' : "{$msg} ") . "in {$file} on line {$line}\nStack trace: \n{$trace}\n"; } } private function __construct() { } } ================================================ FILE: tests/include/lib/src/ChildProcess.php ================================================ process = new Process(function (Process $worker) use ($script) { $worker->exec('/bin/sh', ['-c', $script]); }, true, SOCK_STREAM, false); $this->process->start(); } public function read() { return $this->process->read(); } public function write(string $data): void { $this->process->write($data); } static function exec(string $script): ChildProcess { return new self($script); } } ================================================ FILE: tests/include/lib/src/CoServer.php ================================================ * Date: 2019/3/28 10:30 AM */ namespace SwooleTest; use Co\Socket; use RuntimeException; class CoServer { /** @var Socket */ protected $server = null; /** @var Socket[] */ protected $connections = []; /** @var callable */ protected $connectHandler = null; /** @var callable */ protected $dataHandler = null; public static function createTcp(string $host = '127.0.0.1', int $port = 0, int $backlog = 128): self { return new self([ 'host' => $host, 'port' => $port, 'backlog' => $backlog ]); } public static function createTcpGreeting(...$args): self { $server = self::createTcp(...$args); $server->setConnectHandler(function (Socket $conn) { $conn->sendAll('Hello Swoole'); }); return $server; } public static function createHttpHelloWorld(...$args): self { $server = self::createTcp(...$args); $server->setDataHandler(function (Socket $conn, string $data) { if (strpos($data, 'HTTP/1.0') !== false || stripos($data, 'Connection: closed') !== false) { $conn->keep_alive = false; } if (strrpos($data, "\r\n\r\n") !== false) { $conn->sendAll("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n"); if (!($conn->keep_alive ?? true)) { $conn->close(); } } }); return $server; } public function __construct(array $options) { $this->server = new Socket( $options['domain'] ?? AF_INET, $options['type'] ?? SOCK_STREAM, $options['protocol'] ?? IPPROTO_IP ); if (!$this->server->bind($options['host'] ?? '127.0.0.1', $options['port'] ?? 9501)) { throw new RuntimeException("bind failed due to {$this->server->errMsg}"); } if (!$this->server->listen($options['backlog'] ?? 128)) { throw new RuntimeException("listen failed due to {$this->server->errMsg}"); } } public function getPort(): int { return ($this->server->getsockname() ?: [])['port'] ?? 0; } public function setConnectHandler(callable $handler) { $this->connectHandler = $handler; } public function setDataHandler(callable $handler) { $this->dataHandler = $handler; } public function run() { go(function () { while ($conn = $this->server->accept(-1)) { $this->connections[$conn->fd] = $conn; go(function () use ($conn) { defer(function () use ($conn) { unset($this->connections[$conn->fd]); }); if ($handler = $this->connectHandler) { if ($handler($conn) === false) { return; } } while ($data = $conn->recv(8192, -1)) { if ($handler = $this->dataHandler) { if ($handler($conn, $data) === false) { return; } } } }); } foreach ($this->connections as $conn) { $conn->close(); } $this->server->close(); }); } public function shutdown() { $this->server->close(); } } ================================================ FILE: tests/include/lib/src/CurlManager.php ================================================ nativeCurl = defined('SWOOLE_HOOK_NATIVE_CURL'); } function disableNativeCurl() { $this->nativeCurl = false; } function getUrlBase() { return "http://127.0.0.1:{$this->port}"; } protected function runCliServer($port) { $proc = new Process(function (Process $p) use ($port) { $exec = "/usr/bin/env php -t " . __DIR__ . " -n -S 127.0.0.1:{$port} " . __DIR__ . "/responder/get.php"; $p->exec('/bin/sh', ['-c', $exec]); }, true, 1); $proc->start(); while (1) { usleep(10000); if (@file_get_contents($this->getUrlBase() . '/')) { break; } } return $proc; } function run(callable $fn, $createCliServer = true) { if ($createCliServer) { $this->port = get_one_free_port(); $proc = $this->runCliServer($this->port); } else { $proc = null; } global $argc, $argv; if (!($argc > 1 and $argv[1] == 'ori')) { $flags = $this->nativeCurl ? SWOOLE_HOOK_NATIVE_CURL : SWOOLE_HOOK_CURL; Swoole\Runtime::enableCoroutine($flags); } run(function () use ($fn, $proc) { $fn("127.0.0.1:{$this->port}"); if ($proc) { Swoole\Process::kill($proc->pid); } }); if ($createCliServer) { Process::wait(); } } } ================================================ FILE: tests/include/lib/src/DbWrapper.php ================================================ connect( $config['host'], $config['user'], $config['password'], $config['database'], $config['port'], ); if (false === $res) { throw new RuntimeException($mysql->connect_error, $mysql->errno); } else { $this->mysql = $mysql; $this->config = $config; } return $res; } public function __call($name, $arguments) { // $result = $this->mysql->{$name}(...$arguments); // $result = call_user_func_array([$this->mysql, $name], $arguments); $result = $this->mysql->query($arguments[0]); if (false === $result) { if (!$this->mysql->connected) { $this->connect($this->config); return call_user_func_array([$this->mysql, $name], $arguments); } if (!empty($this->mysql->errno)) { throw new RuntimeException($this->mysql->error, $this->mysql->errno); } } return $result->fetch_all(); } } ================================================ FILE: tests/include/lib/src/LengthServer.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; use Swoole; use \RandStr; abstract class LengthServer { protected $setting; protected $index = array(); protected $recv_bytes = 0; protected $count = 0; protected $show_lost_package = false; static public $pkg_num = 100000; static public $pkg_len_min = 100; static public $pkg_len_max = 200000; static public $random_bytes = true; /** * @var Swoole\Coroutine\Server */ protected $serv; protected $debug = false; function onReceive($data) { $header = unpack('Nlen/Nindex/Nsid', substr($data, 0, 12)); if ($header['index'] % 1000 == 0 and $this->debug) { echo "#{$header['index']} recv package. sid={$header['sid']}, length=" . strlen($data) . ", bytes={$this->recv_bytes}\n"; } if ($header['index'] > self::$pkg_num) { echo "invalid index #{$header['index']}\n"; } $this->index[$header['index']] = true; } abstract function onWorkerStart(); /** * TestServer_Co constructor. * @param int $port * @param bool $ssl * @throws \Swoole\Exception */ function __construct(int $port, bool $ssl = false) { $serv = new Swoole\Coroutine\Server('127.0.0.1', $port, $ssl); $this->serv = $serv; $this->setting = [ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]; } /** * @param $conn \Swoole\Coroutine\Server\Connection * @param $data */ function _receive($conn, $data) { $this->count++; $this->recv_bytes += strlen($data); $this->onReceive($data); if ($this->count == self::$pkg_num) { $conn->send("end\n"); } } function onClose() { echo "Total count={$this->count}, bytes={$this->recv_bytes}\n"; if ($this->show_lost_package) { for ($i = 0; $i < self::$pkg_num; $i++) { if (!isset($this->index[$i])) { echo "lost package#$i\n"; } } } $this->count = $this->recv_bytes = 0; unset($this->index); $this->index = array(); } function set($conf) { $this->setting += $conf; } function start() { $this->serv->set($this->setting); $this->serv->handle(function ($conn) { while (true) { $data = $conn->recv(); if (!$data) { $this->onClose(); break; } else { $this->_receive($conn, $data); } } }); $this->onWorkerStart(); $this->serv->start(); } /** * @return string * @throws Exception */ static function getPacket() { static $index = 0; $sid = rand(10000000, 99999999); $n = rand(self::$pkg_len_min, self::$pkg_len_max); $data = self::$random_bytes ? RandStr::getBytes($n) : (new \Swoole\StringObject('A'))->repeat($n)->toString(); return pack('NNN', $n + 8, $index++, $sid) . $data; } } ================================================ FILE: tests/include/lib/src/MQTT/Helper.php ================================================ > 4; $header['dup'] = ($byte & 0x08) >> 3; $header['qos'] = ($byte & 0x06) >> 1; $header['retain'] = $byte & 0x01; return $header; } public static function encodePublish($data) { $cmd = 3 << 4; $body = pack('n', strlen($data['topic'])) . $data['topic'] . $data['content']; $length = strlen($body); $head = chr($cmd) . self::writeBodyLength($length); return $head . $body; } protected static function writeBodyLength($length) { $string = ''; do { $digit = $length % 128; $length = $length >> 7; if ($length > 0) { $digit = ($digit | 0x80); } $string .= chr($digit); } while ($length > 0); return $string; } public static function encodePing(int $cmd) { $cmd = $cmd << 4; return chr($cmd) . self::writeBodyLength(0); } } ================================================ FILE: tests/include/lib/src/MysqlPool.php ================================================ pool)) { $this->config = $config; $this->pool = new Channel($this->config['pool_size']); for ($index = 0; $index < $this->config['pool_size']; $index++) { // $mysql = new MyDb(); $mysql = new DbWrapper(); $res = $mysql->connect($config); if ($res === false) { throw new RuntimeException("failed to connect mysql server."); } else { $this->put($mysql); } } } } public function put($mySQL) { $this->pool->push($mySQL); } public function get() { /** * @var mysqli */ $mysql = $this->pool->pop($this->config['pool_get_timeout']); if ($mysql === false) { throw new RuntimeException('Get mysql timeout, all mysql connection is used'); } return $mysql; } /** * @param array $config * @return MysqlPool */ public static function getInstance(array $config = []) { if (!empty(self::$instance)) { return self::$instance; } if (empty($config)) { throw new RuntimeException('Mysql config empty'); } self::$instance = new static($config); return self::$instance; } /** * @return mixed * @desc 获取当时连接池可用对象 */ public function getLength() { return $this->pool->length(); } } ================================================ FILE: tests/include/lib/src/ProcessManager.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; use RuntimeException; use Swoole\Atomic; use Swoole\Event; use Swoole\Process; class ProcessManager { /** * @var Atomic */ protected $atomic; protected $alone = false; protected $onlyChild = false; protected $onlyParent = false; protected $freePorts = []; protected $randomFunc = 'get_safe_random'; protected $randomData = [[]]; protected $randomDataArray = []; /** * wait wakeup 1s default */ protected $waitTimeout = 1.0; public $parentFunc; public $childFunc; public $async = false; public $useConstantPorts = false; protected $childPid; protected $childExitStatus = 255; protected $expectExitSignal = [0, SIGTERM]; protected $parentFirst = false; protected $killed = false; /** * @var Process */ protected $childProcess; protected $logFileHandle; public function __construct() { $this->atomic = new Atomic(0); } public function setParent(callable $func) { $this->parentFunc = $func; } public function parentFirst() { $this->parentFirst = true; } public function childFirst() { $this->parentFirst = false; } public function setChild(callable $func) { $this->childFunc = $func; } public function getChildPid(): int { return $this->childPid; } public function setWaitTimeout(int $value) { $this->waitTimeout = $value; } //等待信息 public function wait() { if ($this->alone || $this->waitTimeout == 0) { return false; } return $this->atomic->wait($this->waitTimeout); } //唤醒等待的进程 public function wakeup() { if ($this->alone) { return false; } return $this->atomic->wakeup(); } public function runParentFunc($pid = 0) { if (!$this->parentFunc) { return (function () { $this->kill(); })(); } else { return call_user_func($this->parentFunc, $pid); } } public function setLogFile($file) { $this->logFileHandle = fopen($file, "a+"); } public function writeLog($msg) { fwrite($this->logFileHandle, $msg . PHP_EOL); } /** * @param int $index * @return mixed */ public function getFreePort($index = 0) { return $this->freePorts[$index]; } public function setRandomFunc($func) { $this->randomFunc = $func; } public function initRandomData(int $size, int $len = null) { $this->initRandomDataEx(1, $size, $len); } /** * 生成一个随机字节组成的数组 * @param int $n * @param int $len 默认为0,表示随机产生长度 * @param bool $base64 * @throws \Exception */ public function initRandomDataArray($n = 1, $len = 0, bool $base64 = false) { while ($n--) { if ($len == 0) { $len = rand(1024, 1 * 1024 * 1024); } $bytes = random_bytes($len); $this->randomDataArray[] = $base64 ? base64_encode($bytes) : $bytes; } } /** * @param $index * @return mixed */ public function getRandomDataElement(int $index = 0) { if (!isset($this->randomDataArray[$index])) { throw new RuntimeException("out of array"); } return $this->randomDataArray[$index]; } public function getRandomData() { return $this->getRandomDataEx(0); } public function getRandomDataSize(): int { return $this->getRandomDataSizeEx(0); } public function initRandomDataEx(int $block_num, int $size, ...$arguments) { $arguments = array_reverse($arguments); $shift = 0; foreach ($arguments as $index => $argument) { if ($argument === null) { $shift++; } else { break; } } while ($shift--) { array_shift($arguments); } $arguments = array_reverse($arguments); $func = $this->randomFunc; for ($b = 0; $b < $block_num; $b++) { for ($n = $size; $n--;) { $this->randomData[$b][] = $func(...$arguments); } } } public function getRandomDataEx(int $block_id) { if (!empty($this->randomData[$block_id])) { return array_shift($this->randomData[$block_id]); } else { throw new RuntimeException('Out of the bound'); } } public function getRandomDataSizeEx(int $block_id): int { return count($this->randomData[$block_id]); } public function runChildFunc() { return call_user_func($this->childFunc); } /** * Kill Child Process * @param bool $force */ public function kill(bool $force = false) { if (!defined('PCNTL_ESRCH')) { define('PCNTL_ESRCH', 3); } if (!$this->alone and !$this->killed and $this->childPid) { $this->killed = true; if ($force || (!@Process::kill($this->childPid) && swoole_errno() !== PCNTL_ESRCH)) { if (!@Process::kill($this->childPid, SIGKILL) && swoole_errno() !== PCNTL_ESRCH) { exit('KILL CHILD PROCESS ERROR'); } } } } /** * @param int $num * @param int $increment Only used for constant port number, must be a constant * @return void */ public function initFreePorts(int $num = 1, int $increment = 0): void { for ($i = $num; $i--;) { $this->freePorts[] = $this->useConstantPorts ? (9500 + $num - $i + count($this->freePorts) + $increment) : get_one_free_port(); } } public function initFreeIPv6Ports(int $num = 1): void { for ($i = $num; $i--;) { $this->freePorts[] = $this->useConstantPorts ? (9500 + $num - $i + count($this->freePorts)) : get_one_free_port_ipv6(); } } public function run($redirectStdout = false) { global $argv, $argc; if ($argc > 1) { $this->useConstantPorts = true; $this->alone = true; $this->initFreePorts(); if ($argv[1] == 'child') { $this->onlyChild = true; } elseif ($argv[1] == 'parent') { $this->onlyParent = true; } else { throw new RuntimeException("bad parameter \$1\n"); } } $this->initFreePorts(); if ($this->alone) { if ($this->onlyChild) { return $this->runChildFunc(); } elseif ($this->onlyParent) { return $this->runParentFunc(); } $this->alone = false; } $this->childProcess = new Process(function () { if ($this->parentFirst) { $this->wait(); } $this->runChildFunc(); exit; }, $redirectStdout, $redirectStdout); if (!$this->childProcess || !$this->childProcess->start()) { exit("ERROR: CAN NOT CREATE PROCESS\n"); } register_shutdown_function(function () { $this->kill(); }); if (!$this->parentFirst) { $this->wait(); } $this->runParentFunc($this->childPid = $this->childProcess->pid); Event::wait(); $waitInfo = Process::wait(true); $this->childExitStatus = $waitInfo['code']; if (!in_array($waitInfo['signal'], $this->expectExitSignal)) { throw new RuntimeException("Unexpected exit code {$waitInfo['signal']}"); } return true; } public function getChildOutput() { $this->childProcess->setBlocking(false); $output = ''; while (1) { $data = @$this->childProcess->read(); if (!$data) { break; } else { $output .= $data; } } return $output; } public function expectExitCode($code = 0) { if (!is_array($code)) { $code = [$code]; } if (!in_array($this->childExitStatus, $code)) { throw new RuntimeException("Unexpected exit code {$this->childExitStatus}"); } } function getChildExitStatus() { return $this->childExitStatus; } public function setExpectExitSignal($signal = 0) { if (!is_array($signal)) { $signal = [$signal]; } $this->expectExitSignal = $signal; } static function exec(callable $fn) { $pm = new static(); $pm->setWaitTimeout(0); $pm->parentFunc = function () { }; $pm->childFunc = function () use ($pm, $fn) { $fn($pm); }; $pm->childFirst(); $pm->run(true); return $pm; } } ================================================ FILE: tests/include/lib/src/RandStr.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; class RandStr { const ALPHA = 1; const NUM = 2; const CHINESE = 4; const ALL = self::ALPHA | self::NUM | self::CHINESE; const __ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const __NUM = "0123456789"; const __CHINESE2000 = "五于天末开下理事画现玫珠表珍列玉平不来与屯妻到互寺城霜载直进吉协南才垢圾夫无坟增示赤过志地雪支三夺大厅左丰百右历面帮原胡春克太磁砂灰达成顾肆友龙本村枯林械相查可机格析极检构术样档杰棕杨李要权楷七革基苛式牙划或功贡攻匠菜共区芳燕东芝世节切芭药睛睦盯虎止旧占卤贞睡肯具餐眩瞳步眯瞎卢眼皮此量时晨果虹早昌蝇曙遇昨蝗明蛤晚景暗晃显晕电最归紧昆呈叶顺呆呀中虽吕另员呼听吸只史嘛啼吵喧叫啊哪吧哟车轩因困四辊加男轴力斩胃办罗罚较边思轨轻累同财央朵曲由则崭册几贩骨内风凡赠峭迪岂邮凤生行知条长处得各务向笔物秀答称入科秒秋管秘季委么第后持拓打找年提扣押抽手折扔失换扩拉朱搂近所报扫反批且肝采肛胆肿肋肌用遥朋脸胸及胶膛爱甩妥肥脂全会估休代个介保佃仙作伯仍从你信们偿伙亿他分公化钱针然钉氏外旬名甸负儿铁角欠多久匀乐炙锭包凶争色主计庆订度让刘训为高放诉衣认义方说就变这记离良充率闰半关亲并站间部曾商产瓣前闪交六立冰普帝决闻妆冯北汪法尖洒江小浊澡渐没少泊肖兴光注洋水淡学沁池当汉涨业灶类灯煤粘烛炽烟灿烽煌粗粉炮米料炒炎迷断籽娄宽寂审宫军宙客宾家空宛社实宵灾之官字安它怀导居民收慢避惭届必怕愉懈心习悄屡忱忆敢恨怪尼卫际承阿陈耻阳职阵出降孤阴队防联孙耿辽也子限取陛姨寻姑杂毁旭如舅九奶婚妨嫌录灵巡刀好妇妈姆对参戏台劝观矣牟能难允驻驼马邓艰双线结顷红引旨强细纲张绵级给约纺弱纱继综纪弛绿经比烃定守害一在工上是国和的有人我了发以瑟斑晴语伍残封都动什杆舍鞍伏邦悲韭源善着羚磊矿剧页万尤跋森棵酒宁歌臣茹哎莽酣谨甘黄腊垂道植申卡叔趾足虚玻晶暮临象坚界肃梨刊章朝蚕品喊带雷恩闸鸭轰曼黑柬温盆苯苦某荆匡芋棋柑奎霸震积余叙者复怎炸笺简数赣彻覆碧凰皑易肠派汽拿攀势抛看拜哲岳兵肢县甫拥解穆受貌豺豪橡毅衰畏丧众输夷份雁谷苏癸蹬察镜跑软鸟鸣岛印狈逛鲁渔免见杀便誓辩尺州亢亡丹亥孩俯肪激唯截颜冲头均壮兽敝幸夹旁辞滓疗嫉泵永函兆泰康否杯系党灭庶兼播凯撰挖巨启眉媚声蕊恭舔翌翻熟屈齿龄亨蒸滁椰服矛仓她施媳案淄巢扭那丸津食霓骚令通私云爸骤蕴雍幻慈每沸曳王土木目日口田山禾白月金言火已女又干士二十寸雨古犬石厂丁西戈卜曰虫川甲皿竹斤乃八夕文广辛门贝己巳乙尸羽耳臼巴弓阶而种自深农体路正论些资形重战使等合图新还想点其起性斗把里两应制育去气问展意接情油题利压组期毛群次但孔流席运质治位器指建活教统别更真将识先专常造修病老回验很特根团转总任调热改完集毫研尔求精层清确低再证劳被号装单据设场花传判究须青越轮做整即速织书状海斯议般千影推今德差始响觉液维破消试布需胜济效选话片牧备续标存身板述底规走除置配养敌华测准许技床端感非磨往圆照搞族神容亚段算适讲按值美态彪班麦削排该击素密候草何树属市径螺英快坏移材省武培著河京助升抓含苗副谈围射例致酸却短剂宣环落首波践府考刻靠够满住枝局菌周护岩师举元超模贫减扬亩球医校呢稻滑鼓刚写微略范供块套项倒卷创律远初优死毒圈伟控跟裂粮母练塞钢顶策留误粒础故丝焊株院冷弹错散盾视艺版烈零室血缺厘绝富喷柱望盘雄似巩益洲脱投送侧润盖挥距触星松获独混座依未突架冬湿偏纹执寨责阀吃稳硬价努奇预评读背损棉侵厚泥辟卵箱掌氧停溶营终孟待尽俄缩沙退讨奋胞幼迫旋征槽殖握担鲜钻逐脚盐编蜂急伤飞域露核缘游振操甚迅辉异序纸夜乡隶缸念兰映沟吗儒磷插埃燃欢补咱芽瓦倾碳演附耪裔斜灌欧献猪腐请透司危括靛脉囤若尾束暴企穗愈拖牛染既遍锻夏井费访吹荣铜沿替滚旱悟脑措贯藏隙濮徐吴赵陆沈蒋曹唐潘袁郭蔡戴薛姚宋韩谢姜严陶董郑程倪秦邵汤葛俞杜殷龚魏梁崔邹邱彭尹庄卞贾洪盛樊侯邢郁凌仇韦童翟付祁仲宗梅鲍祝谭钟庞乔虞郝傅焦熊浦柏狄裴柳戚房毕翁储聂莫贺茅屠杭尚诸芦鞠廖骆靳詹阮惠桑柯刁柴丛齐喻桂侍舒戎阎宦巫黎涂符厉糜冒钮郎霍甄姬祖卓晓祥萍忠俊斌宏玲勇峰宝霞丽娟敏琴健静福贵勤锦艳莉涛瑞跃仁泉连喜银亮宇慧鹏茂淑芹坤剑君翠彬恒礼侠智浩菊香蓉炳寿圣贤洁耀延翔芬绍琳颖栋巧铭敬淮登鸿宜莲庭孝泽政彩诚崇彦佩宪锡钧劲锋殿希迎堂裕鹤欣汝妹岭沛莹雅佳纯靖蕾俭蔚彤湘绪尧廷锁勋庚嘉伦娥详钦寅冠骏滨威捷亭巍楼呜娜旺晋悦咏焕昭枫琼慎杏仕仪珊桃谦航舜猛卿鼎咸陵镇召敦佐熙遵桥网闽挺菲禄耘锐潮鉴婉塔蚜描粤粱惮慨乌矩疾徊碍戒买笛痛锈锌匆矢溪荤惟陪掩耸棠祭槐憨狙忙辑奉忧飘沫怖悬厌欲谱瘤货蛊赴垣嚎履闯藩遁雀渠探涸滇钡诡弟秩渗痊捏茸诬枪狠弃摇倘贬庙汇肩捎怒帽寄岸搐饼违汕蝎炔擅掖傀闹蜡裸碱奠秉丑倍萧瞒萌歧勒煎谐梳携蛇箕臂皖坍奸胎赌魁患凿傣栈唁晤碑匪翅瘫烤汛狰捍袄瞩碘嗜绰毖瓶疤俺倦冉递葬骇伶擒谴搬睬盎丈粳袋暇颈屉阜邻篓拆脊镭趣鼠疹寐鼻澎椿倔蝴酿辈钨盂购釉逆诛粹凄桅娇菏瑶父抢浮晦拂葫揉壕弊冻笼箩氛舵凹型默闲菩驰啦篡孪瑚蜗午宴驯镶砚怠粥躁豁靡拴睁丘傈腋碟懂皆淤矗浸隘挛咬帛揩瘩妖荡斟疼哥撬铣拨味哇挞迹哈孺桓蚀萄命惫幂渤稗迂瞧菱躺礁贸赶尝郡咖笆扎裤卉割炕砸潦俏饥羹锗赦博衙摆漱畅码砍钎渡绒牢捡痪棍喂辨璃澳饮洼抿窟咯辰隋憋酋绅狱悔厄"; // const __CHINESE3000 = "啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好耗号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕很狠恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱碱拣捡简俭剪减荐槛鉴践贱见键箭件健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利僳例俐痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟僳塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃汀廷停亭庭艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座"; private static $strCache = []; /** * @param int $len * @param int $type * @return string */ public static function gen($len = 10, $type = self::NUM | self::ALPHA) { $str = self::getChars($type); $strLen = mb_strlen($str); $ret = ""; for ($i = 0; $i < $len; $i++) { // non safe rand $ret .= mb_substr($str, rand(0, $strLen - 1), 1); } return $ret; } /** * @param $n * @return string * @throws \Exception */ static function getBytes($n) { if (function_exists('openssl_random_pseudo_bytes')) { return openssl_random_pseudo_bytes($n); } elseif (function_exists('random_bytes')) { return random_bytes($n); } else { return self::gen($n); } } /** * @param $mask * @return mixed|string */ private static function getChars($mask) { if (isset(static::$strCache[$mask])) { return static::$strCache[$mask]; } $str = ""; if ($mask & self::NUM) { $str .= self::__NUM; } if ($mask & self::ALPHA) { $str .= self::__ALPHA; } if ($mask & self::CHINESE) { $str .= self::__CHINESE2000; } if ($str === "") { $str .= self::NUM . self::ALPHA; } static::$strCache[$mask] = $str; return $str; } } ================================================ FILE: tests/include/lib/src/Redis/DBConnectException.php ================================================ random = substr(md5(microtime()), 0, 8); } public function lock(string $key, int $expires = self::EXPIRES): bool { $this->expires = $expires; $ret = Redis::main()->set($key, $this->random, ['nx', 'ex' => $this->expires]); if ($ret) { $this->keyMap[$key] = microtime(true); } return !!$ret; } public function unlock(string $key = null) { if ($key) { // unlock one if ($this->keyMap[$key] ?? false) { if ($this->keyMap[$key] < microtime(true) - $this->expires) { return; // have already expired } else { @Redis::main()->del($key); } unset($this->keyMap[$key]); } } else { // unlock all foreach ($this->keyMap as $key => $expires) { $this->unlock($key); } } } public function __destruct() { $this->unlock(); } } ================================================ FILE: tests/include/lib/src/Redis/Redis.php ================================================ 0 && ($redis = $pool->shift()) && $redis->isConnect()) { //满足 1.会话池里有空闲连接 2.返回了一个非空连接 3.Redis没有超时时间 $redis->is_in_pool = false; return $redis; } return new self($name, $options); } public static function main(): self { return self::i([ 'host' => REDIS_SERVER_HOST, 'port' => REDIS_SERVER_PORT ]); } private function __construct(string $name, array $options) { $this->name = $name; $this->client = new \Swoole\Coroutine\Redis(); if (!$this->client->connect($options['host'], $options['port'])) { new DBConnectException('[Redis: ' . $this->client->errCode . '] ' . $this->client->errMsg, $this->client->errCode); } } public function isConnect(): bool { return $this->client->connected ?? false; } public function __call(string $name, $params) { if ($this->is_in_pool) { throw new \BadMethodCallException('this redis client is in pool!'); } $ret = call_user_func_array([$this->client, $name], $params); $this->revert(); return $ret; } public function revert() { SQLPool::i($this->name)->push($this); $this->is_in_pool = true; } public function __destruct() { $this->client->close(); } } ================================================ FILE: tests/include/lib/src/Redis/SQLPool.php ================================================ $name ?? (self::$instance->$name = new \SplQueue); } public static function release() { self::$instance = null; } } ================================================ FILE: tests/include/lib/src/Samtleben/WebsocketClient.php ================================================ * @version 2011-10-18 */ namespace SwooleTest\Samtleben; class WebsocketClient { private $_host; private $_port; private $_path; private $_origin; /** * @var \Swoole\Client */ private $_Socket = null; private $_connected = false; public function __construct() { } public function __destruct() { $this->disconnect(); } public function sendRecv($data, $type = 'text', $masked = true) { if($this->_connected === false) { trigger_error("Not connected", E_USER_WARNING); return false; } if( !is_string($data)) { trigger_error("Not a string data was given.", E_USER_WARNING); return false; } if (strlen($data) == 0) { return false; } $res = $this->_Socket->send($this->_hybi10Encode($data, $type, $masked)); if($res === 0 || $res === false) { return false; } $buf = $this->_Socket->recv(512); $out = $this->_hybi10Decode($buf); return $out['payload']; } public function recvData() { $buf = $this->_Socket->recv(512); $out = $this->_hybi10Decode($buf); return $out['payload']; } public function connect($host, $port, $path, $origin = false) { $this->_host = $host; $this->_port = $port; $this->_path = $path; $this->_origin = $origin; $key = base64_encode($this->_generateRandomString(16, false, true)); $header = "GET " . $path . " HTTP/1.1\r\n"; $header .= "Host: " . $host . ":" . $port . "\r\n"; $header .= "Upgrade: websocket\r\n"; $header .= "Connection: Upgrade\r\n"; $header .= "Sec-WebSocket-Key: " . $key . "\r\n"; if ($origin !== false) { $header .= "Sec-WebSocket-Origin: " . $origin . "\r\n"; } $header .= "Sec-WebSocket-Version: 13\r\n"; $this->_Socket = new \Swoole\Client(SWOOLE_TCP, SWOOLE_SOCK_SYNC); $this->_Socket->connect($host, $port, 1); $this->_Socket->send($header . "\r\n"); $response = $this->_Socket->recv(1500); preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $response, $matches); if ($matches) { $keyAccept = trim($matches[1]); $expectedResonse = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); $this->_connected = ($keyAccept === $expectedResonse) ? true : false; } return $this->_connected; } public function checkConnection() { $this->_connected = false; // send ping: $data = 'ping?'; $this->_Socket->send($this->_hybi10Encode($data, 'ping', true)); $response = $this->_Socket->recv(300); if(empty($response)) { return false; } $response = $this->_hybi10Decode($response); if(!is_array($response)) { return false; } if(!isset($response['type']) || $response['type'] !== 'pong') { return false; } $this->_connected = true; return true; } public function disconnect() { $this->_connected = false; $this->_Socket->close(); } public function reconnect() { sleep(10); $this->_connected = false; fclose($this->_Socket); $this->connect($this->_host, $this->_port, $this->_path, $this->_origin); } private function _generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"§$%&/()=[]{}'; $useChars = array(); // select some random chars: for($i = 0; $i < $length; $i++) { $useChars[] = $characters[mt_rand(0, strlen($characters)-1)]; } // add spaces and numbers: if($addSpaces === true) { array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); } if($addNumbers === true) { array_push($useChars, rand(0,9), rand(0,9), rand(0,9)); } shuffle($useChars); $randomString = trim(implode('', $useChars)); $randomString = substr($randomString, 0, $length); return $randomString; } private function _hybi10Encode($payload, $type = 'text', $masked = true) { $frameHead = array(); $frame = ''; $payloadLength = strlen($payload); switch($type) { case 'text': // first byte indicates FIN, Text-Frame (10000001): $frameHead[0] = 129; break; case 'close': // first byte indicates FIN, Close Frame(10001000): $frameHead[0] = 136; break; case 'ping': // first byte indicates FIN, Ping frame (10001001): $frameHead[0] = 137; break; case 'pong': // first byte indicates FIN, Pong frame (10001010): $frameHead[0] = 138; break; } // set mask and payload length (using 1, 3 or 9 bytes) if($payloadLength > 65535) { $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 255 : 127; for($i = 0; $i < 8; $i++) { $frameHead[$i+2] = bindec($payloadLengthBin[$i]); } // most significant bit MUST be 0 (close connection if frame too big) if($frameHead[2] > 127) { $this->close(1004); return false; } } elseif($payloadLength > 125) { $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 254 : 126; $frameHead[2] = bindec($payloadLengthBin[0]); $frameHead[3] = bindec($payloadLengthBin[1]); } else { $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength; } // convert frame-head to string: foreach(array_keys($frameHead) as $i) { $frameHead[$i] = chr($frameHead[$i]); } if($masked === true) { // generate a random mask: $mask = array(); for($i = 0; $i < 4; $i++) { $mask[$i] = chr(rand(0, 255)); } $frameHead = array_merge($frameHead, $mask); } $frame = implode('', $frameHead); // append payload to frame: $framePayload = array(); for($i = 0; $i < $payloadLength; $i++) { $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; } return $frame; } private function _hybi10Decode($data) { $payloadLength = ''; $mask = ''; $unmaskedPayload = ''; $decodedData = array(); // estimate frame type: $firstByteBinary = sprintf('%08b', ord($data[0])); $secondByteBinary = sprintf('%08b', ord($data[1])); $opcode = bindec(substr($firstByteBinary, 4, 4)); $isMasked = ($secondByteBinary[0] == '1') ? true : false; $payloadLength = ord($data[1]) & 127; switch($opcode) { // text frame: case 1: $decodedData['type'] = 'text'; break; case 2: $decodedData['type'] = 'binary'; break; // connection close frame: case 8: $decodedData['type'] = 'close'; break; // ping frame: case 9: $decodedData['type'] = 'ping'; break; // pong frame: case 10: $decodedData['type'] = 'pong'; break; default: return false; break; } if($payloadLength === 126) { $mask = substr($data, 4, 4); $payloadOffset = 8; $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset; } elseif($payloadLength === 127) { $mask = substr($data, 10, 4); $payloadOffset = 14; $tmp = ''; for($i = 0; $i < 8; $i++) { $tmp .= sprintf('%08b', ord($data[$i+2])); } $dataLength = bindec($tmp) + $payloadOffset; unset($tmp); } else { $mask = substr($data, 2, 4); $payloadOffset = 6; $dataLength = $payloadLength + $payloadOffset; } if($isMasked === true) { for($i = $payloadOffset; $i < $dataLength; $i++) { $j = $i - $payloadOffset; if(isset($data[$i])) { $unmaskedPayload .= $data[$i] ^ $mask[$j % 4]; } } $decodedData['payload'] = $unmaskedPayload; } else { $payloadOffset = $payloadOffset - 4; $decodedData['payload'] = substr($data, $payloadOffset); } return $decodedData; } } ================================================ FILE: tests/include/lib/src/ServerManager.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; class ServerManager { protected $host; protected $file; public $port; /** * ServerManager constructor. * @param $file * @throws \Exception */ function __construct($file) { if (!is_file($file)) { throw new \Exception("server file [$file] not exists."); } $this->file = $file; } function listen($host = '127.0.0.1', $port = 0) { $this->port = $port == 0 ? get_one_free_port() : $port; $this->host = $host; } function run($debug = false) { return start_server($this->file, $this->host, $this->port, "/dev/null", null, null, $debug); } } ================================================ FILE: tests/include/lib/src/TcpStat.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; class TcpStat { const SS_NETSTAT_TCP_STATE_MAP = [ "established" => "ESTABLISHED", "syn-sent" => "SYN_SENT", "syn-recv" => "SYN_RCVD", "fin-wait-1" => "FIN_WAIT_1", "fin-wait-2" => "FIN_WAIT_2", "time-wait" => "TIME_WAIT", "closed" => "CLOSED", "close-wait" => "CLOSE_WAIT", "last-ack" => "LAST_ACK", "listen" => "LISTEN", "closing" => "CLOSING", ]; public static function xCount($path) { if (PHP_OS === "Darwin") { $n = `netstat -x | grep $path | wc -l`; return intval(trim($n)); } else { $n = `ss -x src $path | wc -l`; return intval(trim($n)) - 1; } } public static function count($host, $port, $states = ["established", "time-wait", "close-wait"]) { if (!ip2long($host)) { $host = gethostbyname($host); } $pipe = "wc -l"; $func = PHP_OS === "Darwin" ? "netstat" : "ss"; $states = static::fmtTcpState($states, $func); $info = []; foreach ($states as $state) { $ret = call_user_func([static::class, $func], $host, $port, $state, $pipe); $info[$state] = intval(trim($ret)) - 1; } return $info; } private static function netstat($host, $port, $state, $pipe = "") { if ($pipe) { $pipe = " | $pipe"; } // $4 src $5 dst $6 stats return `netstat -an | awk '(\$5 == "$host.$port" && \$6 == "$state") || NR==2 {print \$0}' $pipe`; } private static function ss($host, $port, $state, $pipe = "") { if ($pipe) { $pipe = " | $pipe"; } return `ss state $state dst $host:$port $pipe`; } private static function fmtTcpState(array $states, $type) { $from = $to = []; if ($type === "ss") { $to = static::SS_NETSTAT_TCP_STATE_MAP; $from = array_flip($to); } else if ($type === "netstat") { $from = static::SS_NETSTAT_TCP_STATE_MAP; $to = array_flip($from); } $ret = []; foreach ($states as $state) { if (isset($to[$state])) { $ret[] = $state; } else if (isset($from[$state])) { $ret[] = $from[$state]; } } return $ret; } } ================================================ FILE: tests/include/lib/src/ThreadManager.php ================================================ parentFunc)(); } else { ($this->childFunc)(...$args); } } } ================================================ FILE: tests/include/lib/src/WaitRef.php ================================================ | +----------------------------------------------------------------------+ */ namespace SwooleTest; use Swoole; class WaitRef { private $cid; public function __destruct() { Swoole\Coroutine::resume($this->cid); } static function create() { return new static(); } static function wait(WaitRef &$wr) { $wr->cid = Swoole\Coroutine::getCid(); $wr = null; Swoole\Coroutine::yield(); } } ================================================ FILE: tests/include/lib/src/responder/get.php ================================================ \n' + ' {0} merge [base] ').format(cmd)) def process_args(argv): args = [] skip_next = False for i, arg in enumerate(argv[1:]): if arg == '-h' or arg == '-?' or arg == '--help': print_usage(argv[0]) exit(0) elif i == 0 and (arg == 'diff' or arg == 'merge' or arg == '--temp-project'): args.append(arg) elif arg == '-l' or arg == '--line': args.append(arg) skip_next = True elif skip_next: args.append(arg) skip_next = False else: path = arg if ':' in arg: file_path, line_number = arg.rsplit(':', 1) if line_number.isdigit(): args.append('-l') args.append(line_number) path = file_path args.append(os.path.abspath(path)) return args def try_activate_instance(args): port_path = os.path.join(CONFIG_PATH, 'port') token_path = os.path.join(SYSTEM_PATH, 'token') if not (os.path.exists(port_path) and os.path.exists(token_path)): return False with open(port_path) as pf: port = int(pf.read()) with open(token_path) as tf: token = tf.read() s = socket.socket() s.settimeout(0.3) try: s.connect(('127.0.0.1', port)) except (socket.error, IOError): return False found = False while True: try: path_len = struct.unpack('>h', s.recv(2))[0] path = s.recv(path_len).decode('utf-8') if os.path.abspath(path) == os.path.abspath(CONFIG_PATH): found = True break except (socket.error, IOError): return False if found: cmd = 'activate ' + token + '\0' + os.getcwd() + '\0' + '\0'.join(args) if sys.version_info.major >= 3: cmd = cmd.encode('utf-8') encoded = struct.pack('>h', len(cmd)) + cmd s.send(encoded) time.sleep(0.5) # don't close the socket immediately return True return False def start_new_instance(args): if sys.platform == 'darwin': if len(args) > 0: args.insert(0, '--args') os.execvp('open', ['-a', RUN_PATH] + args) else: bin_file = os.path.split(RUN_PATH)[1] os.execv(RUN_PATH, [bin_file] + args) ide_args = process_args(sys.argv) if not try_activate_instance(ide_args): start_new_instance(ide_args) ================================================ FILE: tests/include/skipif.inc ================================================ = ' . $require_version); } } function skip_if_php_version_ge($require_version = '7.0') { if (version_compare(PHP_VERSION, $require_version, '>=')) { skip('need php version >= ' . $require_version); } } function skip_if_php_version_between($a, $b) { if (version_compare(PHP_VERSION, $a, '>=') && version_compare(PHP_VERSION, $b, '<=')) { skip("unsupported php version between {$a} and {$b}"); } } function skip_if_not_debug_version() { skip('only in swoole debug version', !SWOOLE_DEBUG); } function skip_if_ini_bool_equal_to(string $name, bool $value) { if (((bool)ini_get($name)) === $value) { $value = $value ? 'enable' : 'disable'; skip("{$name} is {$value}"); } } function skip_if_no_nghttp(): void { if (!str_contains(shell_exec("nghttp --version 2>&1"), 'nghttp2')) { skip('no nghttp'); } } function skip_if_constant_not_defined(string $constant_name): void { require_once __DIR__ . '/config.php'; skip("{$constant_name} is not defined", !defined($constant_name)); } function skip_if_function_not_exist(string $function_name) { skip("{$function_name} not exist", !function_exists($function_name)); } function skip_if_class_not_exist(string $class_name) { skip("{$class_name} not exist", !class_exists($class_name, false)); } function skip_if_extension_not_exist(string $extension_name) { skip("{$extension_name} not exist", !extension_loaded($extension_name)); } function skip_if_file_not_exist(string $filename) { skip("file {$filename} is not exist", !file_exists($filename)); } function skip_if_command_not_found(string $command) { skip("command {$command} not found", empty(shell_exec("{$command} --help 2>&1 | grep -i usage"))); } function skip_if_no_ssl() { if (!defined("SWOOLE_SSL")) { skip('no ssl'); } } function skip_if_openssl_version_lower_than($version = '1.0.0') { skip_if_no_ssl(); $exist = preg_match('/openssl => openssl ([\d\.]+)/i', shell_exec("php --ri swoole"), $match); assert($exist); if (version_compare($match[1], $version, '<')) { skip("openssl version {$match[1]} is lower than {$version}"); } } function skip_if_no_http2() { skip('no http2', !class_exists(Swoole\Http2\Request::class, false)); } function skip_if_no_ipv6() { skip('no ipv6 address', !@stream_socket_server('tcp://[::1]:0')); } function skip_if_no_top() { skip_if_darwin(); skip('top provided by busybox (not support)', !empty(shell_exec("top --help 2>&1 | grep -i busybox"))); skip('no top', empty(shell_exec("top help 2>&1 | grep -i usage"))); } function skip_if_darwin() { skip('not support on darwin', stripos(PHP_OS, 'Darwin') !== false); } function skip_if_darwin_todo($msg = 'Need to review') { skip($msg, getenv('MACOS_DEV') === false && stripos(PHP_OS, 'Darwin') !== false); } function skip_if_not_darwin() { skip('only support darwin', stripos(PHP_OS, 'Darwin') === false); } function skip_if_nts() { skip('not support in nts', !defined('SWOOLE_THREAD')); } function skip_if_not_linux() { skip('only support linux', PHP_OS !== 'Linux'); } function skip_if_musl_libc() { skip('not support when use musl libc', !empty(shell_exec("ldd 2>&1 | grep -i musl"))); } function skip_if_no_process_affinity() { skip('no process affinity', !method_exists(Swoole\Process::class, 'setaffinity')); } function skip_if_in_valgrind(string $reason = 'valgrind is too slow') { skip($reason, getenv('USE_ZEND_ALLOC') === '0'); } function skip_if_in_ci(string $reason = 'not support in CI') { skip($reason, !!getenv('GITHUB_ACTIONS') or file_exists('/.cienv')); } function skip_if_in_docker(string $reason = 'not support in docker') { skip($reason, file_exists('/.dockerenv')); } function skip_if_offline() { if (getenv("SKIP_ONLINE_TESTS")) { skip('it\'s the online test but you are offline'); } } function skip_if_no_proxy(string $host, string $port) { skip('no available proxy', !check_tcp_port($host, $port)); } function skip_if_no_http_proxy() { require_once __DIR__ . '/config.php'; skip_if_no_proxy(HTTP_PROXY_HOST, HTTP_PROXY_PORT); } function skip_if_no_socks5_proxy() { require_once __DIR__ . '/config.php'; skip_if_no_proxy(SOCKS5_PROXY_HOST, SOCKS5_PROXY_PORT); } function skip_if_pdo_not_support_mysql8() { skip_if_no_database(); require_once __DIR__ . '/config.php'; try { new PDO( "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", MYSQL_SERVER_USER, MYSQL_SERVER_PWD ); } catch (\PDOException $e) { if ($e->getCode() === 2054) { skip('pdo auth not support'); } } } function skip_if_no_database() { skip("no database", !!getenv('SWOOLE_CI_IN_MACOS')); } function skip_if_not_mysql8() { skip_if_no_database(); require_once __DIR__ . '/config.php'; $skip = true; Swoole\Coroutine\run(function () use (&$skip) { try { if (preg_match( '/caching_sha2_password/', httpGetBody('http://' . MYSQL_SERVER_HOST . ':' . MYSQL_SERVER_PORT)) ) { $skip = false; } } catch (Throwable $throwable) { // ignored } }); if ($skip) { skip('require mysql8'); } } function skip_if_not_redis5() { require_once __DIR__ . '/config.php'; $skip = false; go(function () use (&$skip) { $redis = new Swoole\Coroutine\Redis(); $redis->setOptions(['compatibility_mode' => true]); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $info = $redis->eval("return redis.call('info')"); if (preg_match('/redis_version:(\d+)/U', $info, $version)) { if ($version[1] < 5) { $skip = true; } } }); Swoole\Event::wait(); if ($skip) { skip('require redis5'); } } function skip_unsupported(string $message = '') { skip($message ?: 'the test cannot continue to work for some implementation reasons'); } function skip_if_no_coroutine_get_execute_time() { skip('no Swoole\Coroutine::getExecuteTime', !method_exists(Swoole\Coroutine::class, 'getExecuteTime')); } function skip_if_no_ftp() { require_once __DIR__ . '/config.php'; skip('no available ftp server', !check_tcp_port(FTP_HOST, FTP_PORT)); } function skip_if_not_root() { skip('not root user', posix_geteuid() !== 0); } function skip_if_no_iouring() { skip('no io-uring supports', !defined('SWOOLE_IOURING_DEFAULT')); } function skip_if_not_defined(string $name) { skip("`$name` is not defined", !defined($name)); } ================================================ FILE: tests/include/ssl_certs/ca-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKDCCAhACCQCEAJc680CvRDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc0OTA1WhcNMjgx MjA0MDc0OTA1WjBWMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTELMAkGA1UEAwwC Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDN8IQ468DyDRIIMSx4 v2Apmrbe1vIHYNOfW7DexM+DK8TX46l3v7SsLCHCYrKmEnl2SoDyjkDBQukrZRwQ lZ2VaNmllK3A3HQVl2tdFWHkLz/Qdq5bLnUEz1/tI33DcjJ8fo70wpz6pAgEsHZt +AX1MbZVWJDbMpH8SHaJduHsOD1WQo0qiJF1Z6PdrP9SPD2Z9g56b/TKA7BU9sd+ N7QsChqQHFWjsyC80Hl6f2c+YaYjd4/KbunzIvhFIuK/bOGB0U+Cu2I8Fq4yGHFA F/i/+Rthabc9jxWsbbAMFB2b/TkDhuFrjH+J7Bbqd8cZdUshuQv6l3wU4m5bv5Zm 7s1rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABkr7mqcRBJEBG+argNC0n+IY6Df kd3YRgNhAdedUfyrG6C+SwaFiAkhgZQH8NuHEzANAnLZe8DlqYEsmWJGRT7jy0Pa I8a8PA8PajSAg3tp6wR7ST2E4ZiQwjJsDxaFI+yDMiP5uXyHEA9WHQZ9OiWRDO/9 X8dJw/uuEk4dXdpyqTfNleHN/5TGJP07qrPSRr0+1+HVw1YZjgKsn7ivgAoaNxEe fDnhoF8WncswJLP3e5D0EG6zrod/8r6H009CJWGadaGWEtcvOoGq6E97mCwRrKmI 25Fr/izhV9gvf/Uj+nDZfvJwdx7GjxoHM+D3uc/2D8AxRkAtIPcABTyvPvI= -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/ca-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAzfCEOOvA8g0SCDEseL9gKZq23tbyB2DTn1uw3sTPgyvE1+Op d7+0rCwhwmKyphJ5dkqA8o5AwULpK2UcEJWdlWjZpZStwNx0FZdrXRVh5C8/0Hau Wy51BM9f7SN9w3IyfH6O9MKc+qQIBLB2bfgF9TG2VViQ2zKR/Eh2iXbh7Dg9VkKN KoiRdWej3az/Ujw9mfYOem/0ygOwVPbHfje0LAoakBxVo7MgvNB5en9nPmGmI3eP ym7p8yL4RSLiv2zhgdFPgrtiPBauMhhxQBf4v/kbYWm3PY8VrG2wDBQdm/05A4bh a4x/iewW6nfHGXVLIbkL+pd8FOJuW7+WZu7NawIDAQABAoIBAG8NrW+4ikK9E6VB RzcSFHw7BzRh+wBJkbUUz+tBfJnCp5K4HTILOhXCcq4NZUMzITbN9H1Ygsj1GSIi kyDKXcEtWnj1FUG0YBEop2f52OEhLatv5JQdGFNwtMz/X5le4qTJ5rNMozRx8jT9 J9AjeKsBlM6vROY/fgPq26lDZ49mx9Rp8tIV4lps9cZ4YrsMu47tXTFjK2dYyVdh NbMNE1tgfIQd+ZH5HpLcEZas72y7djRC3UNRqlHJ6CvP1Kb98+ILa+CLj94mN+NV VvoqzLHLk6n7nam2FKVHMfHl0fSmQ1TQmI3dyyh62I7wpYAPi2Sk+s8UQvjRjKwI LCJkGZECgYEA+8DLpKnIvWKf864nknEt6XF9gV/Ft0Mld61rzkTJy5zVjD9FXqJl F8Cfn6RfSb1dw+wqXpoJd2R+WRTI60jCe9tfY8G7Kpmz6jV+At4jQ932QQyNRJnG yVYn8wz4XsPGgfz0OaFDZvIlIcQIEfkEOaow019+sU6K9S6oZsILvHMCgYEA0Wnf mZFlw6C9cbSb1f1Nl0YGm1NLMJuIcYudRx8OfcbNSlRFCQrVIjNcjIpnNE1ltd89 PrRZJnkkX0lxkg9jPFqSeYcvhAksZrhO0a7ZeKHyH7F2c6E6LIqwZnoZ3tRUWzm+ M7L5Bh6TS5G0/Y0tPB3UngRy6SWmkjTI1kCrJSkCgYEA+buHh4GRCyUxjHXe9Wrm Y2NcX6EUrbWjDjPu9D/SefPK9oOxGa44YDaJYcCZcbmysO6uHQfqihAbMdznblbP 7jNwTbHZK+oqyhNkPA4Fp4YgiOidnkZ5JjIcEQN1wfOtEDdQQXbSOOXNpdAjPUjr QxSruJJLSDGksJEnJkApijcCgYAvl24XinGkKe2j37XixfRSQrnRpvZZj59MnrwZ o7c5YmrZT5l42pthcGbCEdouisBoutlCXN493h4kAZ7r4M9esf7D27MywJr0pUJU aPZHchaCmWQgFy4PXV2FjI5Ak+Sv7smJGk151I0JCY4maWU0WlstpjkvPz1B0Pkr p/q9MQKBgQDjVs5yfWb8PEwlw5oqkk8IyNVHKMz/62r7tfuT1gGr9OepWXv900fn Z7TavI6tr0x3zCblqTSDROUYYV6sadMoCMrHQ6yihwJNM4Htp2Qi7tJJs1onC8wE p2gXL1fPFq0sjSaRcZhaxSrpF2KAfIPmoC17Li30A2iVe2FyLIQZvg== -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/ca-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmzCCAYMCAQAwVjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCzAJBgNVBAMM AkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzfCEOOvA8g0SCDEs eL9gKZq23tbyB2DTn1uw3sTPgyvE1+Opd7+0rCwhwmKyphJ5dkqA8o5AwULpK2Uc EJWdlWjZpZStwNx0FZdrXRVh5C8/0HauWy51BM9f7SN9w3IyfH6O9MKc+qQIBLB2 bfgF9TG2VViQ2zKR/Eh2iXbh7Dg9VkKNKoiRdWej3az/Ujw9mfYOem/0ygOwVPbH fje0LAoakBxVo7MgvNB5en9nPmGmI3ePym7p8yL4RSLiv2zhgdFPgrtiPBauMhhx QBf4v/kbYWm3PY8VrG2wDBQdm/05A4bha4x/iewW6nfHGXVLIbkL+pd8FOJuW7+W Zu7NawIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAMs2wMLBDqw2TxBy2wIM2ytp Lupk/3QVkvMpBIiYD7Jbod5YRjkV+LagLq/KE10y0jWSGuQSpbJHypb/bXsvKcU7 18uzM/CU08xz5WWQ7LqAvITT/4GHT/WNLn38WBkIG6qHYeaaaLcGdo53KlbQiYqq 5An3sxVin1C5dHR2ew5bKMNh+Zfq8kNXIPQbKd2KcEDKPYvt7Zb92SOijpkK/SVb hWgHkGsyFothmIpdKarOsQ2E2SSyTtNUKws/ZcRq1KQs7qAxNT7As4vBAfCGv0Yv s7gohLXDPZDWvoXmRWOPiY7nYlTzxBDKXEwtEqN0ZJcQv9OHJveQ6SSK3o75EDM= -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIFijCCA3KgAwIBAgIBATANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJDTjEL MAkGA1UECAwCTkUxCzAJBgNVBAoMAk5FMQ8wDQYDVQQLDAZSb290Q0ExDzANBgNV BAMMBlJvb3RDQTAgFw0xOTEyMTMwNjU3MjNaGA8yMTE5MTExOTA2NTcyM1owSTEL MAkGA1UEBhMCQ04xCzAJBgNVBAgMAk5FMQswCQYDVQQKDAJORTEPMA0GA1UECwwG Um9vdENBMQ8wDQYDVQQDDAZSb290Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDAEChFm9jAOzkqunXYIqoLMJ5GlpaQpznDOaA5eQ/kRE1eSq0SkkPv zfFOMdGcjvMSnke7xcUx0n4N4ATSlr/J0MXV3/4ATDmtP663aVyQRM+SDxSYRIBV epjKv8f7v9KMV1YgsnJD9KLaRHf2mZkVCEtD/eEAOaXyuk3IZgITnG6mynhhxJ9T ICRDtqPZ7b2NBxk+DEjmhruQ+oEFwApMtLuIoeETCXfRu48b+ee4lHSzrAMZKKGy OPQ5BLA5GJXdRx/ien5BViHsD8bZygmAkTFwayVXtr8HG4OZMnaOTjJhCBvt5GMp 2digT1bcwhYxh70NQ8FIQipRCOxsBObVxDglqVLLdjmREpD8OrGA4B++wNjSf8qH liX+Idu1CCGrNajKDDx2dFcaZW0e6ZpCTZt8QUC9hOfBUUjIASqw04dbPlLCzYAW VLPFAmHf/wAwjDoNLKtSbJxAPRW/uAorUnd90rAYWphH3aewgvu/njvzdLRBiyif 13Xo6/dmuWpIA7uGHyUtZF48GmVa+9JMVIVW/UF9G2Hlzi/CN95Bn/vumIvFJEu3 P9wqn+Ps/MlN4IL0VkRvPi7JYMyPQkCwF5QuI8pyzrOrUNKio1wtiehtY1YI8kVJ By6bChiJn5/QAm/JUSfjnnfZs8sKwgrm9vUMyDs7aTMDkWkTUXdN/wIDAQABo3sw eTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBD ZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUSNkyUSoxslFEjb9QtExEqVeYLsAwHwYDVR0j BBgwFoAUSNkyUSoxslFEjb9QtExEqVeYLsAwDQYJKoZIhvcNAQELBQADggIBAElS C/4sgbqaed1BG2aKQv/iOXT+bZeIavp+ZbUGHm968MUREYcCVLfweyHLVasWpg7T 82x07kkabfDtYPkVJM23JxX6cVzpktl7jBTnq4jMUSRi/Ueig4jBG8Hjlp/Y6Z37 0Gr8OJaUorbmmZLDTMytW++Dqu7x/QLZ/zG24zQRFP+B6KyHjDEuhrM84KZoUMwI 3U70EwkGgiQixXnuJ1l1mhoA52c/ettMEp8x/bJc6/thNQtU3NrdrnzEzBoYNevQ XNgDVI6pY8cYvUr/gfiRra+ZJJ2rdYhI6quWSxwvHCvmDQcfGGYH1v41TRZOjVYd +Kz2dpeOtsVYMdMW0UzsTD+fwo+2H6pOm3Y4dF/oaGlzu5oEg7aPolsX/3libt8s boPdeKmyh4MPZHIlHcXsPWQy0nl9Xj8LOf5OTAZOfyw6AaB0G4xVhgOiMa53fo9O R/mlBRr7j2oFu+duV5/htIDPO/FWT4KpWRg5ChdNkFckibVmaU/6RLdx/60aOZsh mNl3kqnPc+KWR/0o5xNuojg1PAU+9tdUxT/HEPdXEoedu2nzM1x4OTHhhqw2P2S/ Jb5TRfgsOav9LeYGGb3EKrcVrEt4iwRArLenMo2Hf5MwaRkHoIB+zgk6eEKzy6uY JCJ5q215xxaQrr/qXTW0yFoy2PHK8LNfZ78IGD2l -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/ca.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIBlzCCAQACAQAwVzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw DwYDVQQHEwhIYW5nemhvdTEOMAwGA1UEChMFTXkgQ0ExEjAQBgNVBAMTCWxvY2Fs aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAkkn6a1CZrX/cDofktWZT GDMIwIVfMsSOCSTBGTeQoCc7i/Zn6minMyJsD5+s3W6ctpja0JlwKt53ZVgNgrm9 H6NZyKNKqdWY8ElZwKE7nJ1LGlpS6nbY+M2VVedZLetU66+pi5/tAOsewRXkDemI 4xi6BRU4jIlvT84ovReklXUCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAI+msopi c6QIu0yYwPix0DyC8FEONih0kOQ//oe1bMLwbox/uxSoRmoJGiOdF7jqZceA0k8H 59SWMGzIUt5MRhDrye/7/LedXDVdCPacYLx7OzIlkBv9Dklk9o26jjT9u3Il7fQx OfcCm2Lk+cwv3xXm6ZtEmklqKbnAnKd1oBfv -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/ca.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEAwBAoRZvYwDs5Krp12CKqCzCeRpaWkKc5wzmgOXkP5ERNXkqt EpJD783xTjHRnI7zEp5Hu8XFMdJ+DeAE0pa/ydDF1d/+AEw5rT+ut2lckETPkg8U mESAVXqYyr/H+7/SjFdWILJyQ/Si2kR39pmZFQhLQ/3hADml8rpNyGYCE5xupsp4 YcSfUyAkQ7aj2e29jQcZPgxI5oa7kPqBBcAKTLS7iKHhEwl30buPG/nnuJR0s6wD GSihsjj0OQSwORiV3Ucf4np+QVYh7A/G2coJgJExcGslV7a/BxuDmTJ2jk4yYQgb 7eRjKdnYoE9W3MIWMYe9DUPBSEIqUQjsbATm1cQ4JalSy3Y5kRKQ/DqxgOAfvsDY 0n/Kh5Yl/iHbtQghqzWoygw8dnRXGmVtHumaQk2bfEFAvYTnwVFIyAEqsNOHWz5S ws2AFlSzxQJh3/8AMIw6DSyrUmycQD0Vv7gKK1J3fdKwGFqYR92nsIL7v54783S0 QYson9d16Ov3ZrlqSAO7hh8lLWRePBplWvvSTFSFVv1BfRth5c4vwjfeQZ/77piL xSRLtz/cKp/j7PzJTeCC9FZEbz4uyWDMj0JAsBeULiPKcs6zq1DSoqNcLYnobWNW CPJFSQcumwoYiZ+f0AJvyVEn45532bPLCsIK5vb1DMg7O2kzA5FpE1F3Tf8CAwEA AQKCAgEAps9hqtNotcy6IfpAT7ALsBxR20z/CDO1X3G1TzVuL/uCltqU5wRSLPzZ D+kE/MzLIOiHtPZYBqWjBboDdkOn30iMCu24VdneXjJpNLM1ybd58IlYgcc9DwCj o7RdpLE91NYl39AcnhjjOF8vMebFphbGCA+AVAm9r8tPszWlqBK9p+9CDugO2MuZ s5X1SVnKv6niqplQ16c9UPXBCZV5XJW8KzctnLMkdexIpyutrgqCMWq8ZiV8Cqac tsjzpSNNRhp3/uQUs/pzC+vH8XAgQKHaJqFolX4K+8lRysShY4dXCh9Y4ozx829Z TiKadrG7RNl3wQWP4YpV5nuxmz/Gm3Gr/5VIIyoiDm8H1D7zC06JZUOTms+c7lU+ kv/eKMI9nhgfmoG0skqhYrRD5jTCinqp3tb9nb92Po2mlhRoUPA9d6QkWrccqGJI oIQv+B9N6si81LWTua7F//nqyRy9la65529pD90OGUROh73+UmLPLGK/mPRiANRv nP8DIh3Yz2sIjfY8ZGrHAdDkoDEmFklNW7wn0/DikVounfSI0BrQxBF364crpRBD geo/oW7LgHtgwvLUNU3WP/T97ghepYT2V3NjpbqP7Ap2L+krSfujGOVUUq4bVD5C U7H7WdpkqgNSLHeYe1Qoqg2zDlF/FvEAOq0NSklqr3ufIWlnbkECggEBAOTHVIUw o0m7fA9outAI97NmhKJ1U41mDEglmxZNiDMC2k4DoWBJX4TzPb3oDVK5vB56jrHW jlR3Rj6vb729epWJMvDsZjiwwoYZTjqJB71iM7I60r0D4tjGhKAnD2mE02fyCQWt OgerrcpDCbQldRCHsjSoHfhj1W6kRwGQUQjCx4mpCmng/3sDpdi/srNirPBK7qdK fltOr+v2twPIZf0jKlENL1UIQkk5mBpBIC9YXbmmwkPWD+kh5KAiH3PASoKh5qKo qWXPJki7bRwRtMS+tE4VqulNbk1GOJOVD1rUserC1CyA7yu2Lk0Tjapz0D5DI955 tkVNLMJY8mri8H0CggEBANbqdfYUckh2MyixSjOKKIZB5jCy4C/rhmmvLZ8RydAX JpWMZIBF5fgLeGNFevkHK6Jaojaq0dVa7/2+R6CD0chYJQ6Pa5sAVbgA0L7r94bH tynpOEQ5Taw7IYuvdSg+YRuQeXjsRqziWuIsScdKN9azgL8vGF2TlQWME3nIn091 UlUxRyVxQDF6n++C0tJbOW0QG6eo/UWaz20kmzfw2WzM5fJiEPlvqFyZyInf6f1Y QkORHLgyP6kMZlbSN6YlhR7OPj/r9IWv/qJmdow95oPV+fQgRdhnWMtK7QouXe0m 62VyPRiKX5OpGl81ubwILEX1GsIR3ftA7+5DoTRB3SsCggEAMdI21cqBxMzPd4uX IzbQuoDY4Ygrad+PTSbjXzgDMRit8c9YlV9lLc7K41Blll5cWUbXTGMtG7fd1Ejg TLG2wFngfhb9K9tSgWGBZ73vsY5xM58bxY3TGOIif74B3TjP0RMkY9gVixYYArQW qOR/WdozNMRnwm/7Stz7mZuUChRg39mwKt6UxHVG6KzWfbo/bxTiZAwU+E1qgjh4 IprBmx7NWFlGtX3W8ZtZRF5qY+UahBOMurkjPeHjp/vGXa7CizA2ybVxLnquG6i1 WieA9leRbG/LiIMUQj/IklsjvOQU2ssS6Z4qd5p0+tQHRddywhxc81YOG4ly9d/o 6t4BAQKCAQBpZZpsECLrvmvgbPHCzOKK1Q6am16uoYbNlYd+Fy8tyey2Mp+m+KCL QLTNmmlwbz/K4z/iJ2u4glxnyEVARuU+p1ulBFj1LvjawCufNgGyK4d8Gk/pxWB+ MpGE+4stLUpaRkQMNXODnY6MlMOp5I/1zxo4qFP+sD3I9qUmeGkIgrTZ6szFZADj QwvkXfgeCXbkMZ4eXxW2aPGIyQcGLCu029d5QvaoHUYoA5MBarpJ8Fqu4B+BhcMK mHYn5fg8lvHt5Z9JoeHNHj52TmGc7U57IRjvzFP0iNpW+Gu/LnYJs6iFm7yyqEmv FKNGRgbMXAW6WTXCLxj5+yKvaqNICXT5AoIBAQDEMMshY8gSzP/ZuawnYTtHS5Cm wtsBKCaEZ81hwUJoxhczCBOO1ad9qvoxHumc+zJ4JrTRKrFa9Bi9V9ph9hQsEtE2 BHTmRSLoJoBDcgq1wPd0IHOAdMx9ThEaGCVQtF3mYdGSPQjwbzqwQZEOptXYxSXY 4LxNlP0D7JT5LKCqWmKpbczVVh7N/0S4/COJnEwp7yYnB258emksbvh/VhCz/GBB MxEH7kwFTIT1qSk9cWCSSp15i33rievMJbvxRx9WjFTAWj9ofgIuUzxY5zP+FOBo G+XV21OVfc2wEvFgs+piKYINVxpKwVHCCVP5FUrebpxCZX34xy4nbmgPbiaL -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/ca.srl ================================================ BC864F1DFA88521C ================================================ FILE: tests/include/ssl_certs/client-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDKDCCAhACCQCRX5mTEHplWDANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc1OTQzWhcNMjgx MjA0MDc1OTQzWjBWMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTELMAkGA1UEAwwC Y24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDATA3sGW041+GHub7 GgfAA1vcZP48Gw12F2jT15mX8SMjjQ8AutdnxG3OMt5lzgpw5+SVUgxK0nIdGg0f O0Rseh3VKYl7F2neUWzETfLo3qywVsu21fJiuxwtS8h+r/pM/0/drC20qbKzDEDT Q4hwVsCMjx0CUxZoFgiHBRhyIT6DqBdt8fylSfC0ocvLXI32Hl75QEhjrSqZqvHY z7cT5+YzuL/SQ5QOEtCGQarExhgqgQEtpD5ZGhbalDhrZDI/ZwRnA27I54s0VCTY ZT6XNASf1UUH6xqV9IT6lY2GQ9rNkzKVjaG5/gJDcKPO+sBLsjzgsW4qJ8GiLPLY tyIZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAICx9BbAwat+nF3Tcw3y4XyX0fpb J3YXAyIESNKEh1F6IRVdPkcHTMd2YU583pGD38UJzWOVj92bBfRRXUbINnCoqM30 QHiBfg0kQhc9UNKY+62urhSfUs4HGiyrRVwVbSDbqZpzg5MXa+msgktWMDL1KL8d gljnqn/Rad/hYKgjIGJ0MIk55qKl6z6tlVwIIPkdEm6FFcRhejLgkCpDC4WBwbmi U1hpSQfoRXICVyO96mL+/wpekZTAPFZB2LOaZOkT4/hGhlNVFZfcgws+hTh11EZb wfpgVYxk/T9MfgEyZGBtarUaNR/xobdnABo5eyxXrrd0RU7SjIrIRA5ORxE= -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/client-expired.crt ================================================ -----BEGIN CERTIFICATE----- MIIFtTCCA52gAwIBAgIBATANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJDTjEL MAkGA1UECAwCTkUxCzAJBgNVBAoMAk5FMQ8wDQYDVQQLDAZSb290Q0ExDzANBgNV BAMMBlJvb3RDQTAeFw0yMDAxMTYwMzA3MTlaFw0yMDAxMTYwMzA3MTlaMEgxCzAJ BgNVBAYTAkNOMQswCQYDVQQIDAJORTELMAkGA1UECgwCTkUxCzAJBgNVBAsMAk5F MRIwEAYDVQQDDAkxMjcuMC4wLjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK AoICAQC/GbFTW4fv4YtzjPpVVdXbKTkJv+od8A6azyI9sWwl5nj1NXV4rLw28/ku 1xka5La67TQ5y44D4StGSsfLuVzehqWed9NqY2t+u/kE82TXyQ90OuabXZlUXfzV T6U2o1l36ko97Q9MYws4izVX4vE8zsCdatNM74kyEsUbHJ3Gut2IKLM148D7UuQo iUhwmmvFCCTL9YdW1HjEtbgg3mzg8XFtAA4mzKv+VgHe4VnAA/Fh1dPtNXpoa7IA hheWS8DC2GN44/BegnBqG877tKfbooCyjBxkE9HKroj1y+f3FrGWCQGEAsHn6Za3 61OQkYcjLQ2eZ+CC3rX0HBk+yY5S6hnTjQ7Z4aFBgsxnqg9WFICjXtI5RmkC6NoO wnFr8DJ3MUEhDE957wjSNPirlvrLxX6kyONVKHsChvBKu0iFgpd5VlLKoCxqwOib gEAJS960wkMVvY91LxDfp8waoAhT02S7ysiCll5aA4UyyHA4hOavOeHsv3hA0sq8 HlbJ+duw/IHLGFlEXgBC/zVsKbVHliIU03oUBEEA0oimcDJGSClTzcPXL43xpqAg kb3I4GPFLhu1r/IexeLoUrkA4KHx1LSLzXf+P9Y1VFGOrRvI7zMS8R8LryqGr7mF xgOxcm2uS0i5/OTYYSk+mQVu609epLl5GgfHeNIQTQdnWHC6NQIDAQABo4GoMIGl MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENl cnRpZmljYXRlMB0GA1UdDgQWBBTNzUGh5F+06+7lm/pnTMcjKC/6ODAfBgNVHSME GDAWgBRI2TJRKjGyUUSNv1C0TESpV5guwDALBgNVHQ8EBAMCBDAwHQYDVR0lBBYw FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQC33cEcngVk N2lqPiZ0fuHh/Rs+LbUv6OWBIdhdHiojVaN4kq3Z0eIvZ5x1saXca9ff/rk4Brk6 K+qntCWNg9DAZ9ahjhsXAeDQJE1l7999cCJxJRa6MoBTHwHejC7XAhpVEf95Gl3C fNkDgEnBEisoy5/XL96TIOcZC/trkESXEKrPrKy+BwyikOaB1m3M2OFPFx+HomWO w4ah4c8BNK/MIRb0zj4TJ2djxOsX1lXt5DC/2AACX2n1H6l/iVJ5oxwzNJSycX9M /zAA0+lsedCiAiWZSkThinHbmu/c2QOmVIfwos/KlgXZTc7A36sYJ1wge8xYMR/0 LV6PqVdoAmq8e9uUZFgyCl1Ojb4HojLRyuUUKYOV7ywODVcezNhk5z7ft2p3Kkgh brQAd0crn3rmDoM8Uay/tovQgg84nIYWag6vpONDPR3XbQG3OvBTTdpsnphfMFvF Clsam9jV8WGi+ZEg4ODnsE3eSbG1hBjEBQw7dKq5jPEZkedNyqTNGPhIOevYWITg nOSESi4ksKroJJCAG3IRA7B10maKItK+NuDXKELze/Xj9uE1kfuEUYm9DnjuxPqI pMLTLS/1Z0NmCuwATcRJrm9gXZGdpVvrjuS2Fc5SkqZTbcRIzSbUf2cE2OJAKyj7 VkKhgAJRKDCNQZ6wru5nzGgs/zyxH20t0A== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/client-expired.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIBmzCCAQQCAQAwWzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw DwYDVQQHEwhIYW5nemhvdTESMBAGA1UEChMJTXkgQ2xpZW50MRIwEAYDVQQDEwls b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEUnBzXTTiyUmDb yhkQoQ/yH1zTnuIk5Meg1Bp0fp1l4kwiizdPbZkk4YkTT/HXdTE6822Cqho+CwGE VqWZyyd2AZmj87OGb4ZRCyyFzzjfEwdCTvyqZSUBoc1gvSGdEiaA4mXE87Y0XcMB BasOrfmO76nuzyaXLT7xDjrB+Qw5AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQAx rsaWSV81/SCf+0af57Wr+BJfiGEutZpdmIe0ofPKfVfz7c8QKjqK+/xQb0INUaYd MUPjuLfvp06iCWyDPsfhsBRZMSDfFZDp8bnoVloVbP+yLL2Gd+h/a5iYjKTJ2FEt mDaoIXqbw7oHXXxfKKLP2iyUQCqbfJTC0XeJtFWJ3w== -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/client-expired.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJKAIBAAKCAgEAvxmxU1uH7+GLc4z6VVXV2yk5Cb/qHfAOms8iPbFsJeZ49TV1 eKy8NvP5LtcZGuS2uu00OcuOA+ErRkrHy7lc3oalnnfTamNrfrv5BPNk18kPdDrm m12ZVF381U+lNqNZd+pKPe0PTGMLOIs1V+LxPM7AnWrTTO+JMhLFGxydxrrdiCiz NePA+1LkKIlIcJprxQgky/WHVtR4xLW4IN5s4PFxbQAOJsyr/lYB3uFZwAPxYdXT 7TV6aGuyAIYXlkvAwthjeOPwXoJwahvO+7Sn26KAsowcZBPRyq6I9cvn9xaxlgkB hALB5+mWt+tTkJGHIy0Nnmfggt619BwZPsmOUuoZ040O2eGhQYLMZ6oPVhSAo17S OUZpAujaDsJxa/AydzFBIQxPee8I0jT4q5b6y8V+pMjjVSh7AobwSrtIhYKXeVZS yqAsasDom4BACUvetMJDFb2PdS8Q36fMGqAIU9Nku8rIgpZeWgOFMshwOITmrznh 7L94QNLKvB5WyfnbsPyByxhZRF4AQv81bCm1R5YiFNN6FARBANKIpnAyRkgpU83D 1y+N8aagIJG9yOBjxS4bta/yHsXi6FK5AOCh8dS0i813/j/WNVRRjq0byO8zEvEf C68qhq+5hcYDsXJtrktIufzk2GEpPpkFbutPXqS5eRoHx3jSEE0HZ1hwujUCAwEA AQKCAgEAjiXhZn0GX+Qoo/ow17syiYRfXiY4Uq6XXoZHFRbduE94mAV87ReoJURT jYkIrp2EYwVAvODyMWaTlwTXG241a31Cwt6lD1UGx82xDQKzd2OisDWEBfuYq04C XPWSklLtoSpQsUGeCJb/6L58HnS8Nxwf4LhulqLzLaESpvkhT4r+cGK9848HpKri 9lgv3zqugXBuVpy7YFZJwyDc5u4slSJj2c2ZuTizk0uMtqpv5UnJipnQu3j1Jak+ Te9wqiaNKyJk067RxKtITVWEIG0gE1sMWkOodrTbb+VmEVfsozppPI5UZ0rZy9VQ fFx622W03PCNWdAxC4f4Vq10BeVNjIsF3w8l/ZUzKuUbMXhTGtIFLzK3jNpw+GMJ fqhEgvJJldWrJkRYiqlP8nbzwlGL1IYQJ4kwZaRzoqMAEKZT5dJ886sF/fYDCAwz 0960gtX7m5TMrSOKxdEGX3lCqovYAkxgz9HdTVg1ITzshJE8A62wqTK18lnrzhXH 7efh6QcXDJMkXZL850RDM7Wq6j6SZqpN6DDAU7+4JssLaAWuyWMAoRqhWrASg4Hr koVe+ctb8uydbKRGZMSkvXDXwPfXc811jzoxAsf6lJeJgl20+eUCofzxina5KqHA vaYbul6IXhUKPjHhRywcUa2axstwRf9UNLakLsNjjDcQn1Y5fH0CggEBAPK+utiR EWvRncRttDA7BHCb9uMd1iVz4ZsOZzbSqaW2vc9heHGMjhlDA4v7F7MHhkMxjD3S 0hjePGr+uwUM0jkHzN3/jRLgyPNqjalH9eubFQTaJuzm5MXEbJ7Y2GyXW9kEdz7E 25D4RO0VQwzQh/ND104uBWhJvsuHQLJMwfvVcPZjBUeCEwvIynUvhev1zIqaS0lb ENxkEe2ipUK2v2e5+tVsU033VkhEVqAgHOcg8yYzREfbqEU5CO7r53XIfF+kTZN9 Jfx035zwnBITD1WaUI/No/lwx/MlAzXnEyh5Ya9fHvcp0+Rp/mk6eamcEgkgiiKb Ck4bfdX/4QSZFqsCggEBAMmJCQTfxSf0jm/zUVBvVXvLKqdUSf7h17dT/MbVfOEP EScYTkHMcnyYe9NB/yNRaa5eBJi9Tfhg+Hd2DO82kaZxmSJwwjJfd1rADELyNQDa xZLyIDFXngd8OVBIzKwSw2N6Dml8WWnS55LVXU3wsV+4SRNhqU0j47uPOtS3UoJu u8bzmOdvUEb4kzo24oKZzlFgS1GlHUwA7GtxedVH++5oqB4fifcfigKt6xM7kddL 31G25RCVaj7jIiHHmMlmBPZkjO+E52ncSbH9gKuZtRww1ZTaM6Fk5YC5CrS8/kW9 IqtW4sAe8inEbKk+m3edtWAHoe3uSIkltEEinqnK8p8CggEAFYnU3Fhq2k08GWy+ ezURXMiJ7Hb+RE526yIpCFVC1kBTsS3t2O1mwlkO3IHIBYuaXyXrUqT9HVXCuJ+n ingrwwGtLNl68/zb97Uxop6O9HvLLqYIpCaMj/uww5tsAJ6heK0Lw5cGAGP3Aa5M R7vNBEm1teshcbJxam+yzt8+qxCyODprAoBJHlePlzxDPr+NiZLNMVISOevrJ1Gy Jiw+6X44Am3dKmLS1hCyOvz+c4eZ637LBTE5SgMYhtKJtAq49meGtpp5H2BjYvJ6 dqQYTTknAst1TDG0nB9hnIvHSGUUxNHE1ptwgKji+QVGPEVr2EF54D4veOS5kp5k odrPhwKCAQAtyrhE5j9a68NthoDEYPewcCJZ8Nn7Uv97xVabKyj7zjucgXZOlxCw ZMlF4CoDsVzs5Knwtpq/w/DQ2/7mX6TKh+c+nxZQ8cYh1Z8zmusWLZ9U2zUbYQgk NUKkjlYLBxxkPviFWV7Ln8NEZryTmAwFbs8T203nnC8Dj/DTInBZ+KUr+G+WroE0 DwkM4imneSEa9mFGxNiaS19srlT7dCa3Lgp9BbeIch8eqrvOG+vwWmn8nC0CDqEc dulreXmq0UZxYblwNueJ8ya/jZUt6HfxVttAvi+bDVzkWacoGAVCfU4iVlShSKbH fL0f23zhufDbSACwGkNa/jktz5rzg4yLAoIBAHVPjXB16AaRl7Uk1e5585kD7Vpd 4K2uGnJsX7aMX1lGGm8OmlamhppgsIqECsgqM9uHLDDnW3CcZ/XTuxD/SQ1IBnZd 7v9JZwPzMJp0JXr3nn/RqCnMZytd/PHE3z0tQvwz8IYtpehEGQekfJKD/wiuOwzn WHJMTr4zbbSQk7Pp+RDb7zXxODLj5YVoCasG1RyM7JtrnvAFadS8oEghKgApJUak eSED2HxCpW5mj389qNkNgBQ9AeDizBHxSU7e2Kj0kvBCqnLiqnjlwYXQxTSdQ/sn Kuea9MuJJe7+iKL4nttqzZSa0Vz4JRpPdkAZfTcr8CeNQSdEddPpEVmnJUw= -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/client-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAwwEwN7BltONfhh7m+xoHwANb3GT+PBsNdhdo09eZl/EjI40P ALrXZ8RtzjLeZc4KcOfklVIMStJyHRoNHztEbHod1SmJexdp3lFsxE3y6N6ssFbL ttXyYrscLUvIfq/6TP9P3awttKmyswxA00OIcFbAjI8dAlMWaBYIhwUYciE+g6gX bfH8pUnwtKHLy1yN9h5e+UBIY60qmarx2M+3E+fmM7i/0kOUDhLQhkGqxMYYKoEB LaQ+WRoW2pQ4a2QyP2cEZwNuyOeLNFQk2GU+lzQEn9VFB+salfSE+pWNhkPazZMy lY2huf4CQ3CjzvrAS7I84LFuKifBoizy2LciGQIDAQABAoIBAAUtZuZTnJ+s2cBp 2aAZFNOYQzwawlU+MqFMBdB6DO3nzRMIj7lpPIbYxEGRQC5bxhMxVPRR9VmXNMhp BKgrnqlypJAgRjIqcPkiGE0t9KESKGuWIlp8W68bYKgjNvOGIJMixDASfjQpvUDb tmzlgh3Zb8tLkW2aXyHsU9Wh4QBGrcW7faiu4CcC1kHJk5FUg6eSxN0vhZxCQgKP 6i4aHPOKoLu3jI36EGERTiFeDDUhjFynRLYg8gfaQc93gzuZGZEMjtlmiTUPMrKn 9O15Y5RIDFDQSRDngNbc60ieIJ1piqzHfL6X8BQxK+Pd/J/3SSfFuXGGfISYahGS 67SzHAECgYEA831stnQB0R6AP5UGuJDnknjgmkH9C4a0EpV3YFb8CKdLN7x3mU3p wLPk0bpjObW+PHmUNZWsrcnKtuw+5yVFefO+bZp8KdFptcFI2cgEsLm/PIoclZyK dBE0s2LlCqg/XNYImTYamsRD8RO0DYoT3yFK0/oQJUdyejBenOXjq7kCgYEAzQYL zKW0BP794eCo//fxmcjNTKrY5TVErWBQS8RI7uqdOsYSnlkBM/62pYnuGX3//Ol5 1fsuRbPBTNhdKMxAF+fjdGCgx6yxEcrt7d6aEp8hIDvix/YuXvYSe3HRSULsWboE yLxVNW7GaeXltcLa8chJzjv5QowPhFtMFnIeGWECgYASyc3WslLlkQXyYgx3t263 Ba7HGGpvNkC04mWowG2IOWh7b4aHBNURso/ogwpfDKAWSIdgLyF484Y54TblXSHk TCOYj4AfNfKDPNvjOiTqghq4kCuue7rPQ/ieEvZ7gQoKntVhBSS+ZoCbnJqbJNlt sUL14mjWh5HUzeCvGKoduQKBgQDAndSKEId/Rh05j/rWeJfoNgoC5GPfe1spU9o8 RI2MwKi05g/p6o7BzaTFAz/JfWH5t1P6oABKBeYuDvv+712r1/UOihWjYm/82l0M ZFz178CMaxbVbEkGeELG7RiFrrUkWypRePbIu2j3ZsCYMhfGnbgRZLD6P2H4pFlo oQ/8IQKBgGicAkmrhW1GLtEXpUaJDW03M59DQDkJzCJwp2ah/BCOuo0yNt3MywpM FPLay8ZGapGrg2vZKd713w540zrQYLNFueP8IobxHRHYvpQM+DTiNr8vHX5ISyH2 Rhc1ZZklKakTefKwTdySi212GFgdV7nCHXvurQL7hvlusWlie5ih -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/client-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmzCCAYMCAQAwVjELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCzAJBgNVBAMM AmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwwEwN7BltONfhh7m +xoHwANb3GT+PBsNdhdo09eZl/EjI40PALrXZ8RtzjLeZc4KcOfklVIMStJyHRoN HztEbHod1SmJexdp3lFsxE3y6N6ssFbLttXyYrscLUvIfq/6TP9P3awttKmyswxA 00OIcFbAjI8dAlMWaBYIhwUYciE+g6gXbfH8pUnwtKHLy1yN9h5e+UBIY60qmarx 2M+3E+fmM7i/0kOUDhLQhkGqxMYYKoEBLaQ+WRoW2pQ4a2QyP2cEZwNuyOeLNFQk 2GU+lzQEn9VFB+salfSE+pWNhkPazZMylY2huf4CQ3CjzvrAS7I84LFuKifBoizy 2LciGQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBABGKoAa56G5lDWbZEByBiKjJ Lf7E74z8CSFLWKd4ytbS/qBJJndMz4oRsVO2etu4XlmRuZTrt1gJ1QXbQHm5b7b2 Rx8CgE2ZGTfWIMc6B111z6Rl5ahZRIt7KEduC4+hsEHeim7eoQNkWRz6XU+fBWYA IAIjteH22Id0LnQuEXPcxvhBycZwcxFCaLKbimltgZskXkNpe1FCYbU3ri9ZTt8H 5MmQOPxyBr5aSWgmeJjLtrgGiH3hOkS494sYAxG+yrkKObDEa09xdP5+vS5Y8rDF 2WODi1xjIZEypAYoDk+UrI6JY6oaSEdZitw4sLew7+kopMXBZcEbsyVuGfA9Iag= -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/client.crt ================================================ -----BEGIN CERTIFICATE----- MIIETDCCAjQCFEFiPP89vh9xURoM0YYsuD6rFewiMA0GCSqGSIb3DQEBCwUAMEkx CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJORTELMAkGA1UECgwCTkUxDzANBgNVBAsM BlJvb3RDQTEPMA0GA1UEAwwGUm9vdENBMB4XDTI1MDQwODA2MTEzMFoXDTI2MDQw ODA2MTEzMFowfDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYD VQQHDAhTaGFuZ2hhaTEPMA0GA1UECgwGU3dvb2xlMRMwEQYDVQQDDApVbml0IFRl c3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5jb20wggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvyeKPL5krERLYq+L1YmU5DMfuBnZ3JKv9 flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD6229g9qlR12xW9clheX7N+3 wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYFxwUJHfJlhpY9yD/JTFZs ZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1KgGiyZ501qjpfKril6bkS Tx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgKzCh3X4pAqxAqSO8obxqS VWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv0cS4PhAvAgMBAAEwDQYJ KoZIhvcNAQELBQADggIBAHL+qmFwWY6Z8Y9A4btmP5nAqugyufJCAflQpPBSJ8Dq UunztrYA4sJZMMXDLCOr5ZhVT9zDCJgNELqgJChgVEKgj3TBsoEeOVTPxH84gUIX BcKMEZktNy/XudX3MlMWYL3avtjWeCcl91q2Vw4VgEzrnt1Rdq47XSHBt0jayF/I 1OLxt2HiN74GiNkBl8lyMfx0umk1qFecKda42vkF0nsWeF20giaj3zsze0BRqgmw zQ2IuC9oiiufbnrExM0cjqIpGky+vAplw7jPFUMJ6jadjq5XxC5ROGmHdcXjHq9/ mLZ2Cvb3m2LUgefyqq5T2tGBIJMK8OE7byGwsLlfZ8B50SCLKtaukvnbS9pMIS3H S459IoQS4ma24QHk0fBo0ond7cSXxGfrlmDKGEJxLp6hX5i2VpPT/RR2cjynM8XG DAyj4IzYbfQjDPgNGCXcfj1IydFreXzBM9ndf9eweJW32vbFmAwDV2FP6IaMkMv0 ycj6YyoSYSxs3tOur5JXAPTCzMIgGFhWTvHzuN0P7b0380aSzKyH8fmN5NqwcGUk Xg29NSGtrf0yX39jkCYyewGd4eA7NvoDooZgFAaY9U+myCHXttYDN2DC41qxC+9F bK+RfzkG6fR5YIVhYtItGVWgOGwuu03exfBu4tzkqKCCwlEVoYXJJdCMVYQPGhPV -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/client.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICwTCCAakCAQAwfDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREw DwYDVQQHDAhTaGFuZ2hhaTEPMA0GA1UECgwGU3dvb2xlMRMwEQYDVQQDDApVbml0 IFRlc3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5jb20wggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvyeKPL5krERLYq+L1YmU5DMfuBnZ3 JKv9flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD6229g9qlR12xW9clheX 7N+3wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYFxwUJHfJlhpY9yD/J TFZsZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1KgGiyZ501qjpfKril 6bkSTx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgKzCh3X4pAqxAqSO8o bxqSVWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv0cS4PhAvAgMBAAGg ADANBgkqhkiG9w0BAQsFAAOCAQEABvjAKmCbJLEUxsLB3z2Nn3qiWZ8G879/Z2sx hmbOnp2/h64LfXF+xneg6CN7aOaBEGujbuTAj3dIQQjhnO9xRS5I4cfylTcNz9MK zKeNV+Ukag4TnUpqpE6MiB3cosvanqpFeHVp9P98DDasxXYnSayIGUqWfJ9B8AFh SphMqDCn1mLD69DIi+Rk1C9Vj/+jb3ec3EuHpaP64/LV7fmvErxIfgSD2uLQtGeo 36CKgxlae7t+OSN+4PczdBzrTY4asiS7QNnoHmtI92EHGNlgy4nxCjNXTSk987Iq nCZLAUxPxw2MAqKRClQxnQ7BIQVaWCd+NpM46LQULJp4C+QT1w== -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/client.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvyeKPL5krERLY q+L1YmU5DMfuBnZ3JKv9flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD622 9g9qlR12xW9clheX7N+3wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYF xwUJHfJlhpY9yD/JTFZsZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1K gGiyZ501qjpfKril6bkSTx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgK zCh3X4pAqxAqSO8obxqSVWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv 0cS4PhAvAgMBAAECggEALT2dZ7Fdz5Ygzg+KpRFyMJkH+UvDgE05UE+NDgIYtj6C gfh/pzcMKwjRDAkMylqYZI88J1/XYmDzmbNA63K99vu/+cmM/+YP+4gQnyKxTbHO 0h4lTVlfz3sRHiwFMgPKrKtDGhyuCGbIoosCUkLZR+S31OWs+N8DHPmUxSoO/N5b ytaS6Kpu1nC+l7YPf6YPRXkvLZYxLAZl7Z2KOecgSCpHQ6OMlZR8PvCndSfVX3yx K4nRftjhim83S4DnWXG4y4ENuMCI+biXvsQrGiYKtJmESNxQ1EsBChI7t931uku9 QGMkpbYmExm0JVuF5P0B8uGTdQzS7WzUMs5hk5LsXQKBgQDOc1RHVWBP3cReD1y/ 9UiHSmPPTzW6rw5W/ctXDeTywSE261lAlenDdh6aptMguukp0DxH0ok6UT1i4O3S gswhY9A8ul0xxpciAjkcY986QA4SZZmuhH/W6JaGwVUu7w0kc0j+xN3mLMq6IyQy y0NE2OKZaaRAJ1JVh1KOUfWFOwKBgQDZ+qMNnsf7TeYqaRfLx+adZLfaqOcd1ifu zidLIVYvEZa8SLFXYdyFed+hPxrhkAeAUzg82plEXG3FvlHxBqSk8wv+jX2AYEkb UBZBahzFC6bhqwZNmcpoM/e0uDf0uXyVKS3n9Mtulf+EgFLKVFxwoT7vepVOXQSK l+SStJBhnQKBgF3MaG5AjBG9YIqb0Dgm6V0On2qBQpgfERTVGp3i4jKpLL4XD+YY Nq1n7V+2jBZC1cz5n2nCbjbg0lMqfuM2KijBgMsWoWEMKX56JBiSB8ofcEVLhBeW e77YzgtLq7g3RHFbL78REwCROUp4zOmHEHknUU0Cgm3jb7UMVqXy9uohAoGASCsl ROMnDtrxs2TsDfn3LnEkNe7p5gT7X6M9ezBkWl0WbOwcWvP5kO+SzM4Z1khbU286 FVvCltGLPocOliA+7qXReMvbm19FlRdk5u986vg6nkiDY7nhO7LI9jpvvESVLMVf f4aau9hJqtLZlkdr+5ZHQf5x5GoDkOdNNA04KDkCgYBUwPXCdHPdoVHeHtcfYVnx lOZMWbH8dRNsd/8o+lw6OIJqdrKk2hbjukBjrL5SMG6wucgHGm4UYRO5qpLLywSF l50yOIZ9bZJvmUazImJ4PAHt7GMzw4GSSBdQwbAxr6Bn6w7ySLmL6tuUJTPszp/g hTI3L3GGVTaQnQrntKsoyQ== -----END PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/client.pem ================================================ -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRFJwc1004slJg28oZEKEP8h9c 057iJOTHoNQadH6dZeJMIos3T22ZJOGJE0/x13UxOvNtgqoaPgsBhFalmcsndgGZ o/Ozhm+GUQsshc843xMHQk78qmUlAaHNYL0hnRImgOJlxPO2NF3DAQWrDq35ju+p 7s8mly0+8Q46wfkMOQIDAQAB -----END PUBLIC KEY----- ================================================ FILE: tests/include/ssl_certs/dhparams.pem ================================================ -----BEGIN DH PARAMETERS----- MIIBCAKCAQEApP/KXarP09GXAzdxdCHg8QAbVeGxU2hc0O2zE6900PI7Jd1VDOAm AZ3UEGfwv7WLxrRUJC31/EbkHH3jjnt28De9r3vNdpt30nYQfYHTNnIodi0j566C CkIY5J0uby/luU5+cX/NpHwA8YCw7wtvt279mACaE99tmfjY7/enDDoO5R+AUZwk neRUDszaTLPLotYcJm6LSaUvp+22fKazorGx1OiK1/jN1qL+sSwrYW64C4/n17GW kGYllrHBU6pgknCFhZdzc6SefMNKbVD79kQnDPXvERtOxOR+kv4rwzfmJ6NB5Ahf Wmz8RdZO5BhrIYqWVjMYi7VZmHtJkzA5FwIBAg== -----END DH PARAMETERS----- ================================================ FILE: tests/include/ssl_certs/mosquitto.org.crt ================================================ -----BEGIN CERTIFICATE----- MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH 3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV 6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC 6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf +pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/passwd.crt ================================================ -----BEGIN CERTIFICATE----- MIID/TCCAuWgAwIBAgIUI7h0MOJPvQH1ox33VVoLeul7iXowDQYJKoZIhvcNAQEL BQAwgY0xCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwI U2hhbmdoYWkxDzANBgNVBAoMBnN3b29sZTEPMA0GA1UECwwGc3dvb2xlMRMwEQYD VQQDDApjb3JlLXRlc3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5j b20wHhcNMjUwNTE4MTAwNDA5WhcNMzUwNTE2MTAwNDA5WjCBjTELMAkGA1UEBhMC Q04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTEPMA0GA1UE CgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxEzARBgNVBAMMCmNvcmUtdGVzdHMx ITAfBgkqhkiG9w0BCQEWEnNlcnZpY2VAc3dvb2xlLmNvbTCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBANbc0dtA8QGKlUcAVf/kwU62EWi0uemeoHUN3kS+ q/QXaRY6QsH8bKPnk+0wv7M8maVL7Fp6QpHQVfIe3Nt9D446zasLZeMr//MzS1Pw Zkq+pJFZNO60bcJeT1VV4Ey+zlORoTWqjZjo6eRHoe/FR20NLZnJLWtxlSeUM+cV U3TYC/1ovsdopM94YiRDl66TUC7yxNbD7iHZ0gZP6v0yPfcRgzME+nSeWWQEG4OR n5xJT5XX5tIAV4NqyUfdEqIFBPJxqhbxoJc77jRAd1USCsKF+GU7DN9J1h0ZsVZP rl+G3Ur3n38VwfNxlhatIfmS9DK556q6OUE2a2ih9on0VwMCAwEAAaNTMFEwHQYD VR0OBBYEFEy+sFb7FcfBaFXRR1/QBZQrnWmDMB8GA1UdIwQYMBaAFEy+sFb7FcfB aFXRR1/QBZQrnWmDMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB AKe6PqJbDU5vHN97qM3xbdPFEfSDqFIyuCrdk5XiqdPaqWn7FomQELfAkUVsW+gD m2LuYl9UDG2KSemr/TuualaP+2uVkUeBq8mMqDHV3iM/bGGabpXYvvdlIo/RDAhi 1K3Oh/TvLRv8YXmYC4kWXHEmYxaLjDFGqYCN1VgQjQ81+h0sT0liq9A+g8/heajF 5QQAVjp8QjiqooS3PwTRtXpg8KrccAJwLJXjAMzBa4apd2aJ8NjHuxUhdW4orUAr V8hr/l482OtuKpakcdDbzLqn1SKh5Oq+cwqnDEr5JYCVb2jM5furKHBq0bfFRu5i 5HxGsJON3fpgyq76dkRN7G8= -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/passwd_key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,97585DACA30B6F5D WPt38qe6lbdheiiW2uHV1G9l9A2Yr3GugE5iEaZTNQS9BjsZP6rpJRj6JdKwqkSr 0Or4B5g+AsTMw1LQES+ap5l27RC4vBcjiSb3NqXoZvvp2qhfUcfjxrwILULTC+Zj sy6g6lD44cwbe1gs42nZrMTKdOUDgxwnH/1f4Nnz3kt7z2Sj/jnPGGr8HbWIazzx aE1KMrBAwfHXyeg8FpMRJZnmK9Dim53X6qjEEi9qrAa2KFX/NU5mr8hpFVw6n4WV 2HMvCr8qoHJuR9lrGaLFEwJyq5WT143L1pLBPV6FI+DppFM8nh6Ze1UqNW4kAH1N xCDl542Hv0XoTl26Yl/YnlBJneSfqm4KpI8RioYfekp5ygEJv9JXFJ7WRfPCJxAz N9kLS5ggEF4etcDr9jtIwMwvkGylL8Bg7BmkPPIwyelv2HudWQaV7NHqxotQSuIm TiLAeEfIPPMdAwuVRR5yHy8tPSwwwqiaf4GrY0hHfv6wMS8Auq0pqIpKToQKMB3J /FdLqbyEWS2PpwPWoHVXCr94wEGS2eUdTW4KxxFrCBoizZC7tS1NiznOJmmKMSme 2ACEy7uVNFecT1d//5MfVW7hwEZIxnF0w3WKv20uOuREwBFh8TtQ/DPZr6hoxUBz 8rizJrqnkQa1WoC2m0zM1ag5BU/Y6kNFiWbEh0O9ShRNlO3mZeB1KwfFooTpZqX9 sldXrhg2Tikus9jRUM/XnnNb5d/iL/CYJIkS7lO1NItDlNUe09OjrWNqWm2oJPKB gAiaBUz3v/eSRviX1YdWrGxFGTZFB6HVnGhBpiWOYD7eRu/jVCAlZuK8XdF+dO66 4eSD/51GDoRtgBz3fR7UWuXfmQCIbnjgTYOVrK25gZdbl9US4bBK3MC66wsG1K5U U8i/Ko+h9YwNJPnHDdiOT9S7l9QczStg10pnmj8jASZzmrD/Pm2FU6mqq0EXV4pF Q+3uSuRo74G9vkQBZNjyBDrDO6YwkNyHULL4+hN6QGcDZD1pLDNN2GnLasJi5ccb E6ptfIU8YrkV9pXdEALf64lE3AshNhREHrUSFBpNia7bDxjEePtMX/J0JTo/Z0b/ DwKSFGMMPbDq3JkHcCzfrKArmjFoIja15A2Q0eDk6xRM++DZeBIduZu9j3oj7lzd a/f24ODzw25SEOj6mxnfx5MxwNgNTnV4SkFy9R5EMkBIpMYuZ9P/nWfo4c7vhOQc 2CrFi4uQxIz7pka0SJUUMuDFlVbHXDLQwYwf/NUwjwl2iyZaykPuk+xBhZKicGkk lFz59jgOPSPEp4FPsmvdEScrJXNa5y6Prmxl2RqY6cA7CJW6iAJvh3XLDuaWDEA7 yQrtJB0R8MQw8tvpBCKl7CuDWOM3x4nMMjTM1ZgFMZdAJQbqqPPyfsuBbJ0JMGSh xCRlkrLuHj2zWKjKtFlVbUh7oCHz0aKQAsa2dBBAb8YliA+Bh+JMxXRPfbNC3W0E xmOHSIMoLfSt/jkxNZpaQGR/Hccu7AJ+DlPpOoJtevpps3BfFBlMiZTCrLkXFYwa bRE3IMBgNanbwivLCkkWP4MQEAy316G7aruJCx24ebidSGdu/ThNBQ== -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/server-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDJzCCAg8CCQCRX5mTEHplVjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD TjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQ8wDQYDVQQKDAZzd29vbGUxDzAN BgNVBAsMBnN3b29sZTELMAkGA1UEAwwCQ0EwHhcNMTgxMjA3MDc1NjUxWhcNMjgx MjA0MDc1NjUxWjBVMQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxCzAJBgNVBAcM AkJKMQ8wDQYDVQQKDAZzd29vbGUxDzANBgNVBAsMBnN3b29sZTEKMAgGA1UEAwwB KjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANQSsIppKx1KEIZSyPlq VNA8NQJ04xsV0fUlQzehwAXTpM2Km9AMXKfLw3b4Z2dIE+O4HTc+2EpIo+oMdEeA Tn1atAAqO5XDfrYfmnRdDCcZg7wxqdCJHh6OHNXv2K58OnmoyZBh5gPoR9LQuIUF C4p9TyE+qUxSwrzast2XkoX5JH+p6uKE/a7eDUMu8Dlx6E1Zs9aTNyyTBO3onK0N iCZrguuOJDUnjSpSS4P8U74Wf4k7brl70QWS6KAXWC+12VSIKivJA8SZDIeQqgO+ ji5K1adQIxQR3g17QHOq8ZmrzwUWE+XdDCwTv7dkgcnt8yR4R3+fc9tMugqSS53/ lDMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAyQZRigqe+J/jGj1b08jAci4Dfu6k 4v52Hb0wvHvXi/C8ccL+g9f/9sLVKP5gdYsZVSf38wPNh9xTNBzbgKwUkXO37yHY 8vXSt/coBkhvXAjQT4ICcKdH683mfD5Vuyq7m8QP6EbqvCo0s0of1I97j0VV3xf/ SiSot3F++5jqaReLiDU4yjhSiACuZzyMBmqZml3jGEUg4s8+ruoj9t2PoeQv9fi8 LHC+GnICEKCgg55SoXGKtsBdM1sewIdy5KHvMet9v00PkVBFFNDSt9wDugttjPCZ RbonE46/2/+qpGG+nVU3jIfUjAoHvXy30w1dBwTAemUC0yeN+xjl3OHMjA== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/server-key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEA1BKwimkrHUoQhlLI+WpU0Dw1AnTjGxXR9SVDN6HABdOkzYqb 0Axcp8vDdvhnZ0gT47gdNz7YSkij6gx0R4BOfVq0ACo7lcN+th+adF0MJxmDvDGp 0IkeHo4c1e/Yrnw6eajJkGHmA+hH0tC4hQULin1PIT6pTFLCvNqy3ZeShfkkf6nq 4oT9rt4NQy7wOXHoTVmz1pM3LJME7eicrQ2IJmuC644kNSeNKlJLg/xTvhZ/iTtu uXvRBZLooBdYL7XZVIgqK8kDxJkMh5CqA76OLkrVp1AjFBHeDXtAc6rxmavPBRYT 5d0MLBO/t2SBye3zJHhHf59z20y6CpJLnf+UMwIDAQABAoIBABsaqGWIO3Jq3OHO AtGdvWk/yKafucbkawkh0g1yJtCX1CBQ7skQS2dGq5yw8sZIeAJKrbE53vrffdOT 8+iPhXiP9yFuRF9HtRIhx+PHYJ4+rI+I6WM1oxvUnerZ1J+4UODrqZdCa9tj/In9 ExgtJOR2T364O3r9uNXaPbIEhT2J9m6+3/7+hk1Cux7FHYLQmPnSSnxNmtuugwx5 aA4Wbytf3zFWG+1sDZQI4fgx80IBQCOVZPw1j8WzByQJko5Y0hYeEpVhXhU2AT/O b0Jo8w+vP/Z9N6UOLH3TCuLNKckeG5Bem/yh3NtldJztnt+rOPO4kL33d6GONbfl LAKTuoECgYEA9xlpIna96ZT9Cot2vgeLUIK/xyMU9WmPNRavx3akz50AxoU3ep4p Fwv1TarXn3pfaeAV34qzM4vxkPGQ7QF8j/qLrvjpXWQwfwiRqrC1T3S8J8FZegGc 11ZzTURICniOtgNn5Ai/RM3y2tR+rOC2gTUpB7UUAW1cOGxZ/xNMPksCgYEA27ZK HNdoDClAW22gWRLn8bJLjOWdVcWpoI8zULRgfcyvm9ffbRaguRx0R+a3TbHZvT7c bxppYTn4+RXuIhM5hI4PxqVtW9hWQvzta9wy44EBXUebwq0NjVEV3X1iB3T7s5UN aXf0p+jwPJeFYRjU46nSqHxLtCu+Z4N67itZsLkCgYBGmLf05L/1kF+LN7VBLZ6b 7f8kwd0nEHWd0VF/R2Jm/pBOJ5lmKvHKZ9w5yGm6YpgIA8G8EECKpZzqsFmbnSUd bZOi9fKWgB1q7ePQRJRIky1+njsWJIfO7iAbHzsF4tPDJM311Jnw6nZMFxHqCnma CLokPZPtpHUImcxMmBLcaQKBgCDCsZJTXCRUElCPrBUcCJG1cCZ2JlufuYdDUvd5 rdmUJ6spKRtvAFAZugzuje570mexc9TtL0FcD4+0gWl7TFxDgn6wM/o568LZS92F W+EoXze1cbg5bYfgy03XYEKUYuOIGhPb2xMeXJPsU+sCdNvteoe84NzgmOd+Z1oU BK+hAoGAXiQPmXfdk/hbxdiUwlClm9OGNH6Wd1jXiFRUcGLN43CYi1rRe0ziX+I0 2TwxHu8RcUy4RLiUlOnB1aDyQu1sUHJd+rnDqSUph5ozsSUuUBuJJxm4g7dC/9kZ Abs7BkHltPaTAGKW9RV07XydJPLEfbDJ0WGzkxeADMRzeOlHkhQ= -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/server-req.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICmjCCAYICAQAwVTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQH DAJCSjEPMA0GA1UECgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxCjAIBgNVBAMM ASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUErCKaSsdShCGUsj5 alTQPDUCdOMbFdH1JUM3ocAF06TNipvQDFyny8N2+GdnSBPjuB03PthKSKPqDHRH gE59WrQAKjuVw362H5p0XQwnGYO8ManQiR4ejhzV79iufDp5qMmQYeYD6EfS0LiF BQuKfU8hPqlMUsK82rLdl5KF+SR/qerihP2u3g1DLvA5cehNWbPWkzcskwTt6Jyt DYgma4LrjiQ1J40qUkuD/FO+Fn+JO265e9EFkuigF1gvtdlUiCoryQPEmQyHkKoD vo4uStWnUCMUEd4Ne0BzqvGZq88FFhPl3QwsE7+3ZIHJ7fMkeEd/n3PbTLoKkkud /5QzAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAU1IPD//ftlyUZAoHrYBY0fkK ROOB/3ALET2+3QYkQJKnDFSucrvYRU8nHuGVe2aE+ej1QZ5yOzicwRoOuixkD98V cg+6ngol96SHXoV4j2EkXYv0w9bTVlUdTUH8/SDfnNLvzzZdRNiw9fOlt15DWgfY MtJi5ReH555lLQ6JU+NNzyP+zAExx9uUdDd1H8DyIPjip5xexxKbXGSgPoDnGGVS zJ7SkzmlGcHR3XYqQglD6poqnvxhDm4O9L6cK74RIJzUr+lDOVWhVND+6zu8vrwW OqM2V2qN8MNu18umKfqXCgcWYFpwhc0fJktPXi8bE2knU9vbOLJHV5w0XET4AQ== -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/server.crt ================================================ -----BEGIN CERTIFICATE----- MIIFtzCCA5+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJDTjEL MAkGA1UECAwCTkUxCzAJBgNVBAoMAk5FMQ8wDQYDVQQLDAZSb290Q0ExDzANBgNV BAMMBlJvb3RDQTAgFw0yMDAxMTYwMzA2MzZaGA8yMTE5MTIyMzAzMDYzNlowSDEL MAkGA1UEBhMCQ04xCzAJBgNVBAgMAk5FMQswCQYDVQQKDAJORTELMAkGA1UECwwC TkUxEjAQBgNVBAMMCTEyNy4wLjAuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAKzZ4sOURW7+OVA+3UhpPEElSfAbnre4V7iCt/P79za1Fgp5gUmm+zW5 Ji+GzscNxFsVTEV6IL3V8nZd5EVjvgdCoN4GP4uu5r1aroRq1pv18+bwmKCkd/KY Q7KyYrCuPBtu79kItaqgzrKFz+xykH0Gcpcg8xnA5fsv1Jw1Gsu41XXzeSJmOkLK oRVZ9aXV2X/8YxFQmtqwTp3wPthZFuZ3yVGLoXL0f8a8ipoxDR4IicEceSfLrpjm nTIqvQ+ZPKpjN2OgWPOFgiRdSnLKlCY4r1KDzPzEiJPNq2t2r8aG9M5JVi8CjvFA L6lNUWPeArcYkwveYEKD2+M0fPngO4UpksGOkHxxPP4NYP7qzCB9UHoenEyA0kwC wB09cXhfxspLHItnVdTOQwaP0hzfm9EAZnkxohIOczWPoswTj95BwJv/dUDu6GgI y+SMnVJICSz4HBohuEJQKued4Hn3b3zjOJjMjI2228u3uawLFybyIQwFCchr1IDL 3yg5nVxefqRBCoh2+zoDcrJGsZTaZRl3KdirF95vAeCnV+CLp22zK3gU0IH6rG3H BBmloZySTPEF+TLqURKMhKicJmK1lSTOw7e3RSbFnolNVPPGFhP0XeEjCLc7bD8I Otaporulet0tPoe/7/rj6Purey+6chv68qwSuhIWYDoovzqRbUrpAgMBAAGjgagw gaUwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg Q2VydGlmaWNhdGUwHQYDVR0OBBYEFG22cJXiXTp6pXrRuz8QX0AHgdvGMB8GA1Ud IwQYMBaAFEjZMlEqMbJRRI2/ULRMRKlXmC7AMAsGA1UdDwQEAwIEMDAdBgNVHSUE FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggIBABTXjyCW kX/zqjIQci8ryrmtQOEMa3Z5k3gPeyCHn60AK/QDLhSpLPBkpoKT9T2VU7Si7HPa 4yhfJ3081wQsMcJIZ4ks40OYsQb17rYmzoUsJzsRfZ4vdgmQx2K3QQY6vkNFh01D TrWOfQeB+UnuzRgjLZP4l+7YCPx5/LcK/mfyUhFD6/AxSFi1qhGpQgs470+8kurd bCvTpzWaBODFlQIJ0pi5dG8MkLVdYA7iAWdqH5s6NGfsPdTz8EmuYv5inSZlTWw7 +FLz1wGbXuvaeOxulvX5PIWzy1cWRmlio6y7rt1Kx1nhY3NuhM5w9I1Jx1aV7DY+ Pisziy5QsGrmlRfCF8Ks5vQSUCfLhwFLcKZ/44oKFx9ufmIorW/En5/FYX4OF5hQ TloB9C7bQnu+vFOWlxH3T1P1qb6B+n4RBk1rafkECj1VP85nbCj6yXlYGM9As1QT qf4tZHNJ7pGb8WWTJrUtvDYtsXtZWbktYzUUMbzdM01IXNqRuI7AEVY9P0VkGhKb brT1UlshqMIR2PjXqRdm7WvfOZxLIA1EyOvTGRna9u4nZUc9iNsMTheys9AXoMzN u3aCPQJEYgQZJIkZaEAldIG3C3zOF0WGcj2kPObLzwd51xxOJGZVQoOJHyz8y4PD sWH/lnjRiy92v788PK5wttGg9lFZ8GI2y1GH -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/server.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIBmzCCAQQCAQAwWzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw DwYDVQQHEwhIYW5nemhvdTESMBAGA1UEChMJTXkgU2VydmVyMRIwEAYDVQQDEwls b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKg4oNy7liSNUU1T gZQEEKPJ1znNgbCzbEJ/QlsKhLMzIjiv+xaTvYFffUcZw++NwsCwQUYsdAsmDwQT 4wdTr6JBKwYuKRnyL/l5N/h4VEmNh2MGz2NSqo66QNOiJMOYhmuxmcr08WXPr6Hp A+KtpQgNt2NFB0nSbb/EvyJgSx85AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQA6 hlKHAfMWwwyqdqsMPd+Q4LetmER+ARnMZTOMSMp1iFXEdTIZwA+WHfxdN+KJ4gnp 3QWpoG+q4O2tyMZQDB9wSAY7LbFyHjzpqq1JJXL2Qpa71NqE0JRZEToAxuyrGf0n NCpZp0vHFERhgntLkNSM9sMFCWuB6dB1YfeCkTWCeA== -----END CERTIFICATE REQUEST----- ================================================ FILE: tests/include/ssl_certs/server.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJJwIBAAKCAgEArNniw5RFbv45UD7dSGk8QSVJ8Buet7hXuIK38/v3NrUWCnmB Sab7NbkmL4bOxw3EWxVMRXogvdXydl3kRWO+B0Kg3gY/i67mvVquhGrWm/Xz5vCY oKR38phDsrJisK48G27v2Qi1qqDOsoXP7HKQfQZylyDzGcDl+y/UnDUay7jVdfN5 ImY6QsqhFVn1pdXZf/xjEVCa2rBOnfA+2FkW5nfJUYuhcvR/xryKmjENHgiJwRx5 J8uumOadMiq9D5k8qmM3Y6BY84WCJF1KcsqUJjivUoPM/MSIk82ra3avxob0zklW LwKO8UAvqU1RY94CtxiTC95gQoPb4zR8+eA7hSmSwY6QfHE8/g1g/urMIH1Qeh6c TIDSTALAHT1xeF/Gyksci2dV1M5DBo/SHN+b0QBmeTGiEg5zNY+izBOP3kHAm/91 QO7oaAjL5IydUkgJLPgcGiG4QlAq553gefdvfOM4mMyMjbbby7e5rAsXJvIhDAUJ yGvUgMvfKDmdXF5+pEEKiHb7OgNyskaxlNplGXcp2KsX3m8B4KdX4IunbbMreBTQ gfqsbccEGaWhnJJM8QX5MupREoyEqJwmYrWVJM7Dt7dFJsWeiU1U88YWE/Rd4SMI tztsPwg61qmiu6V63S0+h7/v+uPo+6t7L7pyG/ryrBK6EhZgOii/OpFtSukCAwEA AQKCAgAuGaKQNRBbrPQOQeX0tRR7730Ly+IF7+V9cWesa+wgLCXnv0ZLJXzkdDAK BvNJRZO0EzcXZJ4s403aYepK03JQL2YGMTZrg8dIg1H+QRxh1jQpyMt8NMFE83VC yK7nnmCrlGflw5voGSFnnaa50khVAA453nDX0onD95rF2qf945sS7U/Ga+tn+vEB Z8/LXgC3UNH/QTc70j/1gajRPNmNuC76T7pMlYcqswhoXWK5Ot+6oGRzx7AWWZNd WOa5rU3vx6xzJsXylQ4DstnzSbI/i2i/IiXe2G/ws67fZ3U0BrRt/EM/IHiNQWg0 yElH0GN6/9PgG3wi4VYY/9vBLRvSVLJEjKOiWnI07/vBfA816xPSog4RIiUHnepT Moqy2cdqyTyJA3kzohdhhr5ejYN67wxg0U5sriqIxDcnIBafHcsuQK22vpkPzkzC QsgwYTJzKjp6uC9UR7/+gNOsZxo894uU3rtuCTYVcEeELbnGguyk7M1uXdNRMQxT +5EMa/7RmLQAAPUd2Zl+SJv7ofD7gzJ4uwKuajVjYBPStnHRlDTG3aTxLToO/2it 853MrgSxIwmEvXIFGTuGMXMrh7p+BYEwmReQb6zrihOM4cRqyUJBoU8t6b85ZIa4 XbpG3upCQIAyDWsP9go/QMRpXJim+z4BfJBw78XhXJd/6Tb+MQKCAQEA4NgoNqf0 nMQiMLJBVzIaNzwm/pThemJ9uvXbeJfEg7SZ0QABAEAbfvG7UIOcKpyOnp0UHDKa x2YdjzXRxnFdcr9NwfSDnSdcgeIz/AbleANtkt+tCuNP2mWU53cYg4uTpjboy2BC IrknH1uuJlJC7YtnoJptStqde3mw4P2AUCSpIz5yFkNQt1PzvHb7RjCZ/BFQDb00 CJRn8P0Ak6m+K+SVW6aJTz6u0/Tw12rz1C+Vmp2o1EAQweQrqC+ia4M4QC3FYzxJ bUhCW03ky7bzkL9n+nVhUO7mNUAI3vOuUmBUHEtT3hfq/2iGR84jUOcTyZgNF+rO mMuiaBxnQdXidQKCAQEAxM1i62+vclWwFPjjGkrGR8+59R7C4mAjTVeAsNSk1yGh /V2UnGTIPp7o0se9PcMo3FORjdZ8s95un2VqU3qymXRV9fi6rbDXKWdQ7qN47xbk mfPmMD3/QnYZL2Lf2itpnW3FHqa9i6sIcoHtPMdKCGAoGTyIB7dV6jAcHewXzAt7 yjAyt/43NFCo+rUYxGWDEQdXEweo4ObOai1uU0mJpzNF/Rw4pDvm2nVa3JmELIML BBe3YyNSRLVDxlm3JtEAWA4WYgiyHus9bOwNN0BRTCBBIPKbBH/Kc92wXG7OJynP FAxmjIOMWqJyrfsUze111dwVEDGtrAarbhPmpeNQJQKCAQAQpzHjSnFNoyE9Jha8 1JMogCYLOQ+yNtVFIayNs//0GU5DMpLgZ98hedn7zqOkbFAL42p35C+ekSujTcMp xX+8XfruYKPW4fFyFUTwZ7pbNzXIaRSx98AUVu2+bw27wHzBFNQNLCX52GjTY8D0 Sad3XJniRU3O+34qhQravyvfw7dbSua8S2QREAC/driJrFPbXZTmT5so/4TrTnHa ffn92kJNtXn6CpR8Umn8KonAr32PJBo7DjuvgKzTGiQXfnKOHlW+UdrGY1G23YAS row/rk2POV5p1ifqzcFacgX5R/Uy20ls66yWGjHH47at6U+H4OEnYlbG7l4rA9+J +sN5AoIBAH15VXA2LO4Ao9s7M9A3jGOMlUsq6aXbFabh+zLC4LYow1b+qxlrSCBk wJxirM0L+KlAvrRn4+nqDy2hQ0EmIWilO4kvpBXJgeTr8jW0FA6b0fQ2BLUNUgnV sgj8SSyYMSYmLl90f7TkvuYC4Ha3uQPOCsLMTQ5eXXCKa+LH/AQj7jDiufOFKT05 kAMII1pUPlRFEccYQxvhDAm2HXkMka8Aicbuj05mwrDV0bCEUDjw870QQeP7SSB1 UVjNKnCdIFMD6FFoqef8o5UmiuaHI04d4mhXRW6A61skJKVKIDXn+hcWMOu1k27V 07gJNSJ2G4LWZreEdO4Q8oukNJHfc+ECggEAZDW7RFC05XXvrErpztnfFDwT3cTt hdU0wzkTDv63i/U8tktrUKFrr51/9s6YR+Jk0H3k0QshkkZcGrZZxGVo5Wxi8BhL AkAIwOKSgqXjy7VEibb3+3DfjS4nYXTeJxN1uTStJwk7c03sJVHIg8cZ+30fIiPr 0see5Lw7VGj4vYG0fSt0iEyjrl16Y5otMK9s0H1yh2hrRzfI2t4DltIIfAi25h4w EzO10q57QfPhjkw8J846xmTO9X4clolTMDM0qkN3Zhf5d6kwexPi0/4QUhSZjYGg fq0q62fl8tiVpZSNXYcU4tr+PYByWdCeH8ObmnLD1GD0yskjx07LJsbuEg== -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/server.pem ================================================ -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoOKDcu5YkjVFNU4GUBBCjydc5 zYGws2xCf0JbCoSzMyI4r/sWk72BX31HGcPvjcLAsEFGLHQLJg8EE+MHU6+iQSsG LikZ8i/5eTf4eFRJjYdjBs9jUqqOukDToiTDmIZrsZnK9PFlz6+h6QPiraUIDbdj RQdJ0m2/xL8iYEsfOQIDAQAB -----END PUBLIC KEY----- ================================================ FILE: tests/include/ssl_certs/sni_server_ca.pem ================================================ -----BEGIN CERTIFICATE----- MIIFejCCA2KgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwQzELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAMMB3Bo cC5uZXQwHhcNMTcxMTIyMTQyODIzWhcNMjcxMTIwMTQyODIzWjBVMQswCQYDVQQG EwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwHUEhQLm5ldDEQMA4GA1UE CwwHb3BlbnNzbDEQMA4GA1UEAwwHcGhwLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBAMeENPus84HfIaypUVybbn5ZZyu1gXSYZAnDYYHhOlHu5kFh 26ohnHnoheapXVRbUtDZgUW5BztA+vDhKkM5G3U4p+YsqcSA/IkF2JQBkJH1jrxs XMDtXIA4/MzYAVtyySqoMsrbkSCDzvI8imsbZI9O1x02VgaqoaQ9JZvglt1nhTIY YpTbTKGCzMmrnFGHITeBsVose1ACFIAtqKqvUaA6TxcCtVcrENY3T3N/vEmoHHBg O7j9+UvYN/WRPNcv0aIOlro83O9xQuJdRCygPmcv/jkqWRKm2Pu0lEkvbMCvOJr/ Fz/kQg/6EVmIMsR/dgOMIjWjmpeTx24UJaNHUdNCeUbxIyTIyl9ISLWLM0Ipoq3L 95ygH6FSESAOpc8rVE2A5YZCtKlwvogMx3C/tMH1gWwMlfxn0CopP4Jx6lngw3yn IpP0hnuCauQikBkyhsGu4J0Lbx5BsyDWQLwjiq4s8EcEy0vTdAvhDMRUGvpnjWaa zBDVVrzHaRFvp6ueh35xf+dYieGL5UTICyrrQX9wwSafHV90a87y6XU+RwA25Dmc V/kTn8/xkFv/4CbZp+0yArKpwXjqBMsQFbkFjT+Ky9MN+nedoFUMl7slgjpBs6RI T08Hoa7/kMZGtLCiGcRXlwiIhB00gDz2avUvFTxwik9ssjDqBJGzNbBvZGb/AgMB AAGjZjBkMB0GA1UdDgQWBBTjyuOBGnHnb+x20diIHG3sPuJFGjAfBgNVHSMEGDAW gBQ0s99BNsV4Dszph6PArogA8HP4TDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1Ud DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAFrQMmCoSe8uJbJLqPGMPW4ZE 0U81WWboc9FrSfezBYkTKuCjMTjQFv8oLp4pnuWZw22mHrOiz9izjNnXcR/rdVbG Cip/JSHlTzHd3gnxSSd2diXvVrSb4zuLyN6WIficlsj3QdIP4Cdfv1pdHBTsRPbW vuHpwYqqjxPBjqvoVZyZRyRM1pqpxLCvxOt4S3z4uZXyIH2UI7yWxI6MvdHBZg5m TwiVi1gEQuRnYRy4sK9qJlBo5jERvzDr46W1e9Yz5sNMPfwWb64Hkkys3GqJt5am OKxvgF3lvIK90vBjenGDfYEENP+0qdmdpHQkxGM2cl9iPqW8iyRGBVjdbzTIeRgB u+CEbffSKWCpbm1Zcszv3FHMlsU4Px2pUTdLwEMgBQUPW/ngBMdRgVZE01GI7D1k AM3hO347eehQTWoyuHgsnB2uaANEeR/XFBGDFmbs5ggAzxxRsH1FxoHXrv3iDIPW 9lKnSow+OZGcPKgaY5Nsregt4jO+pQqhzxlMa2QMfrMoGkEvrKNcDqeEI6KQa1SI Zbq7sjf2w+VL0oQcse+gtg4q771dKy9V/XcfnPbiBAXfQqGj/v0LmZRn3ZRrkgzl CaZocqVRJZbqu7/iu/p9pDRMR03vhBWPn7iM8VqzkhKjAlD0rolnQqvOAXyPpd/O qYuYP+6Ymt/rUWXGVh0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFbDCCA1SgAwIBAgIJAJcrLHeFQrGEMA0GCSqGSIb3DQEBCwUAMEMxCzAJBgNV BAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMRAwDgYDVQQKDAdQSFAubmV0MRAwDgYD VQQDDAdwaHAubmV0MB4XDTE3MTExNTE5MTEwMVoXDTM4MDUyOTE5MTEwMVowQzEL MAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQx EDAOBgNVBAMMB3BocC5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQC4bIXbh+e+OCbLum2FYB8i6ZnOShp1teWNEE/8A4ot20wkPxLeXFSU1Egn+3BG fxHKrWxV/FOiL7bims/SfwptFkgNZkwE52HVk49zgQevG38smsnIGuBMXMKebLPT wlsIWRqCmZqEmh1wNcC9+qwoHoYjvJvMYfFvofiSc7qkTFsjoBVe+ho90vjYMznr Bs9DlD88iXO0kKN0Bsu2igz68JWkUsoNeD6UKSir16SmQZxdFPCM0O0gxmOyz4NZ w8pJ5XGDJyS82fVp13tYCOBFhK0QBvvzaz/gN6De5arefm6cer9TX0HE+VvGhx4T 2lqydFLfAVKTTNoZHOQk2mFVUm7dlMzLvsgNKdEZH6wAa12eBImCea+CyJNkbGLC nrN6a93iGqTft75sbDyvzRk/BdnxadkAPlOxgK1qGrOgeS0+7chPMh24MRoKj5bU lYGdcp7Y9qIW1hZXFfgPJO0yMOug/7J14jybp+YhymBIBTZYi1zbGqGNXdLPyp77 9KTfyJE9KkbGegBvwqJmZTW8QoUDllopDjr1zaIqHWJqfAK7vGivu0iLVJHBRbRs w9c48RCjfIh7AkvFKsdIDt9SMdZOdm9D8SXosYPM1zMFQsFFRPh/nKyl9QnhWbwd Vs1tIFDrb10Z2sn0pF/V0F0gubR5rvYs+3h/dLciLVFobQIDAQABo2MwYTAdBgNV HQ4EFgQUNLPfQTbFeA7M6YejwK6IAPBz+EwwHwYDVR0jBBgwFoAUNLPfQTbFeA7M 6YejwK6IAPBz+EwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJ KoZIhvcNAQELBQADggIBAIkctDRCNcNVs2qP/O3zZsVZ1cqlqYNjcHIVHqNZRvVb bPtInZ/i/6BbwA5QM/giKpl2aMwetUnezjHVD+7/m24Mfs1FWyPD3joCMIANj7hR MNbmX1LQBMt/bD3E5trq9S9rZ++/ny4gn/z2jH3x+lzDMBDiam/wXbvSmyIUPUX6 V8FFpkTBNERpurUvX1NEFLuiilWKrI7/u1qS11PGBcX3pNQNfhKNMtuFz74hAE7C RE8756CGu2bZzydYCarUXSNTXqNm5ly2J0UN6Al4myFwj/6XnLnDhXDAI8CaqDMD MwmxUzJvzuphNS085ZIntrSgPJESGozevEsf9bGD8GyoBQN/cSqOJCFfnJqxtKHb wNa2F78SXYsiWkRb5EzoyCeon+0tygG6gLBouVY8ouCerExkH9qzRLLzFInxmCm2 ill7c8Xmml4H2BxJE33dz/Qh7PM3eF2A2VriioLDj4+HOiWq/1BjdW6/Xiwx5iP2 nC6Cex+lBt6HkP9WIiJbGxVeamF5jxNWWvLoUkrjFhpukjb5BPR9zFt7mAty9J/T iQt26yylP4ucmL9KB6lv4ISW4Ri8mhMcYh6nF3tIYWZMCSQKqkyvymhKdLMZa20A IME2oxnfZcmsfMpM6BYsN4XDfGBeYctqqt2AFRNLkI4wFkFFdPTMcTi1q0iRH2Xk -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_cs.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAvy5NhzktzEdsHTbGB6vqYANms5rn1zXFmTJrGlWCwoIsNmTf ahvZkrC1cCXTZ7fbPB8XQbpAtz2ZSU7OcwBW9B8okYUPo9zi/ptwcrgsQsN0hrcD 8MBRUccevwime5fLvg8E9RJ/68y9y3BnRcVWYO2sAK9juTfidNjETU3Bb05oXv8D SD/6onXQu4uXDgsQ3cRXeld9UB0xazmQXyyiIqXc/cpTAnaEVYzn28aj7NlUbzNq 511UXMXY44x9EcXWpPVZ7heNcJNzY5DCNzmtXKrt9yiMpWQcPXEzsESVxAMqib9u TFOlvVX17LIPxBG656PjTD9J1h6kBbMCUxzs7wIDAQABAoIBAQC85lBeY0X4ST3v I7bJz7kWQ2YP4uhfAdeLhoDDFWjNLffniwYhfwEc6xNri0R2f/jUT9gX7qORKwEx qPdeNCC2t67LElGg1FlJv2Z9Q7MgCKYzkdQH5s6y4e9kTHTLO/JpiceZKz1QTQ3f XOH9032E6nIAf0wmr6xHTgOwajrN8VI5BuPEMVmEwIw3AtYeqVuPCNKyGR4HUVkC 2bAydnGngbRJRnNzmKcWJancxpHDGBSFqPyuXMFC7Jgo3ZmyCbGp99vuXVk/sW9x 5aj94M9nRE0guk05ivH2/JZao2uLYkIgjFWlhNxKdWgWRk8DEuN4djC8mKS9YH1q crYRToMhAoGBAOspUTtKP54mpZmyhxuDqj02JaJRzNTskPHsiF1UhtXuw7uT+ryV ekUFLNXoFmn9mbx1WVaUvGH4qjilvQOxz7u++lz0ApqJEfyM3jc/cC40Y5zcuGSu Etbg+SyDoytlgMCIydJyrS7NNALSo5p5oG6XY2f8yd/DCAmo8LzypaHRAoGBANAf R1SlBMc/bOsi6GrJxcBVSCFMiKYiO5woL5aUKa9yM+UQuQ/6xbQ7Q+sOlt0FH3xo AJ2L60qTdjyXVtjOdtXs5ZC4l+C6AfnCx6yLr+fNc4SOYXEfqS4LZylgwKd9KyVB asspIW9Idbgebmi6vPyt9LDkIp0h1VuFGjkvQJK/AoGBAI4pbS0dprXyARyYW6sb fpgAmuG099IkrT9DUfCx/81myTclr2fAKal+BmvOIXaz0/OlMXvw8K19iVIzh7+r B70lJ+93p/dKM/BsLI5TsHqOO0YB/QsIXOVAHgJ2FfdPJnW+e9vYba+kZ/Po6PSi 4ITaykJ8BIJcQgis89QWEGFxAoGBAJhQO+jzuDKF9ZWEf6ofrw0anOZZ16wWY5/e PS2rk3JmVxpuibHrKqPDt+ogTELHDAsFJmYmz3VNxHuFmrajK49Wh4/JuMVr/CQo 6+8YcA1qa/94IFIlBLDBAafjujsZvOjQHnM+z8xcsGKmStF00Pjv6qNG4xoyd646 FD4DmfOLAoGAWXehpopZKXE9gRAni881ucK6WqxPPBoofbozi09D0MmfarIVaSkv jNVVHBfLWd7IEXTjiipPBeUqq6Jc3pscN1Vp4rrl8jTmVTdazEv0LuzpdUFqmNo2 M+xw17uz9D9Q32/aW1Lar0PdIaL/wGEDEyzEBFwrGppcENLilPz8gzU= -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjEyWhcNMjYwNDAy MTgzNjEyWjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKY3MucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAL8uTYc5LcxHbB02xger6mADZrOa59c1xZkyaxpVgsKC LDZk32ob2ZKwtXAl02e32zwfF0G6QLc9mUlOznMAVvQfKJGFD6Pc4v6bcHK4LELD dIa3A/DAUVHHHr8IpnuXy74PBPUSf+vMvctwZ0XFVmDtrACvY7k34nTYxE1NwW9O aF7/A0g/+qJ10LuLlw4LEN3EV3pXfVAdMWs5kF8soiKl3P3KUwJ2hFWM59vGo+zZ VG8zauddVFzF2OOMfRHF1qT1We4XjXCTc2OQwjc5rVyq7fcojKVkHD1xM7BElcQD Kom/bkxTpb1V9eyyD8QRuuej40w/SdYepAWzAlMc7O8CAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFHPfd8dK Lz1R0Ck4WV1B9AWXd5DSMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQB6WSIHEyDXLZxH hZjqSNQOA7Wc9Z2FCAiD29xYkGTL8WuPVGGP1mu4B92ytj+PMWwqSReDa7eTGLE7 O7ozw9l+c+gNmHFNikSsGjlV2E8CToQOFMny+jAQYMSXf8UbTp9xDfgG02t/71hv SLWqdeHMLcR0xi0nBQH0vDOkwUbuWYqFa3jejHieGhykHM6CkIk6lqnyOEO+ooIF ZsLprrg1ss/mXCPI6niP0hze55ERKdxI7Rk8sZ4pVkf2SUWqZrUS0aJ+Ymmwi6Xd 2V7izq5N30PkJS8MtqII4FAjRBIkwPh0sy8PmW/DzkYU+lYQnDfYLKDFKcj8xJK/ o8oZUBsQltrSj0KlM9QuqxCTCBCy1nXZ9WHOhq+jdLiTc1Oi60uEHcUMrLK8aYc4 HqIvZS6C2iwMI0d1OP3VxmAbMQ9yqRi+FbLYavJ3H40jrU9SYqdxa0BrTaz8MJNE 6AEwgQDPChczSghvHME+Fs4mtGCY3TesbNZKVahQRjaFIhMZIZ4RP4CRc0bJOBG+ 8Me4+KHNsD2ki5b03wAN6C1P2QrMzI+gH9fXLZYp761ciDAsX6YIzrhHHYLxYpJH BkQKKs8dCQWE5IzgVrdlvC3Z1/l9om66wHqqx7nKnPfYs/Sfnwe9MpCD6xJrXiTm WS7NM6fbQpO9APNr7o0ZOjbbWFzlNw== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_cs_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjEyWhcNMjYwNDAy MTgzNjEyWjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKY3MucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAL8uTYc5LcxHbB02xger6mADZrOa59c1xZkyaxpVgsKC LDZk32ob2ZKwtXAl02e32zwfF0G6QLc9mUlOznMAVvQfKJGFD6Pc4v6bcHK4LELD dIa3A/DAUVHHHr8IpnuXy74PBPUSf+vMvctwZ0XFVmDtrACvY7k34nTYxE1NwW9O aF7/A0g/+qJ10LuLlw4LEN3EV3pXfVAdMWs5kF8soiKl3P3KUwJ2hFWM59vGo+zZ VG8zauddVFzF2OOMfRHF1qT1We4XjXCTc2OQwjc5rVyq7fcojKVkHD1xM7BElcQD Kom/bkxTpb1V9eyyD8QRuuej40w/SdYepAWzAlMc7O8CAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFHPfd8dK Lz1R0Ck4WV1B9AWXd5DSMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQB6WSIHEyDXLZxH hZjqSNQOA7Wc9Z2FCAiD29xYkGTL8WuPVGGP1mu4B92ytj+PMWwqSReDa7eTGLE7 O7ozw9l+c+gNmHFNikSsGjlV2E8CToQOFMny+jAQYMSXf8UbTp9xDfgG02t/71hv SLWqdeHMLcR0xi0nBQH0vDOkwUbuWYqFa3jejHieGhykHM6CkIk6lqnyOEO+ooIF ZsLprrg1ss/mXCPI6niP0hze55ERKdxI7Rk8sZ4pVkf2SUWqZrUS0aJ+Ymmwi6Xd 2V7izq5N30PkJS8MtqII4FAjRBIkwPh0sy8PmW/DzkYU+lYQnDfYLKDFKcj8xJK/ o8oZUBsQltrSj0KlM9QuqxCTCBCy1nXZ9WHOhq+jdLiTc1Oi60uEHcUMrLK8aYc4 HqIvZS6C2iwMI0d1OP3VxmAbMQ9yqRi+FbLYavJ3H40jrU9SYqdxa0BrTaz8MJNE 6AEwgQDPChczSghvHME+Fs4mtGCY3TesbNZKVahQRjaFIhMZIZ4RP4CRc0bJOBG+ 8Me4+KHNsD2ki5b03wAN6C1P2QrMzI+gH9fXLZYp761ciDAsX6YIzrhHHYLxYpJH BkQKKs8dCQWE5IzgVrdlvC3Z1/l9om66wHqqx7nKnPfYs/Sfnwe9MpCD6xJrXiTm WS7NM6fbQpO9APNr7o0ZOjbbWFzlNw== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_cs_key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAvy5NhzktzEdsHTbGB6vqYANms5rn1zXFmTJrGlWCwoIsNmTf ahvZkrC1cCXTZ7fbPB8XQbpAtz2ZSU7OcwBW9B8okYUPo9zi/ptwcrgsQsN0hrcD 8MBRUccevwime5fLvg8E9RJ/68y9y3BnRcVWYO2sAK9juTfidNjETU3Bb05oXv8D SD/6onXQu4uXDgsQ3cRXeld9UB0xazmQXyyiIqXc/cpTAnaEVYzn28aj7NlUbzNq 511UXMXY44x9EcXWpPVZ7heNcJNzY5DCNzmtXKrt9yiMpWQcPXEzsESVxAMqib9u TFOlvVX17LIPxBG656PjTD9J1h6kBbMCUxzs7wIDAQABAoIBAQC85lBeY0X4ST3v I7bJz7kWQ2YP4uhfAdeLhoDDFWjNLffniwYhfwEc6xNri0R2f/jUT9gX7qORKwEx qPdeNCC2t67LElGg1FlJv2Z9Q7MgCKYzkdQH5s6y4e9kTHTLO/JpiceZKz1QTQ3f XOH9032E6nIAf0wmr6xHTgOwajrN8VI5BuPEMVmEwIw3AtYeqVuPCNKyGR4HUVkC 2bAydnGngbRJRnNzmKcWJancxpHDGBSFqPyuXMFC7Jgo3ZmyCbGp99vuXVk/sW9x 5aj94M9nRE0guk05ivH2/JZao2uLYkIgjFWlhNxKdWgWRk8DEuN4djC8mKS9YH1q crYRToMhAoGBAOspUTtKP54mpZmyhxuDqj02JaJRzNTskPHsiF1UhtXuw7uT+ryV ekUFLNXoFmn9mbx1WVaUvGH4qjilvQOxz7u++lz0ApqJEfyM3jc/cC40Y5zcuGSu Etbg+SyDoytlgMCIydJyrS7NNALSo5p5oG6XY2f8yd/DCAmo8LzypaHRAoGBANAf R1SlBMc/bOsi6GrJxcBVSCFMiKYiO5woL5aUKa9yM+UQuQ/6xbQ7Q+sOlt0FH3xo AJ2L60qTdjyXVtjOdtXs5ZC4l+C6AfnCx6yLr+fNc4SOYXEfqS4LZylgwKd9KyVB asspIW9Idbgebmi6vPyt9LDkIp0h1VuFGjkvQJK/AoGBAI4pbS0dprXyARyYW6sb fpgAmuG099IkrT9DUfCx/81myTclr2fAKal+BmvOIXaz0/OlMXvw8K19iVIzh7+r B70lJ+93p/dKM/BsLI5TsHqOO0YB/QsIXOVAHgJ2FfdPJnW+e9vYba+kZ/Po6PSi 4ITaykJ8BIJcQgis89QWEGFxAoGBAJhQO+jzuDKF9ZWEf6ofrw0anOZZ16wWY5/e PS2rk3JmVxpuibHrKqPDt+ogTELHDAsFJmYmz3VNxHuFmrajK49Wh4/JuMVr/CQo 6+8YcA1qa/94IFIlBLDBAafjujsZvOjQHnM+z8xcsGKmStF00Pjv6qNG4xoyd646 FD4DmfOLAoGAWXehpopZKXE9gRAni881ucK6WqxPPBoofbozi09D0MmfarIVaSkv jNVVHBfLWd7IEXTjiipPBeUqq6Jc3pscN1Vp4rrl8jTmVTdazEv0LuzpdUFqmNo2 M+xw17uz9D9Q32/aW1Lar0PdIaL/wGEDEyzEBFwrGppcENLilPz8gzU= -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/sni_server_uk.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEArMpFePSNUHNHCiUWfsSPQxHaQTpDfogEISIMuGq5JhEuHWyl HRe51JWZGi39a/PY9rAClfg/qqPnuxwFDoovhQsq9Kd0JnsXW11CMSqiBimf8vyg GE/V2DIOiex7Ab8+5zEL7cCrNyXfDk+DuoT7Np3jaSE/u31OMUAzFz4/EL7Zqzlk IMkHB/GSwLIJB8VbHt4Hunf28bLreTcjO/5QpNkSFtJ1PvbWRv3WheR5ykQQWGKF 9VgwYwS0Lu7YktLF2RGFZhIypUK8jazJiwsBRQ0faSCpAXTSPYh12GDegwAiKqGk L+fayUdWGnnpYTVOysYL8mCoi9Ar0BPgtHEVvwIDAQABAoIBAGpg7XyWMxpAno/k XYEWSo0kRJa75CnWW5R6fhJbwjlpglajONQ8czAqGeRARDFeI0lc+3qRJ8FKv7Q2 f/Z/pNnoEj4liiDWz350X/gdIztgDRVv6rCNFj7QMps/eEuPDo8PZySs5bxCGJ2a 3qLKlE7/za4/xhkMAEPaHppEvFb15dpomqP2Fjqei9afp8tD+xJv9BGtkFIy9acw 94AWQi974kgW7ZzfEf8RqHM0ExKiESCc26knbcBu0tQ892YHggMccUMk/UrVgR5d vuGOBurlYGLBstSDSKCCe6la4N/FX3FiC3WhVNescnuTZPXcX3zebh7roGjgrTdd 6UJYBIECgYEA09XYihdwW66G9Y09OatzlzBh1Rvzjv2Vl5NmWHHb6vh38H4PcOof S1pT0JvRzpt8uEs4i+Eda5PyQyp4iGtD+DQ07Dxh2jiixsE3PRFRG2wg2QCgDHnS 6vgV1VOqA/A4on4nhxLZXxhX4YvHaJYlm1bZ+amG+j5XiI/NtHJm9k8CgYEA0NB9 bAoaZnc06+T8rMaO45boX5SvYsCZbtY3VRl75SrbMal0IKfnXHELVHuei12LmlMs LuFgG8jGO3+ncxUqMytoGvrmk/cM9tCNbDKVHtFbqz0qt9lkDAwLsBougKnVsNDW Aae/tz/wYXJct86unfLh0xtvkx5Iz1we4cS1fZECgYA8YR+vfK7R8xUUuIVTAsOX vHqmO6lYgH87DRCG9S0x9FB7g/LyxEjXOY/aTg+nTDpobUhY1nmESE8tRdXFTI27 GWZcT3m0sZ/z9u6/wUfVAST7tWnpJHAx+TR/8bDsHnSGHF836O98Y3vpFeZosSNW +5J1zxRiD5LzocmIPXOLkQKBgEgHApxXx898YwbTj8zRGMysay89DFpV8RboUWHL To83/y/cMbBp+kZKwAu+MGwGMndjJSRunUY4NRik6c+qh0nrORfFX+++Efy4529g 60scEDC7Apc0J2x4Yze1cED1VD6PaqJbiKffKD2UwyKr6lOVSgwVtKDcm2Tbc9OQ lMHhAoGBAIPwYVxzWM6I6pr8x2TucpBZZReLytz7uzybMNvbKCrwlETbNNXubnfp nPuPKzpeRI0y26pIIAbijzFW0MXq+kIu3H7we8TaImsJ1AaQCTYeoLWBVcr6RISk 3d4i7iT35aWCuhPVve0FNIv/u3jrqX2H2C2MXMiLOsw1GFxPvpi4 -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjMyWhcNMjYwNDAy MTgzNjMyWjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKdWsucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAKzKRXj0jVBzRwolFn7Ej0MR2kE6Q36IBCEiDLhquSYR Lh1spR0XudSVmRot/Wvz2PawApX4P6qj57scBQ6KL4ULKvSndCZ7F1tdQjEqogYp n/L8oBhP1dgyDonsewG/PucxC+3Aqzcl3w5Pg7qE+zad42khP7t9TjFAMxc+PxC+ 2as5ZCDJBwfxksCyCQfFWx7eB7p39vGy63k3Izv+UKTZEhbSdT721kb91oXkecpE EFhihfVYMGMEtC7u2JLSxdkRhWYSMqVCvI2syYsLAUUNH2kgqQF00j2Iddhg3oMA IiqhpC/n2slHVhp56WE1TsrGC/JgqIvQK9AT4LRxFb8CAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFJK58uJe Lv8WDOR8TTLUcSxO1zCyMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQDHayU795hFnQNj fuYV21sEmkSjgtp6X4rV9JfoCp8U2gieL4mizoCPx9Q8HYaAf98hPYTofGUaCSd+ Xlzd8lhLt9GCU+BsCxGtJocrjOQDhyhkioN8iirQRTtJpRVC6a4RCBGpenOD7Z7C Z32uGXohzFuigP/J8/SXBdHrlpNSa03iEkkisPR46mbGj06lFVOFHJDNKA3CnQwM 3VfqvZt0ksZi/gAOol8dtrAd3k5JBfJ2BtqsPbycEGAhsGrTK/MeqiFcO8QZiXpN qf0wZzOMFZJ4HqTiHs4TMMQaDK8c4qfR0l1OE224ijcB5NlqMjGAZabMfHgNZcl7 fYPX+POsOMaqubv98uoC0PCK8NCTyfB4V/el72EEoVfwKYOxm3eYXBEE2ZNyIci6 YKYhNa9e2k+cFueYMztV06HTGgkcHcSJATP+3ISTRx3tj1BPmc4O4WAMEMx1E0gg PaVrBVAylGzt8kaMaOQMBImpsALSfutq6+KtQiu0OTGS3yVUGamV4AyRjuwF1/ZA kZbjmNEGsf9d2rzS89ckds24QUx0zrfJ56p7lRvyVK/pML7iadv8dUH3fKDDDerD oocLjOjNxFGN9Woz+kfidA8Siu0zsfQ6CLPlaQrVAVQH4iMuCZraqMc3TqAiUKRj iHKp6FFrRZmY8W8HEKHv98uGofn+3g== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_uk_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjMyWhcNMjYwNDAy MTgzNjMyWjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKdWsucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAKzKRXj0jVBzRwolFn7Ej0MR2kE6Q36IBCEiDLhquSYR Lh1spR0XudSVmRot/Wvz2PawApX4P6qj57scBQ6KL4ULKvSndCZ7F1tdQjEqogYp n/L8oBhP1dgyDonsewG/PucxC+3Aqzcl3w5Pg7qE+zad42khP7t9TjFAMxc+PxC+ 2as5ZCDJBwfxksCyCQfFWx7eB7p39vGy63k3Izv+UKTZEhbSdT721kb91oXkecpE EFhihfVYMGMEtC7u2JLSxdkRhWYSMqVCvI2syYsLAUUNH2kgqQF00j2Iddhg3oMA IiqhpC/n2slHVhp56WE1TsrGC/JgqIvQK9AT4LRxFb8CAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFJK58uJe Lv8WDOR8TTLUcSxO1zCyMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQDHayU795hFnQNj fuYV21sEmkSjgtp6X4rV9JfoCp8U2gieL4mizoCPx9Q8HYaAf98hPYTofGUaCSd+ Xlzd8lhLt9GCU+BsCxGtJocrjOQDhyhkioN8iirQRTtJpRVC6a4RCBGpenOD7Z7C Z32uGXohzFuigP/J8/SXBdHrlpNSa03iEkkisPR46mbGj06lFVOFHJDNKA3CnQwM 3VfqvZt0ksZi/gAOol8dtrAd3k5JBfJ2BtqsPbycEGAhsGrTK/MeqiFcO8QZiXpN qf0wZzOMFZJ4HqTiHs4TMMQaDK8c4qfR0l1OE224ijcB5NlqMjGAZabMfHgNZcl7 fYPX+POsOMaqubv98uoC0PCK8NCTyfB4V/el72EEoVfwKYOxm3eYXBEE2ZNyIci6 YKYhNa9e2k+cFueYMztV06HTGgkcHcSJATP+3ISTRx3tj1BPmc4O4WAMEMx1E0gg PaVrBVAylGzt8kaMaOQMBImpsALSfutq6+KtQiu0OTGS3yVUGamV4AyRjuwF1/ZA kZbjmNEGsf9d2rzS89ckds24QUx0zrfJ56p7lRvyVK/pML7iadv8dUH3fKDDDerD oocLjOjNxFGN9Woz+kfidA8Siu0zsfQ6CLPlaQrVAVQH4iMuCZraqMc3TqAiUKRj iHKp6FFrRZmY8W8HEKHv98uGofn+3g== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_uk_key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEArMpFePSNUHNHCiUWfsSPQxHaQTpDfogEISIMuGq5JhEuHWyl HRe51JWZGi39a/PY9rAClfg/qqPnuxwFDoovhQsq9Kd0JnsXW11CMSqiBimf8vyg GE/V2DIOiex7Ab8+5zEL7cCrNyXfDk+DuoT7Np3jaSE/u31OMUAzFz4/EL7Zqzlk IMkHB/GSwLIJB8VbHt4Hunf28bLreTcjO/5QpNkSFtJ1PvbWRv3WheR5ykQQWGKF 9VgwYwS0Lu7YktLF2RGFZhIypUK8jazJiwsBRQ0faSCpAXTSPYh12GDegwAiKqGk L+fayUdWGnnpYTVOysYL8mCoi9Ar0BPgtHEVvwIDAQABAoIBAGpg7XyWMxpAno/k XYEWSo0kRJa75CnWW5R6fhJbwjlpglajONQ8czAqGeRARDFeI0lc+3qRJ8FKv7Q2 f/Z/pNnoEj4liiDWz350X/gdIztgDRVv6rCNFj7QMps/eEuPDo8PZySs5bxCGJ2a 3qLKlE7/za4/xhkMAEPaHppEvFb15dpomqP2Fjqei9afp8tD+xJv9BGtkFIy9acw 94AWQi974kgW7ZzfEf8RqHM0ExKiESCc26knbcBu0tQ892YHggMccUMk/UrVgR5d vuGOBurlYGLBstSDSKCCe6la4N/FX3FiC3WhVNescnuTZPXcX3zebh7roGjgrTdd 6UJYBIECgYEA09XYihdwW66G9Y09OatzlzBh1Rvzjv2Vl5NmWHHb6vh38H4PcOof S1pT0JvRzpt8uEs4i+Eda5PyQyp4iGtD+DQ07Dxh2jiixsE3PRFRG2wg2QCgDHnS 6vgV1VOqA/A4on4nhxLZXxhX4YvHaJYlm1bZ+amG+j5XiI/NtHJm9k8CgYEA0NB9 bAoaZnc06+T8rMaO45boX5SvYsCZbtY3VRl75SrbMal0IKfnXHELVHuei12LmlMs LuFgG8jGO3+ncxUqMytoGvrmk/cM9tCNbDKVHtFbqz0qt9lkDAwLsBougKnVsNDW Aae/tz/wYXJct86unfLh0xtvkx5Iz1we4cS1fZECgYA8YR+vfK7R8xUUuIVTAsOX vHqmO6lYgH87DRCG9S0x9FB7g/LyxEjXOY/aTg+nTDpobUhY1nmESE8tRdXFTI27 GWZcT3m0sZ/z9u6/wUfVAST7tWnpJHAx+TR/8bDsHnSGHF836O98Y3vpFeZosSNW +5J1zxRiD5LzocmIPXOLkQKBgEgHApxXx898YwbTj8zRGMysay89DFpV8RboUWHL To83/y/cMbBp+kZKwAu+MGwGMndjJSRunUY4NRik6c+qh0nrORfFX+++Efy4529g 60scEDC7Apc0J2x4Yze1cED1VD6PaqJbiKffKD2UwyKr6lOVSgwVtKDcm2Tbc9OQ lMHhAoGBAIPwYVxzWM6I6pr8x2TucpBZZReLytz7uzybMNvbKCrwlETbNNXubnfp nPuPKzpeRI0y26pIIAbijzFW0MXq+kIu3H7we8TaImsJ1AaQCTYeoLWBVcr6RISk 3d4i7iT35aWCuhPVve0FNIv/u3jrqX2H2C2MXMiLOsw1GFxPvpi4 -----END RSA PRIVATE KEY----- ================================================ FILE: tests/include/ssl_certs/sni_server_us.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEA1QkC3tiNYDY+ZxMmPbagYUbMpzuXo9mVBvYh86bYZaeB7bts QCBK+6VD4D2LjR3RszpzmOzhJXjm8j0t+GeRS3OMIM75/BKAnixXicRSIb8zdIPz JP992vvMq8p46/XftAfBhAMOaCqcD85zpyX4PhfC9733nOyN4yqx58O4UhVTKih6 W9/ldp3uwSYAW+HyoINnHls/bFO8vv60K5VIhkxK30LHnC5PvByGfuHOgrscEThs jW0ESqO0+9l88KhGdmLgzvbBlGxNziCMfn0LcFH6p2ITc3foD4LSzGEFtJ92OZi3 buCOfbFsN7vWHTsEi89fRcCnWGtMwLUx0TCluQIDAQABAoIBAQCnsUQ1Lrl6trhA Yu6DPbLZX+XQ7jPbonaQ2Ea5iOhmfIjmHdaEU+cyV1EqvseO+Z4MO0KraiuAV79T h50cIEpa3kW7vbFCHz5nQ/hUVdlg/yT93rASu5rSOctOnz64Xv8Ms948kDtS+9eF Cbo4JMdX+VRbt4mmWP8HhqAsFACPexEoWxJcIxwFcI24GTGzySjemNjQzbmcVhzM a4k6n8DolCL1cRS54C5Aaf5g4+IFDgyydcgZXp1lnX3MnqivSNkejnPnY55NcmrH X3ZWPlAi9GHOJE33uy8bGWnip7Tn4iTt6tJvjz/yP82TGACDg1B8XsKrqsuQLsoU cNBVGcQBAoGBAPteCgNmuNOpo4SRA1UVRw1WgnE8YtnNA6vYyVcTLSpqabq33UaD 03L9CQsbHtj88U+E8OH24Iqj3U9x7QJfH8DVmWuBrlwez80JsKGnLdViHydjKcAz H2Cbv+SiWeaWXkFCkN4Jf7k3q0Ew4SG2LOq5PVUy/NB4bilbJD2ExKpZAoGBANj2 Hpwo35IQ4XfSSsGaCdn+8ajMcNUMMGZ6YkZqmVO4kogqobyrPL/2KE9ol/hlacw6 U/6Digox5/wqruYfqyM8lqGOq2/0Xf7c4XfiOTS9Na4JN3OGzlyqPvcn2zdqhYFY iHPu2RqpA+LhCHW9Zs8C1Bp/KAEPdRP6OabqVaphAoGACLrHVj7nBFLL3vq6RuYq RYhPl2cld7LrAbjRpTiBRQvVCCsCgERrv36SJdSXSanfJ4fSZcaRHb97HBs0w/RR wfypC1bBm2lmhhRkEfkgWlzCADgtZwNff5dpHqOUw7FNLK8HIO7rhJ8uT2FHMEiH Xs94FdFjfknwaXdE1u4ZdmECgYEAgxfbkQHFbO2UPqErGGXp0/WOsS6ucpyF1jXW kbOxZ3vb1jjkNyrEbzzeSHTrdmRYk9UekWeLjfNvt9dWjKfP8V+XqJCbF+9wqCFw fs6LQEmfWMQq5DwtDqKznwVPGOHdPzVuZZaJSemb9oeAZBwINccAv+3bDyD23hZQ pYFsN6ECgYEA33QYDNG/spki4D8rlxyxZ+1MdB/efnrGBhO8FsJpG5+AtmYhWgD9 sl29+3aiRkmDznoy36z+hoeZePILEAKMcbHyXOymixOHPuaZJ95hbvq6sqd6WMAe w5tHnxlfEuu11zatolk6WiAmTmG3sZpN5Tqloq0Ye4dvlhVKNV3Bn3E= -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAQwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjQ0WhcNMjYwNDAy MTgzNjQ0WjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKdXMucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBANUJAt7YjWA2PmcTJj22oGFGzKc7l6PZlQb2IfOm2GWn ge27bEAgSvulQ+A9i40d0bM6c5js4SV45vI9LfhnkUtzjCDO+fwSgJ4sV4nEUiG/ M3SD8yT/fdr7zKvKeOv137QHwYQDDmgqnA/Oc6cl+D4Xwve995zsjeMqsefDuFIV Uyooelvf5Xad7sEmAFvh8qCDZx5bP2xTvL7+tCuVSIZMSt9Cx5wuT7wchn7hzoK7 HBE4bI1tBEqjtPvZfPCoRnZi4M72wZRsTc4gjH59C3BR+qdiE3N36A+C0sxhBbSf djmYt27gjn2xbDe71h07BIvPX0XAp1hrTMC1MdEwpbkCAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFOXnUeNs grKQb+EvG36DXjjDDmsFMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBDLwuLbx75ACSU 5cF2L/D17JEnhlna62MgKNdpNLJSpaofK2Lk2BqsmnQf5JdkrXWMUN/DsmXZc9pq 25XmprfABUP9Cx18KPVqLQ43Z9o+R9xI1Ospt5mrpxGp6l2BHSs/4G69nuPFpcIJ iabnLYdUk2Z+64lPe4EMBrZH+pj4xn3JA59BACJYNYn0nLaw45DIAyzyLJ0vVSwc 0JtjsztXQov4UqdWXxLRFfe2nEGoK8ZkTJ8ELcCYu6sNSBjw9Ech78uXN1BQOBTK lhAgN3FKqOp3hqf0umqf35gDvmWwLB/eptUYZ96gBYT0tbPA0P+YsW+iZmamxXma Odgg8iRcPxKl9bVPt57NLaDy/RQhOxTGXQs1Q2jp7UhzqoZDClwVSDxd6DEppAFA OZAY+Rsrm7VoCwVQ/1KbcJHmJ/79tArvaWJk3KHLGMpdZq4KwrC3hM8/QxYtyX/6 cfnXvShBYCdfTGgNlj3t/mNAgp1ZB3s9ClGqRBR/P0Db+ryv5DuxYM6nzEB3Od0y kT5tHbXDQY+1HCExjOMi7Al0cmC2r3+oxDA4UjGv+npgcfeoxQhXmm/SQRiPdKlb vT0D594sLoB23jqA9bMehpxEyI7eGjfFUmXwMeu0tJhipvpJI3ogJoM+SCFTyLkc 12cPiz/sR/ALhvhUJXTeUH8wxPjzbA== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_us_cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIFIjCCAwqgAwIBAgICEAQwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjQ0WhcNMjYwNDAy MTgzNjQ0WjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE CgwHUEhQLm5ldDETMBEGA1UEAwwKdXMucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBANUJAt7YjWA2PmcTJj22oGFGzKc7l6PZlQb2IfOm2GWn ge27bEAgSvulQ+A9i40d0bM6c5js4SV45vI9LfhnkUtzjCDO+fwSgJ4sV4nEUiG/ M3SD8yT/fdr7zKvKeOv137QHwYQDDmgqnA/Oc6cl+D4Xwve995zsjeMqsefDuFIV Uyooelvf5Xad7sEmAFvh8qCDZx5bP2xTvL7+tCuVSIZMSt9Cx5wuT7wchn7hzoK7 HBE4bI1tBEqjtPvZfPCoRnZi4M72wZRsTc4gjH59C3BR+qdiE3N36A+C0sxhBbSf djmYt27gjn2xbDe71h07BIvPX0XAp1hrTMC1MdEwpbkCAwEAAaOCAQkwggEFMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFOXnUeNs grKQb+EvG36DXjjDDmsFMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBDLwuLbx75ACSU 5cF2L/D17JEnhlna62MgKNdpNLJSpaofK2Lk2BqsmnQf5JdkrXWMUN/DsmXZc9pq 25XmprfABUP9Cx18KPVqLQ43Z9o+R9xI1Ospt5mrpxGp6l2BHSs/4G69nuPFpcIJ iabnLYdUk2Z+64lPe4EMBrZH+pj4xn3JA59BACJYNYn0nLaw45DIAyzyLJ0vVSwc 0JtjsztXQov4UqdWXxLRFfe2nEGoK8ZkTJ8ELcCYu6sNSBjw9Ech78uXN1BQOBTK lhAgN3FKqOp3hqf0umqf35gDvmWwLB/eptUYZ96gBYT0tbPA0P+YsW+iZmamxXma Odgg8iRcPxKl9bVPt57NLaDy/RQhOxTGXQs1Q2jp7UhzqoZDClwVSDxd6DEppAFA OZAY+Rsrm7VoCwVQ/1KbcJHmJ/79tArvaWJk3KHLGMpdZq4KwrC3hM8/QxYtyX/6 cfnXvShBYCdfTGgNlj3t/mNAgp1ZB3s9ClGqRBR/P0Db+ryv5DuxYM6nzEB3Od0y kT5tHbXDQY+1HCExjOMi7Al0cmC2r3+oxDA4UjGv+npgcfeoxQhXmm/SQRiPdKlb vT0D594sLoB23jqA9bMehpxEyI7eGjfFUmXwMeu0tJhipvpJI3ogJoM+SCFTyLkc 12cPiz/sR/ALhvhUJXTeUH8wxPjzbA== -----END CERTIFICATE----- ================================================ FILE: tests/include/ssl_certs/sni_server_us_key.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEA1QkC3tiNYDY+ZxMmPbagYUbMpzuXo9mVBvYh86bYZaeB7bts QCBK+6VD4D2LjR3RszpzmOzhJXjm8j0t+GeRS3OMIM75/BKAnixXicRSIb8zdIPz JP992vvMq8p46/XftAfBhAMOaCqcD85zpyX4PhfC9733nOyN4yqx58O4UhVTKih6 W9/ldp3uwSYAW+HyoINnHls/bFO8vv60K5VIhkxK30LHnC5PvByGfuHOgrscEThs jW0ESqO0+9l88KhGdmLgzvbBlGxNziCMfn0LcFH6p2ITc3foD4LSzGEFtJ92OZi3 buCOfbFsN7vWHTsEi89fRcCnWGtMwLUx0TCluQIDAQABAoIBAQCnsUQ1Lrl6trhA Yu6DPbLZX+XQ7jPbonaQ2Ea5iOhmfIjmHdaEU+cyV1EqvseO+Z4MO0KraiuAV79T h50cIEpa3kW7vbFCHz5nQ/hUVdlg/yT93rASu5rSOctOnz64Xv8Ms948kDtS+9eF Cbo4JMdX+VRbt4mmWP8HhqAsFACPexEoWxJcIxwFcI24GTGzySjemNjQzbmcVhzM a4k6n8DolCL1cRS54C5Aaf5g4+IFDgyydcgZXp1lnX3MnqivSNkejnPnY55NcmrH X3ZWPlAi9GHOJE33uy8bGWnip7Tn4iTt6tJvjz/yP82TGACDg1B8XsKrqsuQLsoU cNBVGcQBAoGBAPteCgNmuNOpo4SRA1UVRw1WgnE8YtnNA6vYyVcTLSpqabq33UaD 03L9CQsbHtj88U+E8OH24Iqj3U9x7QJfH8DVmWuBrlwez80JsKGnLdViHydjKcAz H2Cbv+SiWeaWXkFCkN4Jf7k3q0Ew4SG2LOq5PVUy/NB4bilbJD2ExKpZAoGBANj2 Hpwo35IQ4XfSSsGaCdn+8ajMcNUMMGZ6YkZqmVO4kogqobyrPL/2KE9ol/hlacw6 U/6Digox5/wqruYfqyM8lqGOq2/0Xf7c4XfiOTS9Na4JN3OGzlyqPvcn2zdqhYFY iHPu2RqpA+LhCHW9Zs8C1Bp/KAEPdRP6OabqVaphAoGACLrHVj7nBFLL3vq6RuYq RYhPl2cld7LrAbjRpTiBRQvVCCsCgERrv36SJdSXSanfJ4fSZcaRHb97HBs0w/RR wfypC1bBm2lmhhRkEfkgWlzCADgtZwNff5dpHqOUw7FNLK8HIO7rhJ8uT2FHMEiH Xs94FdFjfknwaXdE1u4ZdmECgYEAgxfbkQHFbO2UPqErGGXp0/WOsS6ucpyF1jXW kbOxZ3vb1jjkNyrEbzzeSHTrdmRYk9UekWeLjfNvt9dWjKfP8V+XqJCbF+9wqCFw fs6LQEmfWMQq5DwtDqKznwVPGOHdPzVuZZaJSemb9oeAZBwINccAv+3bDyD23hZQ pYFsN6ECgYEA33QYDNG/spki4D8rlxyxZ+1MdB/efnrGBhO8FsJpG5+AtmYhWgD9 sl29+3aiRkmDznoy36z+hoeZePILEAKMcbHyXOymixOHPuaZJ95hbvq6sqd6WMAe w5tHnxlfEuu11zatolk6WiAmTmG3sZpN5Tqloq0Ye4dvlhVKNV3Bn3E= -----END RSA PRIVATE KEY----- ================================================ FILE: tests/init ================================================ #!/usr/bin/env php $line) { if (strlen($line) === 0) { continue; } if (substr($line, -1, 1) !== ';') { if (!$multi) { $multi = true; goto _new_line; } else { _append: $end_line = &$init_sql[count($init_sql) - 1]; $end_line = $end_line . $line . "\n"; } } else { if ($multi) { $multi = false; goto _append; } else { $multi = false; _new_line: $init_sql[] = "{$line}"; } } } return $init_sql; } // 解析命令行参数 $options = getopt('', ['mysql', 'firebird', 'odbc-mysql', 'help']); // 显示帮助信息 if (isset($options['help'])) { echo "Usage: php init [options]\n"; echo "Options:\n"; echo " --mysql init MySQL database\n"; echo " --firebird init Firebird database\n"; echo " --odbc-mysql init ODBC MySQL database\n"; echo " --help Show help message\n"; echo "default: init all databases\n"; exit(0); } require __DIR__ . '/include/config.php'; $has_specific_option = isset($options['mysql']) || isset($options['firebird']) || isset($options['odbc-mysql']); $load_mysql = !$has_specific_option || isset($options['mysql']); $load_firebird = !$has_specific_option || isset($options['firebird']); $load_odbc_mysql = !$has_specific_option || isset($options['odbc-mysql']); require __DIR__ . '/swoole_ssh2/ssh2_test.inc'; if ($load_firebird) { require __DIR__ . '/swoole_pdo_firebird/pdo_firebird.inc'; } swoole_library_set_option('default_remote_object_server_worker_num', 8); swoole_init_default_remote_object_server(); Swoole\Coroutine\run(function () use ($load_mysql, $load_firebird, $load_odbc_mysql) { echo "[SSH2-init] Setting up SSH2 test environment...\n"; $ssh_user = TEST_SSH2_USER ; $ssh_password = TEST_SSH2_PASS; $user_exists = trim(shell_exec("id -u $ssh_user 2>/dev/null")); if (empty($user_exists)) { echo "[SSH2-init] Creating test user: $ssh_user\n"; exec("useradd -m $ssh_user"); exec("echo '$ssh_user:$ssh_password' | chpasswd"); exec("mkdir -p /home/$ssh_user/.ssh"); echo "[SSH2-init] Copying SSH test key pair...\n"; exec("cat " . __DIR__ . '/swoole_ssh2/testkey_ed25519.pub' . " > /home/$ssh_user/.ssh/authorized_keys"); readfile("/home/$ssh_user/.ssh/authorized_keys"); readfile('/etc/ssh/sshd_config'); exec("chmod 700 /home/$ssh_user/.ssh"); exec("chown -R $ssh_user:$ssh_user /home/$ssh_user/.ssh"); exec("ssh -vvv -i " . __DIR__ . '/swoole_ssh2/testkey_ed25519' . " $ssh_user@localhost"); readfile('/var/log/auth.log'); echo "[SSH2-init] SSH2 test user setup completed\n"; } else { echo "[SSH2-init] User $ssh_user already exists, skipping creation\n"; } // 初始化MySQL数据库 if ($load_mysql) { echo "[DB-init] initialization MySQL database...\n"; $mysql = new mysqli(); $connected = $mysql->connect(MYSQL_SERVER_HOST, MYSQL_SERVER_USER, MYSQL_SERVER_PWD, MYSQL_SERVER_DB, MYSQL_SERVER_PORT); if (!$connected) { echo "[DB-init] Connect failed! Error#{$mysql->connect_errno}: {$mysql->connect_error}\n"; exit(1); } $sql_file = read_sql_file(__DIR__ . '/test.sql'); foreach ($sql_file as $line) { if (!$mysql->query($line)) { echo "[DB-init] Failed! Error#{$mysql->errno}: {$mysql->error}\n"; exit(1); } } echo "[DB-init] MySQL Done!\n"; } // 初始化ODBC MySQL配置 - 独立控制 if ($load_odbc_mysql) { echo "[DB-init] initialization ODBC...\n"; echo `set -ex`; file_put_contents('/etc/odbcinst.ini', "[mysql]" . PHP_EOL . "Driver=libmaodbc.so" . PHP_EOL . "Description=MariaDB Connector/ODBC(Unicode)" . PHP_EOL . "UsageCount=1" . PHP_EOL ); echo `odbcinst -q -d -n "mysql"`; file_put_contents('/etc/odbc.ini', "[mysql-test]" . PHP_EOL . "Description = MySQL test database" . PHP_EOL . "Trace = On" . PHP_EOL . "TraceFile = stderr" . PHP_EOL . "Driver = mysql" . PHP_EOL . "SERVER = " . MYSQL_SERVER_HOST . PHP_EOL . "USER = " . MYSQL_SERVER_USER . PHP_EOL . "PASSWORD =" . MYSQL_SERVER_PWD . PHP_EOL . "PORT = " . MYSQL_SERVER_PORT . PHP_EOL . "DATABASE = " . MYSQL_SERVER_DB); echo `odbcinst -i -d -f /etc/odbc.ini`; echo "[DB-init] ODBC Done!\n"; } // 初始化Firebird数据库 if ($load_firebird) { echo "[DB-init] initialization Firebird database...\n"; try { $firebird = PdoFirebirdTest::create(); // 创建一些基础表结构 - 使用Firebird兼容的方式 try { // 尝试查询表是否存在 - 使用单引号避免PHP解析$符号 $result = $firebird->query('SELECT 1 FROM rdb$relations WHERE rdb$relation_name = "SWOOLE_TEST"'); if (!$result || $result->fetchColumn() === false) { // 表不存在,创建表 $firebird->exec('CREATE TABLE swoole_test (id INTEGER PRIMARY KEY, data VARCHAR(255))'); } } catch (PDOException $e) { // 如果查询失败,尝试直接创建表(可能是因为权限问题) try { $firebird->exec('CREATE TABLE swoole_test (id INTEGER PRIMARY KEY, data VARCHAR(255))'); } catch (PDOException $e2) { // 如果表已存在,忽略此错误 if (!strpos($e2->getMessage(), 'already exists')) { throw $e2; } } } echo "[DB-init] Firebird Done!\n"; } catch (PDOException $e) { echo "[DB-init] Firebird initialization failed: " . $e->getMessage() . "\n"; } } }); ================================================ FILE: tests/new ================================================ #!/usr/bin/env php $filename]; } else { $path = pathinfo($filename); } $path['dirname'] = trim($path['dirname'], './'); // i know arg2 is a list but it's no problem $replacement = []; $tip = swoole_color("[Test name]", SWOOLE_COLOR_BLUE); $path['filename'] = fgetsin($tip, false);// use test name to be filename $tip = swoole_color("[Test intro]", SWOOLE_COLOR_BLUE); $replacement['test_intro'] = fgetsin($tip, false); $this_dir_name = explode('/', $path['dirname']); $replacement['test_name'] = end($this_dir_name); // use dir name to be test name $filename = "{$path['dirname']}/{$path['filename']}.phpt"; //if dir not exist, create it if (!is_dir(__DIR__ . "/{$path['dirname']}")) { echo swoole_color( "The dir [{$path['dirname']}] is not exist, if you want to create it? [y/n]: ", SWOOLE_COLOR_YELLOW ); if (yes()) { mkdir($path['dirname'], 0755, true); } else { exit(swoole_color('Can\'t generate the test file in nonexistent dir!', SWOOLE_COLOR_RED)); } } elseif (file_exists($filename)) { echo swoole_color( "The file [{$path['filename']}] is exist, if you want to overwrite it? [y/n]: ", SWOOLE_COLOR_YELLOW ); if (!yes()) { exit(swoole_color('You should rename your test filename.', SWOOLE_COLOR_RED)); } } //calc dir deep $deep = 0; $temp = $filename; while (($temp = dirname($temp)) !== '.') { $deep++; } if ($deep < 1) { goto error_filename; } $template = file_get_contents(__DIR__ . '/template'); $replacement['dir_deep'] = str_repeat('/..', $deep); foreach ($replacement as $key => $value) { $template = str_replace("{{{$key}}}", $value, $template); } if (file_put_contents($filename, $template)) { echo swoole_color("Generate the test file successfully!\n", SWOOLE_COLOR_GREEN) . "[" . __DIR__ . "/$filename]"; @shell_exec('/usr/bin/env git add ' . __DIR__ . "/$filename"); if (\stripos(PHP_OS, 'Darwin') !== false) { //MacOS $pstorm = '/usr/local/bin/phpstorm'; if (file_exists($pstorm) || ( file_exists('/Applications/PhpStorm.app') && file_put_contents($pstorm, file_get_contents(__DIR__ . '/include/macos/phpstorm.py')) && chmod($pstorm, 0744) ) ) { @shell_exec("{$pstorm} {$filename}"); } } } else { exit("\nGenerate the test file failed!"); } ================================================ FILE: tests/pgsql.sql ================================================ DROP TABLE IF EXISTS weather; CREATE TABLE weather ( id SERIAL primary key NOT NULL, city character varying(80), temp_lo integer, temp_hi integer, prcp real, date date); INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27') RETURNING id; INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('Test2', 11, 22, 0.3, '1994-11-28') RETURNING id; DROP TABLE IF EXISTS oid; CREATE TABLE oid ( id SERIAL primary key NOT NULL, oid oid); ================================================ FILE: tests/run-tests ================================================ #!/usr/bin/env php | | Preston L. Bannister | | Marcus Boerger | | Derick Rethans | | Sander Roobol | | Andrea Faulds | | (based on version by: Stig Bakken ) | | (based on the PHP 3 test framework by Rasmus Lerdorf) | +----------------------------------------------------------------------+ */ /* $Id: ebcaabba02a5af6f5dc62dda027befb95385d5fc $ */ /* Let there be no top-level code beyond this point: * Only functions and classes, thanks! * * Minimum required PHP version: 8.2.0 */ function show_usage(): void { echo << Run up to simultaneous testing processes in parallel for quicker testing on systems with multiple logical processors. Note that this is experimental feature. -l Read the testfiles to be executed from . After the test has finished all failed tests are written to the same . If the list is empty and no further test is specified then all tests are executed (same as: -r -w ). -r Read the testfiles to be executed from . -w Write a list of all failed tests to . -a Same as -w but append rather then truncating . -W Write a list of all tests and their result status to . -c Look for php.ini in directory or use as ini. -n Pass -n option to the php binary (Do not use a php.ini). -d foo=bar Pass -d option to the php binary (Define INI entry foo with value 'bar'). -g Comma separated list of groups to show during test run (possible values: PASS, FAIL, XFAIL, XLEAK, SKIP, BORK, WARN, LEAK, REDIRECT). -m Test for memory leaks with Valgrind (equivalent to -M memcheck). -M Test for errors with Valgrind tool. -p Specify PHP executable to run. -P Use PHP_BINARY as PHP executable to run (default). -q Quiet, no user interaction (same as environment NO_INTERACTION). -s Write output to . -x Sets 'SKIP_SLOW_TESTS' environmental variable. --offline Sets 'SKIP_ONLINE_TESTS' environmental variable. --verbose -v Verbose mode. --help -h This Help. --temp-source --temp-target [--temp-urlbase ] Write temporary files to by replacing from the filenames to generate with . In general, you want to make the path to your source files and some patch in your web page hierarchy with pointing to . --keep-[all|php|skip|clean] Do not delete 'all' files, 'php' test file, 'skip' or 'clean' file. --set-timeout [n] Set timeout for individual tests, where [n] is the number of seconds. The default value is 60 seconds, or 300 seconds when testing for memory leaks. --context [n] Sets the number of lines of surrounding context to print for diffs. The default value is 3. --show-[all|php|skip|clean|exp|diff|out|mem] Show 'all' files, 'php' test file, 'skip' or 'clean' file. You can also use this to show the output 'out', the expected result 'exp', the difference between them 'diff' or the valgrind log 'mem'. The result types get written independent of the log format, however 'diff' only exists when a test fails. --show-slow [n] Show all tests that took longer than [n] milliseconds to run. --no-clean Do not execute clean section if any. --color --no-color Do/Don't colorize the result type in the test result. --repeat [n] Run the tests multiple times in the same process and check the output of the last execution (CLI SAPI only). --bless Bless failed tests using scripts/dev/bless_tests.php. HELP; } /** * One function to rule them all, one function to find them, one function to * bring them all and in the darkness bind them. * This is the entry point and exit point überfunction. It contains all the * code that was previously found at the top level. It could and should be * refactored to be smaller and more manageable. */ function main() { /* This list was derived in a naïve mechanical fashion. If a member * looks like it doesn't belong, it probably doesn't; cull at will. */ global $DETAILED, $PHP_FAILED_TESTS, $SHOW_ONLY_GROUPS, $argc, $argv, $cfg, $cfgfiles, $cfgtypes, $conf_passed, $end_time, $environment, $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file, $html_file, $html_output, $ignored_by_ext, $ini_overwrites, $is_switch, $just_save_results, $log_format, $matches, $no_clean, $no_file_cache, $optionals, $output_file, $pass_option_n, $pass_options, $pattern_match, $php, $php_cgi, $phpdbg, $preload, $redir_tests, $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch, $temp_source, $temp_target, $temp_urlbase, $test_cnt, $test_dirs, $test_files, $test_idx, $test_list, $test_results, $testfile, $user_tests, $valgrind, $sum_results, $shuffle; // Parallel testing global $workers, $workerID; define('IS_WINDOWS', str_starts_with(PHP_OS, "WIN")); $workerID = 0; if (getenv("TEST_PHP_WORKER")) { $workerID = intval(getenv("TEST_PHP_WORKER")); run_worker(); return; } define('INIT_DIR', getcwd()); // change into the PHP source directory. if (getenv('TEST_PHP_SRCDIR')) { @chdir(getenv('TEST_PHP_SRCDIR')); } define('TEST_PHP_SRCDIR', getcwd()); if (!function_exists('proc_open')) { echo <<&1"), 'usage') ? '1' : '0'); if ($environment['HAS_NETSTAT']) { $environment['MYSQL_SERVER_PATH'] = getenv('MYSQL_SERVER_PATH') ?? (trim(shell_exec("netstat -ln | grep -o -m 1 -E '\S*mysqld?\.sock'")) ?: null); $environment['REDIS_SERVER_PATH'] = getenv('REDIS_SERVER_PATH') ?? (trim(shell_exec("netstat -ln | grep -o -m 1 -E '\S*redis.*\.sock'")) ?: null); } } $php = null; $php_cgi = null; $phpdbg = null; if (getenv('TEST_PHP_EXECUTABLE')) { $php = getenv('TEST_PHP_EXECUTABLE'); if ($php == 'auto') { $php = TEST_PHP_SRCDIR . '/sapi/cli/php'; putenv("TEST_PHP_EXECUTABLE=$php"); if (!getenv('TEST_PHP_CGI_EXECUTABLE')) { $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; if (file_exists($php_cgi)) { putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); } else { $php_cgi = null; } } } $environment['TEST_PHP_EXECUTABLE'] = $php; } if (getenv('TEST_PHP_CGI_EXECUTABLE')) { $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); if ($php_cgi == 'auto') { $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); } $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; } if (!getenv('TEST_PHPDBG_EXECUTABLE')) { if (IS_WINDOWS && file_exists(dirname($php) . "/phpdbg.exe")) { $phpdbg = realpath(dirname($php) . "/phpdbg.exe"); } elseif (file_exists(dirname($php) . "/../../sapi/phpdbg/phpdbg")) { $phpdbg = realpath(dirname($php) . "/../../sapi/phpdbg/phpdbg"); } elseif (file_exists("./sapi/phpdbg/phpdbg")) { $phpdbg = realpath("./sapi/phpdbg/phpdbg"); } elseif (file_exists(dirname($php) . "/phpdbg")) { $phpdbg = realpath(dirname($php) . "/phpdbg"); } else { $phpdbg = null; } if ($phpdbg) { putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); } } if (getenv('TEST_PHPDBG_EXECUTABLE')) { $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); if ($phpdbg == 'auto') { $phpdbg = TEST_PHP_SRCDIR . '/sapi/phpdbg/phpdbg'; putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); } $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; } if (getenv('TEST_PHP_LOG_FORMAT')) { $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT')); } else { $log_format = 'LEODS'; } // Check whether a detailed log is wanted. if (getenv('TEST_PHP_DETAILED')) { $DETAILED = getenv('TEST_PHP_DETAILED'); } else { $DETAILED = 0; } junit_init(); if (getenv('SHOW_ONLY_GROUPS')) { $SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS')); } else { $SHOW_ONLY_GROUPS = []; } // Check whether user test dirs are requested. if (getenv('TEST_PHP_USER')) { $user_tests = explode(',', getenv('TEST_PHP_USER')); } else { $user_tests = []; } $exts_to_test = []; $ini_overwrites = [ 'output_handler=', 'open_basedir=', 'disable_functions=', 'output_buffering=Off', 'error_reporting=' . E_ALL, 'display_errors=1', 'display_startup_errors=1', 'log_errors=0', 'html_errors=0', 'track_errors=0', 'report_memleaks=1', 'report_zend_debug=0', 'docref_root=', 'docref_ext=.html', 'error_prepend_string=', 'error_append_string=', 'auto_prepend_file=', 'auto_append_file=', 'ignore_repeated_errors=0', 'precision=14', 'serialize_precision=-1', 'memory_limit=1024M', 'log_errors_max_len=0', 'opcache.fast_shutdown=0', 'opcache.file_update_protection=0', 'opcache.revalidate_freq=0', 'opcache.jit_hot_loop=1', 'opcache.jit_hot_func=1', 'opcache.jit_hot_return=1', 'opcache.jit_hot_side_exit=1', 'zend.assertions=1', 'zend.exception_ignore_args=0', 'zend.exception_string_param_max_len=15', 'short_open_tag=0', ]; $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); define('QA_SUBMISSION_PAGE', 'https://qa.php.net/buildtest-process.php'); define('QA_REPORTS_PAGE', 'https://qa.php.net/reports'); define('TRAVIS_CI', (bool) getenv('TRAVIS')); // Determine the tests to be run. $test_files = []; $redir_tests = []; $test_results = []; $PHP_FAILED_TESTS = [ 'BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => [] ]; // If parameters given assume they represent selected tests to run. $result_tests_file = false; $failed_tests_file = false; $pass_option_n = false; $pass_options = ''; $output_file = INIT_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt'; $just_save_results = false; $valgrind = null; $html_output = false; $html_file = null; $temp_source = null; $temp_target = null; $temp_urlbase = null; $conf_passed = null; $no_clean = false; $slow_min_ms = INF; $preload = false; $shuffle = false; $workers = null; $cfgtypes = ['show', 'keep']; $cfgfiles = ['skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem']; $cfg = []; foreach ($cfgtypes as $type) { $cfg[$type] = []; foreach ($cfgfiles as $file) { $cfg[$type][$file] = false; } } if (!isset($argc, $argv) || !$argc) { $argv = [__FILE__]; $argc = 1; } // swoole patch: show diff by default $cfg['show']['diff'] = true; $cfg['show']['mem'] = true; $cfg['show']['slow'] = true; $slow_min_ms = 1000; if (getenv('TEST_PHP_ARGS')) { $argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS'))); $argc = count($argv); } for ($i = 1; $i < $argc; $i++) { $is_switch = false; $switch = substr($argv[$i], 1, 1); $repeat = str_starts_with($argv[$i], '-'); while ($repeat) { if (!$is_switch) { $switch = substr($argv[$i], 1, 1); } $is_switch = true; if ($repeat) { foreach ($cfgtypes as $type) { if (str_starts_with($switch, '--' . $type)) { foreach ($cfgfiles as $file) { if ($switch == '--' . $type . '-' . $file) { $cfg[$type][$file] = true; $is_switch = false; break; } } } } } if (!$is_switch) { $is_switch = true; break; } $repeat = false; switch ($switch) { case 'j': $workers = substr($argv[$i], 2); if (!preg_match('/^\d+$/', $workers) || $workers == 0) { error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers"); } $workers = intval($workers, 10); // Don't use parallel testing infrastructure if there is only one worker. if ($workers === 1) { $workers = null; } break; case 'r': case 'l': $test_list = file($argv[++$i]); if ($test_list) { foreach ($test_list as $test) { $matches = []; if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { $redir_tests[] = [$matches[1], $matches[2]]; } else { if (strlen($test)) { $test_files[] = trim($test); } } } } if ($switch != 'l') { break; } $i--; // no break case 'w': $failed_tests_file = fopen($argv[++$i], 'w+t'); break; case 'a': $failed_tests_file = fopen($argv[++$i], 'a+t'); break; case 'W': $result_tests_file = fopen($argv[++$i], 'w+t'); break; case 'c': $conf_passed = $argv[++$i]; break; case 'd': $ini_overwrites[] = $argv[++$i]; break; case 'g': $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]); break; //case 'h' case '--keep-all': foreach ($cfgfiles as $file) { $cfg['keep'][$file] = true; } break; //case 'l' case 'm': $valgrind = new RuntestsValgrind($environment); break; case 'M': $valgrind = new RuntestsValgrind($environment, $argv[++$i]); break; case 'n': if (!$pass_option_n) { $pass_options .= ' -n'; } $pass_option_n = true; break; case 'e': $pass_options .= ' -e'; break; case '--preload': $preload = true; break; case '--no-clean': $no_clean = true; break; case 'p': $php = $argv[++$i]; putenv("TEST_PHP_EXECUTABLE=$php"); $environment['TEST_PHP_EXECUTABLE'] = $php; break; case 'P': $php = PHP_BINARY; putenv("TEST_PHP_EXECUTABLE=$php"); $environment['TEST_PHP_EXECUTABLE'] = $php; break; case 'q': putenv('NO_INTERACTION=1'); $environment['NO_INTERACTION'] = 1; break; //case 'r' case 's': $output_file = $argv[++$i]; $just_save_results = true; break; case '--set-timeout': $environment['TEST_TIMEOUT'] = $argv[++$i]; break; case '--show-all': foreach ($cfgfiles as $file) { $cfg['show'][$file] = true; } break; case '--show-slow': $slow_min_ms = $argv[++$i]; break; case '--temp-source': $temp_source = $argv[++$i]; break; case '--temp-target': $temp_target = $argv[++$i]; if ($temp_urlbase) { $temp_urlbase = $temp_target; } break; case '--temp-urlbase': $temp_urlbase = $argv[++$i]; break; case 'v': case '--verbose': $DETAILED = true; break; case 'x': $environment['SKIP_SLOW_TESTS'] = 1; break; case '--offline': $environment['SKIP_ONLINE_TESTS'] = 1; break; case '--shuffle': $shuffle = true; break; case '--asan': $environment['USE_ZEND_ALLOC'] = 0; $environment['USE_TRACKED_ALLOC'] = 1; $environment['SKIP_ASAN'] = 1; $environment['SKIP_PERF_SENSITIVE'] = 1; $lsanSuppressions = __DIR__ . '/azure/lsan-suppressions.txt'; if (file_exists($lsanSuppressions)) { $environment['LSAN_OPTIONS'] = 'suppressions=' . $lsanSuppressions . ':print_suppressions=0'; } break; //case 'w' case '-': // repeat check with full switch $switch = $argv[$i]; if ($switch != '-') { $repeat = true; } break; case '--html': $html_file = fopen($argv[++$i], 'wt'); $html_output = is_resource($html_file); break; case '--version': echo '$Id: ebcaabba02a5af6f5dc62dda027befb95385d5fc $' . "\n"; exit(2); default: echo "Illegal switch '$switch' specified!\n"; case 'h': case '-help': case '--help': show_usage(); exit(2); } } if (!$is_switch) { $testfile = realpath($argv[$i]); if (!$testfile && str_contains($argv[$i], '*') && function_exists('glob')) { if (str_ends_with($argv[$i], '.phpt')) { $pattern_match = glob($argv[$i]); } else { if (preg_match("/\*$/", $argv[$i])) { $pattern_match = glob($argv[$i] . '.phpt'); } else { die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); } } if (is_array($pattern_match)) { $test_files = array_merge($test_files, $pattern_match); } } else { if (is_dir($testfile)) { find_files($testfile); } else { if (str_ends_with($testfile, '.phpt')) { $test_files[] = $testfile; } else { die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); } } } } } // Default to PHP_BINARY as executable if (!isset($environment['TEST_PHP_EXECUTABLE'])) { $php = PHP_BINARY; putenv("TEST_PHP_EXECUTABLE=$php"); $environment['TEST_PHP_EXECUTABLE'] = $php; } if ($conf_passed && strlen($conf_passed)) { if (IS_WINDOWS) { $pass_options .= " -c " . escapeshellarg($conf_passed); } else { $pass_options .= " -c '" . realpath($conf_passed) . "'"; } } $test_files = array_unique($test_files); $test_files = array_merge($test_files, $redir_tests); // Run selected tests. $test_cnt = count($test_files); if ($test_cnt) { putenv('NO_INTERACTION=1'); verify_config(); write_information(); usort($test_files, "test_sort"); $start_time = time(); if (!$html_output) { echo "Running selected tests.\n"; } else { show_start($start_time); } $test_idx = 0; run_all_tests($test_files, $environment); $end_time = time(); if ($html_output) { show_end($end_time); } if ($failed_tests_file) { fclose($failed_tests_file); } if ($result_tests_file) { fclose($result_tests_file); } compute_summary(); if ($html_output) { fwrite($html_file, "
\n" . get_summary(false, true)); } echo "====================================================================="; echo get_summary(false, false); if ($html_output) { fclose($html_file); } if ($output_file != '' && $just_save_results) { save_or_mail_results(); } junit_save_xml(); if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) { exit(201); } return; } verify_config(); write_information(); // Compile a list of all test files (*.phpt). $test_files = array(); $exts_tested = count($exts_to_test); $exts_skipped = 0; $ignored_by_ext = 0; sort($exts_to_test); $test_dirs = array(); $optionals = array('Zend', 'tests', 'ext', 'sapi'); foreach ($optionals as $dir) { if (is_dir($dir)) { $test_dirs[] = $dir; } } // Convert extension names to lowercase foreach ($exts_to_test as $key => $val) { $exts_to_test[$key] = strtolower($val); } foreach ($test_dirs as $dir) { find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext'); } foreach ($user_tests as $dir) { find_files($dir, $dir == 'ext'); } $test_files = array_unique($test_files); usort($test_files, "test_sort"); $start_time = time(); show_start($start_time); $test_cnt = count($test_files); $test_idx = 0; run_all_tests($test_files, $environment); $end_time = time(); if ($failed_tests_file) { fclose($failed_tests_file); } if ($result_tests_file) { fclose($result_tests_file); } // Summarize results if (0 == count($test_results)) { echo "No tests were run.\n"; return; } compute_summary(); show_end($end_time); show_summary(); if ($html_output) { fclose($html_file); } save_or_mail_results(); junit_save_xml(); if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['LEAKED'])) { exit(202); } exit(0); } function get_shortname($file): array|string { return str_replace(TEST_PHP_SRCDIR . '/', '', $file); } function verify_config() { global $php; if (empty($php) || !file_exists($php)) { error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!'); } if (!is_executable($php)) { error("invalid PHP executable specified by TEST_PHP_EXECUTABLE = $php"); } } function write_information() { global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache; // Get info from php $info_file = __DIR__ . '/run-test-info.php'; @unlink($info_file); $php_info = ''; save_text($info_file, $php_info); $info_params = array(); settings2array($ini_overwrites, $info_params); $info_params = settings2params($info_params); $php_info = shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\""); define('TESTED_PHP_VERSION', shell_exec("$php -n -r \"echo PHP_VERSION;\"")); if ($php_cgi && $php != $php_cgi) { $php_info_cgi = shell_exec("$php_cgi $pass_options $info_params $no_file_cache -q \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; } else { $php_cgi_info = ''; } if ($phpdbg) { $phpdbg_info = shell_exec("$phpdbg $pass_options $info_params $no_file_cache -qrr \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; } else { $phpdbg_info = ''; } if (function_exists('opcache_invalidate')) { opcache_invalidate($info_file, true); } @unlink($info_file); // load list of enabled extensions save_text($info_file, ''); $exts_to_test = explode(',', shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\"")); // check for extensions that need special handling and regenerate $info_params_ex = array( 'session' => array('session.auto_start=0'), 'tidy' => array('tidy.clean_output=0'), 'zlib' => array('zlib.output_compression=Off'), 'xdebug' => array('xdebug.default_enable=0'), 'mbstring' => array('mbstring.func_overload=0'), ); foreach ($info_params_ex as $ext => $ini_overwrites_ex) { if (in_array($ext, $exts_to_test)) { $ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex); } } if (function_exists('opcache_invalidate')) { opcache_invalidate($info_file, true); } @unlink($info_file); // Write test context information. echo " ===================================================================== PHP : $php $php_info $php_cgi_info $phpdbg_info CWD : " . TEST_PHP_SRCDIR . " Extra dirs : "; foreach ($user_tests as $test_dir) { echo "{$test_dir}\n "; } echo " VALGRIND : " . ($valgrind ? $valgrind->getHeader() : 'Not used') . " ===================================================================== "; } function save_or_mail_results() { global $sum_results, $just_save_results, $failed_test_summary, $PHP_FAILED_TESTS, $php, $output_file; /* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */ if (!getenv('NO_INTERACTION') && !TRAVIS_CI) { $fp = fopen("php://stdin", "r+"); if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) { echo "\nYou may have found a problem in PHP."; } echo "\nThis report can be automatically sent to the PHP QA team at\n"; echo QA_REPORTS_PAGE . " and https://news.php.net/php.qa.reports\n"; echo "This gives us a better understanding of PHP's behavior.\n"; echo "If you don't want to send the report immediately you can choose\n"; echo "option \"s\" to save it. You can then email it to " . PHP_QA_EMAIL . " later.\n"; echo "Do you want to send this report now? [Yns]: "; flush(); $user_input = fgets($fp, 10); $just_save_results = (strtolower($user_input[0]) == 's'); } if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) { if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') { /* * Collect information about the host system for our report * Fetch phpinfo() output so that we can see the PHP environment * Make an archive of all the failed tests * Send an email */ if ($just_save_results) { $user_input = 's'; } /* Ask the user to provide an email address, so that QA team can contact the user */ if (TRAVIS_CI) { $user_email = 'travis at php dot net'; } elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) { echo "\nPlease enter your email address.\n(Your address will be mangled so that it will not go out on any\nmailinglist in plain text): "; flush(); $user_email = trim(fgets($fp, 1024)); $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email)); } $failed_tests_data = ''; $sep = "\n" . str_repeat('=', 80) . "\n"; $failed_tests_data .= $failed_test_summary . "\n"; $failed_tests_data .= get_summary(true, false) . "\n"; if ($sum_results['FAILED']) { foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) { $failed_tests_data .= $sep . $test_info['name'] . $test_info['info']; $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output'])); $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff'])); $failed_tests_data .= $sep . "\n\n"; } $status = "failed"; } else { $status = "success"; } $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep; $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n"; $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A'; if (!IS_WINDOWS) { /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */ if (getenv('PHP_AUTOCONF')) { $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version'); } else { $autoconf = shell_exec('autoconf --version'); } /* Always use the generated libtool - Mac OSX uses 'glibtool' */ $libtool = shell_exec(INIT_DIR . '/libtool --version'); /* Use shtool to find out if there is glibtool present (MacOSX) */ $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool'); if ($sys_libtool_path) { $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version'); } /* Try the most common flags for 'version' */ $flags = array('-v', '-V', '--version'); $cc_status = 0; foreach ($flags as $flag) { system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status); if ($cc_status == 0) { $compiler = shell_exec(getenv('CC') . " $flag 2>&1"); break; } } $ldd = shell_exec("ldd $php 2>/dev/null"); } $failed_tests_data .= "Autoconf:\n$autoconf\n"; $failed_tests_data .= "Bundled Libtool:\n$libtool\n"; $failed_tests_data .= "System Libtool:\n$sys_libtool\n"; $failed_tests_data .= "Compiler:\n$compiler\n"; $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n"; $failed_tests_data .= "Libraries:\n$ldd\n"; $failed_tests_data .= "\n"; if (isset($user_email)) { $failed_tests_data .= "User's E-mail: " . $user_email . "\n\n"; } $failed_tests_data .= $sep . "PHPINFO" . $sep; $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null'); if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) { file_put_contents($output_file, $failed_tests_data); if (!$just_save_results) { echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n"; } echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n"; } elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) { fwrite($fp, "\nThank you for helping to make PHP better.\n"); fclose($fp); } } } } function find_files($dir, $is_ext_dir = false, $ignore = false) { global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped; $o = opendir($dir) or error("cannot open directory: $dir"); while (($name = readdir($o)) !== false) { if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) { $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); if ($skip_ext) { $exts_skipped++; } find_files("{$dir}/{$name}", false, $ignore || $skip_ext); } // Cleanup any left-over tmp files from last run. if (str_ends_with($name, '.tmp')) { @unlink("$dir/$name"); continue; } // Otherwise we're only interested in *.phpt files. if (str_ends_with($name, '.phpt')) { if ($ignore) { $ignored_by_ext++; } else { $testfile = realpath("{$dir}/{$name}"); $test_files[] = $testfile; } } } closedir($o); } function test_name($name) { if (is_array($name)) { return $name[0] . ':' . $name[1]; } else { return $name; } } function test_sort($a, $b) { $a = test_name($a); $b = test_name($b); $ta = str_starts_with($a, TEST_PHP_SRCDIR . "/tests") ? 1 + (str_starts_with($a, TEST_PHP_SRCDIR . "/tests/run-test") ? 1 : 0) : 0; $tb = str_starts_with($b, TEST_PHP_SRCDIR . "/tests") ? 1 + (str_starts_with($b, TEST_PHP_SRCDIR . "/tests/run-test") ? 1 : 0) : 0; if ($ta == $tb) { return strcmp($a, $b); } else { return $tb - $ta; } } // // Send Email to QA Team // function mail_qa_team($data, $status = false) { $url_bits = parse_url(QA_SUBMISSION_PAGE); if ($proxy = getenv('http_proxy')) { $proxy = parse_url($proxy); $path = $url_bits['host'] . $url_bits['path']; $host = $proxy['host']; if (empty($proxy['port'])) { $proxy['port'] = 80; } $port = $proxy['port']; } else { $path = $url_bits['path']; $host = $url_bits['host']; $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port']; } $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data))); $data_length = strlen($data); $fs = fsockopen($host, $port, $errno, $errstr, 10); if (!$fs) { return false; } $php_version = urlencode(TESTED_PHP_VERSION); echo "\nPosting to " . QA_SUBMISSION_PAGE . "\n"; fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n"); fwrite($fs, "Host: " . $host . "\r\n"); fwrite($fs, "User-Agent: QA Browser 0.1\r\n"); fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n"); fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n"); fwrite($fs, $data); fwrite($fs, "\r\n\r\n"); fclose($fs); return 1; } // // Write the given text to a temporary file, and return the filename. // function save_text($filename, $text, $filename_copy = null) { global $DETAILED; if ($filename_copy && $filename_copy != $filename) { if (file_put_contents($filename_copy, $text) === false) { error("Cannot open file '" . $filename_copy . "' (save_text)"); } } if (file_put_contents($filename, $text) === false) { error("Cannot open file '" . $filename . "' (save_text)"); } if (1 < $DETAILED) echo " FILE $filename {{{ $text }}} "; } // // Write an error in a format recognizable to Emacs or MSVC. // function error_report($testname, $logname, $tested) { $testname = realpath($testname); $logname = realpath($logname); switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) { case 'MSVC': echo $testname . "(1) : $tested\n"; echo $logname . "(1) : $tested\n"; break; case 'EMACS': echo $testname . ":1: $tested\n"; echo $logname . ":1: $tested\n"; break; } } function system_with_timeout($commandline, $env = null, $stdin = null, $captureStdIn = true, $captureStdOut = true, $captureStdErr = true) { global $valgrind; $data = ''; $bin_env = array(); foreach ((array)$env as $key => $value) { $bin_env[$key] = $value; } $descriptorspec = array(); if ($captureStdIn) { $descriptorspec[0] = array('pipe', 'r'); } if ($captureStdOut) { $descriptorspec[1] = array('pipe', 'w'); } if ($captureStdErr) { $descriptorspec[2] = array('pipe', 'w'); } $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, array('suppress_errors' => true)); if (!$proc) { return false; } if ($captureStdIn) { if (!is_null($stdin)) { fwrite($pipes[0], $stdin); } fclose($pipes[0]); unset($pipes[0]); } $timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60); while (true) { /* hide errors from interrupted syscalls */ $r = $pipes; $w = null; $e = null; $n = @stream_select($r, $w, $e, $timeout); if ($n === false) { break; } else if ($n === 0) { /* timed out */ $data .= "\n ** ERROR: process timed out **\n"; proc_terminate($proc, 9); return $data; } else if ($n > 0) { // swoole patch: fix blocking read if (SWOOLE_IS_MUSL_LIBC) { if ($captureStdOut) { $line = fread($pipes[1], 8192); } elseif ($captureStdErr) { $line = fread($pipes[2], 8192); } else { $line = ''; } } else { $line = ''; if ($captureStdOut && in_array($pipes[1], $r)) { $line .= fread($pipes[1], 8192); } if ($captureStdErr && in_array($pipes[2], $r)) { $line .= fread($pipes[2], 8192); } } if (strlen($line) == 0) { /* EOF */ break; } $data .= $line; if (strlen($data) > SWOOLE_TEST_OUTPUT_MAX_SIZE) { echo "FATAL ERROR: The output content exceeds the maximum size\n"; echo "========================================================\n"; echo substr($data, 0, 65536); exit(253); } } } $stat = proc_get_status($proc); if ($stat['signaled']) { $data .= "\nTermsig=" . $stat['stopsig'] . "\n"; } if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) { $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n"; } proc_close($proc); return $data; } function run_all_tests($test_files, $env, $redir_tested = null) { global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx; // Parallel testing global $PHP_FAILED_TESTS, $workers, $workerID, $workerSock; /* Ignore -jN if there is only one file to analyze. */ if ($workers !== null && count($test_files) > 1 && !$workerID) { run_all_tests_parallel($test_files, $env, $redir_tested); return; } foreach ($test_files as $name) { if (is_array($name)) { $index = "# $name[1]: $name[0]"; if ($redir_tested) { $name = $name[0]; } } else if ($redir_tested) { $index = "# $redir_tested: $name"; } else { $index = $name; } $test_idx++; if ($workerID) { send_message($workerSock, [ "type" => "begin", "file" => $name, ]); } if ($workerID) { $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; ob_start(); } $result = run_test($php, $name, $env); if ($workerID) { $resultText = ob_get_clean(); } if (!is_array($name) && $result != 'REDIR') { if ($workerID) { send_message($workerSock, [ "type" => "test_result", "name" => $name, "index" => $index, "result" => $result, "text" => $resultText, "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS ]); continue; } $test_results[$index] = $result; if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { fwrite($failed_tests_file, "$index\n"); } if ($result_tests_file) { fwrite($result_tests_file, "$result\t$index\n"); } } } } /** The heart of parallel testing. */ function run_all_tests_parallel($test_files, $env, $redir_tested) { global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS, $valgrind; // The PHP binary running run-tests.php, and run-tests.php itself // This PHP executable is *not* necessarily the same as the tested version $thisPHP = PHP_BINARY; $thisScript = __FILE__; $workerProcs = []; $workerSocks = []; echo "=====================================================================\n"; echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n"; echo "=====================================================================\n"; // Each test may specify a list of conflict keys. While a test that conflicts with // key K is running, no other test that conflicts with K may run. Conflict keys are // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory. $dirConflictsWith = []; $fileConflictsWith = []; $sequentialTests = []; foreach ($test_files as $i => $file) { $contents = file_get_contents($file); if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) { $conflicts = parse_conflicts($matches[1]); } else { // Cache per-directory conflicts in a separate map, so we compute these only once. $dir = dirname($file); if (!isset($dirConflictsWith[$dir])) { $dirConflicts = []; if (file_exists($dir . '/CONFLICTS')) { $contents = file_get_contents($dir . '/CONFLICTS'); $dirConflicts = parse_conflicts($contents); } $dirConflictsWith[$dir] = $dirConflicts; } $conflicts = $dirConflictsWith[$dir]; } // For tests conflicting with "all", no other tests may run in parallel. We'll run these // tests separately at the end, when only one worker is left. if (in_array('all', $conflicts, true)) { $sequentialTests[] = $file; unset($test_files[$i]); } $fileConflictsWith[$file] = $conflicts; } // Some tests assume that they are executed in a certain order. We will be popping from // $test_files, so reverse its order here. This makes sure that order is preserved at least // for tests with a common conflict key. $test_files = array_reverse($test_files); // To discover parallelization issues it is useful to randomize the test order. if ($shuffle) { shuffle($test_files); } /* Don't start more workers than test files */ $workers = max(1, min($workers, count($test_files))); echo "Spawning workers… "; // We use sockets rather than STDIN/STDOUT for comms because on Windows, // those can't be non-blocking for some reason. $listenSock = stream_socket_server("tcp://127.0.0.1:0") or error("Couldn't create socket on localhost."); $sockName = stream_socket_get_name($listenSock, false); // PHP is terrible and returns IPv6 addresses not enclosed by [] $portPos = strrpos($sockName, ":"); $sockHost = substr($sockName, 0, $portPos); if (str_contains($sockHost, ":")) { $sockHost = "[$sockHost]"; } $sockPort = substr($sockName, $portPos + 1); $sockUri = "tcp://$sockHost:$sockPort"; $totalFileCount = count($test_files); for ($i = 1; $i <= $workers; $i++) { $proc = proc_open( $thisPHP . ' ' . escapeshellarg($thisScript), [], // Inherit our stdin, stdout and stderr $pipes, NULL, $_ENV + [ "TEST_PHP_WORKER" => $i, "TEST_PHP_URI" => $sockUri, ], [ "suppress_errors" => TRUE, 'create_new_console' => TRUE, ] ); if ($proc === FALSE) { kill_children($workerProcs); error("Failed to spawn worker $i"); } $workerProcs[$i] = $proc; $workerSock = stream_socket_accept($listenSock, 5); if ($workerSock === FALSE) { kill_children($workerProcs); error("Failed to accept connection from worker $i"); } $greeting = base64_encode(serialize([ "type" => "hello", "workerID" => $i, "GLOBALS" => $GLOBALS, "constants" => [ "INIT_DIR" => INIT_DIR, "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, "PHP_QA_EMAIL" => PHP_QA_EMAIL, "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, "TRAVIS_CI" => TRAVIS_CI ] ])) . "\n"; stream_set_timeout($workerSock, 5); if (fwrite($workerSock, $greeting) === FALSE) { kill_children($workerProcs); error("Failed to send greeting to worker $i."); } $rawReply = fgets($workerSock); if ($rawReply === FALSE) { kill_children($workerProcs); error("Failed to read greeting reply from worker $i."); } $reply = unserialize(base64_decode($rawReply)); if (!$reply || $reply["type"] !== "hello_reply" || $reply["workerID"] !== $i) { kill_children($workerProcs); error("Greeting reply from worker $i unexpected or could not be decoded: '$rawReply'"); } stream_set_timeout($workerSock, 0); stream_set_blocking($workerSock, FALSE); $workerSocks[$i] = $workerSock; echo "$i "; } echo "… done!\n"; echo "=====================================================================\n"; echo "\n"; $rawMessageBuffers = []; $testsInProgress = 0; // Map from conflict key to worker ID. $activeConflicts = []; // Tests waiting due to conflicts. Map from conflict key to array. $waitingTests = []; escape: while ($test_files || $sequentialTests || $testsInProgress > 0) { $toRead = array_values($workerSocks); $toWrite = NULL; $toExcept = NULL; if (stream_select($toRead, $toWrite, $toExcept, 10)) { foreach ($toRead as $workerSock) { $i = array_search($workerSock, $workerSocks); if ($i === FALSE) { kill_children($workerProcs); error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN."); } while (FALSE !== ($rawMessage = fgets($workerSock))) { // work around fgets truncating things if (($rawMessageBuffers[$i] ?? '') !== '') { $rawMessage = $rawMessageBuffers[$i] . $rawMessage; $rawMessageBuffers[$i] = ''; } if (!str_ends_with($rawMessage, "\n")) { $rawMessageBuffers[$i] = $rawMessage; continue; } $message = unserialize(base64_decode($rawMessage)); if (!$message) { kill_children($workerProcs); $stuff = fread($workerSock, 65536); error("Could not decode message from worker $i: '$rawMessage$stuff'"); } switch ($message["type"]) { case "tests_finished": $testsInProgress--; foreach ($activeConflicts as $key => $workerId) { if ($workerId === $i) { unset($activeConflicts[$key]); if (isset($waitingTests[$key])) { while ($test = array_pop($waitingTests[$key])) { $test_files[] = $test; } unset($waitingTests[$key]); } } } if (junit_enabled()) { junit_merge_results($message["junit"]); } // intentional fall-through case "ready": // Schedule sequential tests only once we are down to one worker. if (count($workerProcs) === 1 && $sequentialTests) { $test_files = array_merge($test_files, $sequentialTests); $sequentialTests = []; } // Batch multiple tests to reduce communication overhead. // - When valgrind is used, communication overhead is relatively small, // so just use a batch size of 1. // - If this is running a small enough number of tests, // reduce the batch size to give batches to more workers. $files = []; $maxBatchSize = $valgrind ? 1 : ($shuffle ? 4 : 32); $averageFilesPerWorker = max(1, (int)ceil($totalFileCount / count($workerProcs))); $batchSize = min($maxBatchSize, $averageFilesPerWorker); while (count($files) <= $batchSize && $file = array_pop($test_files)) { foreach ($fileConflictsWith[$file] as $conflictKey) { if (isset($activeConflicts[$conflictKey])) { $waitingTests[$conflictKey][] = $file; continue 2; } } $files[] = $file; } if ($files) { foreach ($files as $file) { foreach ($fileConflictsWith[$file] as $conflictKey) { $activeConflicts[$conflictKey] = $i; } } $testsInProgress++; send_message($workerSocks[$i], [ "type" => "run_tests", "test_files" => $files, "env" => $env, "redir_tested" => $redir_tested ]); } else { proc_terminate($workerProcs[$i]); unset($workerProcs[$i]); unset($workerSocks[$i]); goto escape; } break; case "begin": if (!$SHOW_ONLY_GROUPS) { show_test($test_idx, get_shortname($message['file'])); } break; case "test_result": list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests); } $test_idx++; if (!$SHOW_ONLY_GROUPS) { clear_show_test(); } echo $resultText; if (!is_array($name) && $result != 'REDIR') { $test_results[$index] = $result; if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { fwrite($failed_tests_file, "$index\n"); } if ($result_tests_file) { fwrite($result_tests_file, "$result\t$index\n"); } } break; case "error": kill_children($workerProcs); error("Worker $i reported error: $message[msg]"); break; case "php_error": kill_children($workerProcs); $error_consts = [ 'E_ERROR', 'E_WARNING', 'E_PARSE', 'E_NOTICE', 'E_CORE_ERROR', 'E_CORE_WARNING', 'E_COMPILE_ERROR', 'E_COMPILE_WARNING', 'E_USER_ERROR', 'E_USER_WARNING', 'E_USER_NOTICE', 'E_STRICT', // TODO Cleanup when removed from Zend Engine. 'E_RECOVERABLE_ERROR', 'E_USER_DEPRECATED' ]; $error_consts = array_combine(array_map('constant', $error_consts), $error_consts); error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]"); default: kill_children($workerProcs); error("Unrecognised message type '$message[type]' from worker $i"); } } } } } if (!$SHOW_ONLY_GROUPS) { clear_show_test(); } kill_children($workerProcs); if ($testsInProgress < 0) { error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN."); } } function send_message($stream, array $message) { $blocking = stream_get_meta_data($stream)["blocked"]; stream_set_blocking($stream, true); fwrite($stream, base64_encode(serialize($message)) . "\n"); stream_set_blocking($stream, $blocking); } function kill_children(array $children) { foreach ($children as $child) { if ($child) { proc_terminate($child); } } } function run_worker() { global $workerID, $workerSock; $sockUri = getenv("TEST_PHP_URI"); $workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri"); $greeting = fgets($workerSock); $greeting = unserialize(base64_decode($greeting)) or die("Could not decode greeting\n"); if ($greeting["type"] !== "hello" || $greeting["workerID"] !== $workerID) { error("Unexpected greeting of type $greeting[type] and for worker $greeting[workerID]"); } set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($workerSock) { if (error_reporting() & $errno) { send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + [ 'type' => 'php_error' ]); } return true; }); foreach ($greeting["GLOBALS"] as $var => $value) { if ($var !== "workerID" && $var !== "workerSock" && $var !== "GLOBALS") { $GLOBALS[$var] = $value; } } foreach ($greeting["constants"] as $const => $value) { define($const, $value); } send_message($workerSock, [ "type" => "hello_reply", "workerID" => $workerID ]); send_message($workerSock, [ "type" => "ready" ]); while (($command = fgets($workerSock))) { $command = unserialize(base64_decode($command)); switch ($command["type"]) { case "run_tests": run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]); send_message($workerSock, [ "type" => "tests_finished", "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null, ]); junit_init(); break; default: send_message($workerSock, [ "type" => "error", "msg" => "Unrecognised message type: $command[type]" ]); break 2; } } } // // Show file or result block // function show_file_block($file, $block, $section = null) { global $cfg; if ($cfg['show'][$file]) { if (is_null($section)) { $section = strtoupper($file); } static $max_length = 1024; if (strlen($block) > $max_length) { $block = substr($block, 0, $max_length) . '...'; } echo "\n========" . $section . "========\n"; echo rtrim($block); echo "\n========DONE========\n"; } } // // Run an individual test case. // function run_test($php, $file, $env) { global $log_format, $ini_overwrites, $PHP_FAILED_TESTS; global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; global $valgrind, $temp_source, $temp_target, $cfg, $environment; global $no_clean; global $SHOW_ONLY_GROUPS; global $no_file_cache; global $slow_min_ms; global $preload; // Parallel testing global $workerID; $temp_filenames = null; $org_file = $file; if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; } if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; } if (is_array($file)) { $file = $file[0]; } if ($DETAILED) echo " ================= TEST $file "; // Load the sections of the test file. $section_text = array('TEST' => ''); $fp = fopen($file, "rb") or error("Cannot open test file: $file"); $bork_info = null; if (!feof($fp)) { $line = fgets($fp); if ($line === false) { $bork_info = "cannot read test"; } } else { $bork_info = "empty test [$file]"; } if ($bork_info === null && strncmp('--TEST--', $line, 8)) { $bork_info = "tests must start with --TEST-- [$file]"; } $section = 'TEST'; $secfile = false; $secdone = false; while (!feof($fp)) { $line = fgets($fp); if ($line === false) { break; } // Match the beginning of a section. if (preg_match('/^--([_A-Z0-9]+)--/', $line, $r)) { $section = (string)$r[1]; if (isset($section_text[$section]) && $section_text[$section]) { $bork_info = "duplicated $section section"; } // check for unknown sections if (!in_array($section, array( 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', 'EXPECT_85', 'EXPECTF_85', 'EXPECTREGEX_85', // PHP 8.5+ 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', 'INI', 'ENV', 'EXTENSIONS', 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', ))) { $bork_info = 'Unknown section "' . $section . '"'; } $section_text[$section] = ''; $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; $secdone = false; continue; } // Add to the section text. if (!$secdone) { $section_text[$section] .= $line; } // End of actual test? if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { $secdone = true; } } // the redirect section allows a set of tests to be reused outside // a given test directory. if ($bork_info === null) { if (isset($section_text['REDIRECTTEST'])) { if ($IN_REDIRECT) { $bork_info = "Can't redirect a test from within a redirected test"; } } else { if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) { $bork_info = "missing section --FILE--"; } if (isset($section_text['FILEEOF'])) { $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); unset($section_text['FILEEOF']); } if (PHP_VERSION_ID >= 80500) { if (isset($section_text['EXPECT_85'])) { $section_text['EXPECT'] = $section_text['EXPECT_85']; unset($section_text['EXPECT_85']); } if (isset($section_text['EXPECTF_85'])) { $section_text['EXPECTF'] = $section_text['EXPECTF_85']; unset($section_text['EXPECTF_85']); } if (isset($section_text['EXPECTREGEX_85'])) { $section_text['EXPECTREGEX'] = $section_text['EXPECTREGEX_85']; unset($section_text['EXPECTREGEX_85']); } } foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { $key = $prefix . '_EXTERNAL'; if (isset($section_text[$key])) { // don't allow tests to retrieve files from anywhere but this subdirectory $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); if (file_exists($section_text[$key])) { $section_text[$prefix] = file_get_contents($section_text[$key]); unset($section_text[$key]); } else { $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); } } } if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; } } } fclose($fp); $shortname = get_shortname($file); $tested_file = $shortname; if ($bork_info !== null) { show_result("BORK", $bork_info, $tested_file); $PHP_FAILED_TESTS['BORKED'][] = array( 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", ); junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); return 'BORKED'; } if (isset($section_text['CAPTURE_STDIO'])) { $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; } else { $captureStdIn = true; $captureStdOut = true; $captureStdErr = true; } if ($captureStdOut && $captureStdErr) { $cmdRedirect = ' 2>&1'; } else { $cmdRedirect = ''; } $tested = trim($section_text['TEST']); /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ if (array_key_exists('CGI', $section_text) || !empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { if (isset($php_cgi)) { $php = $php_cgi . ' -C '; } else if (IS_WINDOWS && file_exists(dirname($php) . "/php-cgi.exe")) { $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; } else { if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; } else if (file_exists("./sapi/cgi/php-cgi")) { $php = realpath("./sapi/cgi/php-cgi") . ' -C '; } else if (file_exists(dirname($php) . "/php-cgi")) { $php = realpath(dirname($php) . "/php-cgi") . ' -C '; } else { show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); junit_init_suite(junit_get_suitename_for($shortname)); junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); return 'SKIPPED'; } } $uses_cgi = true; } /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ $extra_options = ''; if (array_key_exists('PHPDBG', $section_text)) { if (!isset($section_text['STDIN'])) { $section_text['STDIN'] = $section_text['PHPDBG'] . "\n"; } if (isset($phpdbg)) { $php = $phpdbg . ' -qIb'; // Additional phpdbg command line options for sections that need to // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. $extra_options = '-rr'; } else { show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); junit_init_suite(junit_get_suitename_for($shortname)); junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); return 'SKIPPED'; } } if (!$SHOW_ONLY_GROUPS && !$workerID) { show_test($test_idx, $shortname); echo PHP_EOL; } if (is_array($IN_REDIRECT)) { $temp_dir = $test_dir = $IN_REDIRECT['dir']; } else { $temp_dir = $test_dir = realpath(dirname($file)); } if ($temp_source && $temp_target) { $temp_dir = str_replace($temp_source, $temp_target, $temp_dir); } $main_file_name = basename($file, 'phpt'); $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff'; $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log'; $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp'; $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out'; $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem'; $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh'; $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; $preload_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'preload.php'; $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'post'; $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't'; if ($temp_source && $temp_target) { $temp_skipif .= 's'; $temp_file .= 's'; $temp_clean .= 's'; $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; if (!is_dir(dirname($copy_file))) { mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); } if (isset($section_text['FILE'])) { save_text($copy_file, $section_text['FILE']); } $temp_filenames = array( 'file' => $copy_file, 'diff' => $diff_filename, 'log' => $log_filename, 'exp' => $exp_filename, 'out' => $output_filename, 'mem' => $memcheck_filename, 'sh' => $sh_filename, 'php' => $temp_file, 'skip' => $temp_skipif, 'clean' => $temp_clean ); } if (is_array($IN_REDIRECT)) { $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); $tested_file = $tmp_relative_file; $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file); } // unlink old test results @unlink($diff_filename); @unlink($log_filename); @unlink($exp_filename); @unlink($output_filename); @unlink($memcheck_filename); @unlink($sh_filename); @unlink($temp_file); @unlink($test_file); @unlink($temp_skipif); @unlink($test_skipif); @unlink($tmp_post); @unlink($temp_clean); @unlink($test_clean); @unlink($preload_filename); // Reset environment from any previous test. $env['REDIRECT_STATUS'] = ''; $env['QUERY_STRING'] = ''; $env['PATH_TRANSLATED'] = ''; $env['SCRIPT_FILENAME'] = ''; $env['REQUEST_METHOD'] = ''; $env['CONTENT_TYPE'] = ''; $env['CONTENT_LENGTH'] = ''; $env['TZ'] = ''; if (!empty($section_text['ENV'])) { foreach (explode("\n", trim($section_text['ENV'])) as $e) { $e = explode('=', trim($e), 2); if (!empty($e[0]) && isset($e[1])) { $env[$e[0]] = $e[1]; } } } // Default ini settings $ini_settings = $workerID ? array('opcache.cache_id' => "worker$workerID") : array(); // Additional required extensions if (array_key_exists('EXTENSIONS', $section_text)) { $ext_params = array(); settings2array($ini_overwrites, $ext_params); $ext_params = settings2params($ext_params); $ext_dir = shell_exec("$php $pass_options $extra_options $ext_params -d display_errors=0 -r \"echo ini_get('extension_dir');\""); $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); $loaded = explode(",", shell_exec("$php $pass_options $extra_options $ext_params -d display_errors=0 -r \"echo implode(',', get_loaded_extensions());\"")); $ext_prefix = IS_WINDOWS ? "php_" : ""; foreach ($extensions as $req_ext) { if (!in_array($req_ext, $loaded)) { if ($req_ext == 'opcache') { $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; } else { $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; } } } } // additional ini overwrites //$ini_overwrites[] = 'setting=value'; settings2array($ini_overwrites, $ini_settings); $orig_ini_settings = settings2params($ini_settings); // Any special ini settings // these may overwrite the test defaults... if (array_key_exists('INI', $section_text)) { $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']); $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null'; $section_text['INI'] = preg_replace('/{MAIL:(\S+)}/', $replacement, $section_text['INI']); settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); } $ini_settings = settings2params($ini_settings); $env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings; // Check if test should be skipped. $info = ''; $warn = false; if (array_key_exists('SKIPIF', $section_text)) { if (trim($section_text['SKIPIF'])) { show_file_block('skip', $section_text['SKIPIF']); save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif); $extra = !IS_WINDOWS ? "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; if ($valgrind) { $env['USE_ZEND_ALLOC'] = '0'; $env['ZEND_DONT_UNLOAD_MODULES'] = 1; } junit_start_timer($shortname); $output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0 \"$test_skipif\"", $env); $output = trim($output); junit_finish_timer($shortname); if (!$cfg['keep']['skip']) { @unlink($test_skipif); } if (!strncasecmp('skip', $output, 4)) { if (preg_match('/^skip\s*(.+)/i', $output, $m)) { show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); } else { show_result('SKIP', $tested, $tested_file, '', $temp_filenames); } if (!$cfg['keep']['skip']) { @unlink($test_skipif); } $message = !empty($m[1]) ? $m[1] : ''; junit_mark_test_as('SKIP', $shortname, $tested, null, $message); return 'SKIPPED'; } if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) { $info = " (info: $m[1])"; } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) { $warn = true; /* only if there is a reason */ $info = " (warn: $m[1])"; } elseif (!strncasecmp('xfail', $output, 5)) { // Pretend we have an XFAIL section $section_text['XFAIL'] = ltrim(substr($output, 5)); } elseif ($output !== '') { show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); $PHP_FAILED_TESTS['BORKED'][] = array( 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$output [$file]", ); junit_mark_test_as('BORK', $shortname, $tested, null, $output); return 'BORKED'; } } } if (!extension_loaded("zlib") && (array_key_exists("GZIP_POST", $section_text) || array_key_exists("DEFLATE_POST", $section_text))) { $message = "ext/zlib required"; show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); junit_mark_test_as('SKIP', $shortname, $tested, null, $message); return 'SKIPPED'; } if (isset($section_text['REDIRECTTEST'])) { $test_files = array(); $IN_REDIRECT = eval($section_text['REDIRECTTEST']); $IN_REDIRECT['via'] = "via [$shortname]\n\t"; $IN_REDIRECT['dir'] = realpath(dirname($file)); $IN_REDIRECT['prefix'] = trim($section_text['TEST']); if (!empty($IN_REDIRECT['TESTS'])) { if (is_array($org_file)) { $test_files[] = $org_file[1]; } else { $GLOBALS['test_files'] = $test_files; find_files($IN_REDIRECT['TESTS']); foreach ($GLOBALS['test_files'] as $f) { $test_files[] = array($f, $file); } } $test_cnt += count($test_files) - 1; $test_idx--; show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file); // set up environment $redirenv = array_merge($environment, $IN_REDIRECT['ENV']); $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR; usort($test_files, "test_sort"); run_all_tests($test_files, $redirenv, $tested); show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file); // a redirected test never fails $IN_REDIRECT = false; junit_mark_test_as('PASS', $shortname, $tested); return 'REDIR'; } else { $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; show_result("BORK", $bork_info, '', '', $temp_filenames); $PHP_FAILED_TESTS['BORKED'][] = array( 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", ); } } if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) { if (is_array($org_file)) { $file = $org_file[0]; } $bork_info = "Redirected test did not contain redirection info"; show_result("BORK", $bork_info, '', '', $temp_filenames); $PHP_FAILED_TESTS['BORKED'][] = array( 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', 'info' => "$bork_info [$file]", ); junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); return 'BORKED'; } // We've satisfied the preconditions - run the test! if (isset($section_text['FILE'])) { show_file_block('php', $section_text['FILE'], 'TEST'); save_text($test_file, $section_text['FILE'], $temp_file); } else { $test_file = $temp_file = ""; } if (array_key_exists('GET', $section_text)) { $query_string = trim($section_text['GET']); } else { $query_string = ''; } $env['REDIRECT_STATUS'] = '1'; if (empty($env['QUERY_STRING'])) { $env['QUERY_STRING'] = $query_string; } if (empty($env['PATH_TRANSLATED'])) { $env['PATH_TRANSLATED'] = $test_file; } if (empty($env['SCRIPT_FILENAME'])) { $env['SCRIPT_FILENAME'] = $test_file; } if (array_key_exists('COOKIE', $section_text)) { $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); } else { $env['HTTP_COOKIE'] = ''; } $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; if ($preload && !empty($test_file)) { save_text($preload_filename, "wrapCommand($cmd, $memcheck_filename, str_contains($test_file, "pcre")); } if ($DETAILED) echo " CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " CONTENT_TYPE = " . $env['CONTENT_TYPE'] . " PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . " QUERY_STRING = " . $env['QUERY_STRING'] . " REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . " REQUEST_METHOD = " . $env['REQUEST_METHOD'] . " SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . " HTTP_COOKIE = " . $env['HTTP_COOKIE'] . " COMMAND $cmd "; junit_start_timer($shortname); $hrtime = hrtime(); $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); junit_finish_timer($shortname); $hrtime = hrtime(); $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime; if ($time >= $slow_min_ms * 1000000) { $PHP_FAILED_TESTS['SLOW'][] = array( 'name' => $file, 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 'output' => '', 'diff' => '', 'info' => $time / 1000000000, ); } if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { if (trim($section_text['CLEAN'])) { show_file_block('clean', $section_text['CLEAN']); save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); if (!$no_clean) { $clean_params = array(); settings2array($ini_overwrites, $clean_params); $clean_params = settings2params($clean_params); $extra = !IS_WINDOWS ? "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; system_with_timeout("$extra $php $pass_options $extra_options -q $clean_params $no_file_cache \"$test_clean\"", $env); } if (!$cfg['keep']['clean']) { @unlink($test_clean); } } } @unlink($preload_filename); $leaked = false; $passed = false; if ($valgrind) { // leak check // swoole patch: leak log $leaked = file_get_contents($memcheck_filename); $leaked = preg_replace('/==\d+== Conditional jump[\s\S]+?==\d+== (?:\n|$)/', '', $leaked); if (empty(preg_replace('/\s/', '', $leaked))) { $leaked = false; @unlink($memcheck_filename); } } // Does the output match what is expected? $output = preg_replace("/\r\n/", "\n", trim($out)); /* when using CGI, strip the headers from the output */ $headers = array(); if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { $output = trim($match[2]); $rh = preg_split("/[\n\r]+/", $match[1]); foreach ($rh as $line) { if (str_contains($line, ':')) { $line = explode(':', $line, 2); $headers[trim($line[0])] = trim($line[1]); } } } $failed_headers = false; if (isset($section_text['EXPECTHEADERS'])) { $want = array(); $wanted_headers = array(); $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); foreach ($lines as $line) { if (str_contains($line, ':')) { $line = explode(':', $line, 2); $want[trim($line[0])] = trim($line[1]); $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); } } $output_headers = array(); foreach ($want as $k => $v) { if (isset($headers[$k])) { $output_headers[] = $k . ': ' . $headers[$k]; } if (!isset($headers[$k]) || $headers[$k] != $v) { $failed_headers = true; } } ksort($wanted_headers); $wanted_headers = implode("\n", $wanted_headers); ksort($output_headers); $output_headers = implode("\n", $output_headers); } show_file_block('out', $output); if ($preload) { $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output)); } if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { if (isset($section_text['EXPECTF'])) { $wanted = trim($section_text['EXPECTF']); } else { $wanted = trim($section_text['EXPECTREGEX']); } show_file_block('exp', $wanted); $wanted_re = preg_replace('/\r\n/', "\n", $wanted); if (isset($section_text['EXPECTF'])) { // do preg_quote, but miss out any %r delimited sections $temp = ""; $r = "%r"; $startOffset = 0; $length = strlen($wanted_re); while ($startOffset < $length) { $start = strpos($wanted_re, $r, $startOffset); if ($start !== false) { // we have found a start tag $end = strpos($wanted_re, $r, $start + 2); if ($end === false) { // unbalanced tag, ignore it. $end = $start = $length; } } else { // no more %r sections $start = $end = $length; } // quote a non re portion of the string $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/'); // add the re unquoted. if ($end > $start) { $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; } $startOffset = $end + 2; } $wanted_re = $temp; // Stick to basics $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); $wanted_re = str_replace('%a', '.+', $wanted_re); $wanted_re = str_replace('%A', '.*', $wanted_re); $wanted_re = str_replace('%w', '\s*', $wanted_re); $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); $wanted_re = str_replace('%d', '\d+', $wanted_re); $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); $wanted_re = str_replace('%c', '.', $wanted_re); // %f allows two points "-.0.0" but that is the best *simple* expression } /* DEBUG YOUR REGEX HERE var_dump($wanted_re); print(str_repeat('=', 80) . "\n"); var_dump($output); */ if (preg_match("/^$wanted_re\$/s", $output)) { $passed = true; if (!$cfg['keep']['php']) { @unlink($test_file); } @unlink($tmp_post); if (!$leaked && !$failed_headers) { if (isset($section_text['XFAIL'])) { $warn = true; $info = " (warn: XFAIL section but test passes)"; } else if (isset($section_text['XLEAK'])) { $warn = true; $info = " (warn: XLEAK section but test passes)"; } else { show_result("PASS", $tested, $tested_file, '', $temp_filenames); junit_mark_test_as('PASS', $shortname, $tested); return 'PASSED'; } } } } else { $wanted = trim($section_text['EXPECT']); $wanted = preg_replace('/\r\n/', "\n", $wanted); show_file_block('exp', $wanted); // compare and leave on success if (!strcmp($output, $wanted)) { $passed = true; if (!$cfg['keep']['php']) { @unlink($test_file); } @unlink($tmp_post); if (!$leaked && !$failed_headers) { if (isset($section_text['XFAIL'])) { $warn = true; $info = " (warn: XFAIL section but test passes)"; } elseif (isset($section_text['XLEAK'])) { $warn = true; $info = " (warn: XLEAK section but test passes)"; } else { show_result("PASS", $tested, $tested_file, '', $temp_filenames); junit_mark_test_as('PASS', $shortname, $tested); return 'PASSED'; } } } $wanted_re = null; } // Test failed so we need to report details. if ($failed_headers) { $passed = false; $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted; $output = $output_headers . "\n--HEADERS--\n" . $output; if (isset($wanted_re)) { $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re; } } if ($leaked) { $restype[] = isset($section_text['XLEAK']) ? 'XLEAK' : 'LEAK'; } if ($warn) { $restype[] = 'WARN'; } if (!$passed) { if (isset($section_text['XFAIL'])) { $restype[] = 'XFAIL'; $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); } else if (isset($section_text['XLEAK'])) { $restype[] = 'XLEAK'; $info = ' XLEAK REASON: ' . rtrim($section_text['XLEAK']); } else { $restype[] = 'FAIL'; } } if (!$passed) { // write .exp if (str_contains($log_format, 'E') && file_put_contents($exp_filename, $wanted) === false) { error("Cannot create expected test output - $exp_filename"); } // write .out if (str_contains($log_format, 'O') && file_put_contents($output_filename, $output) === false) { error("Cannot create test output - $output_filename"); } // write .diff $diff = generate_diff($wanted, $wanted_re, $output); if (is_array($IN_REDIRECT)) { $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); $diff = "# original source file: $orig_shortname\n" . $diff; } show_file_block('diff', $diff); // swoole patch: clear port if (preg_match('/bind\([^:]+?:(?\d+?)\)/', $diff, $matches)) { $port = $matches['port']; @shell_exec("lsof -i:{$port} | grep LISTEN | awk '{print $2}' | xargs kill -9 > /dev/null 2>&1"); } if (str_contains($log_format, 'D') && file_put_contents($diff_filename, $diff) === false) { error("Cannot create test diff - $diff_filename"); } // write .sh if (str_contains($log_format, 'S') && file_put_contents($sh_filename, "#!/bin/sh {$cmd} ") === false) { error("Cannot create test shell script - $sh_filename"); } chmod($sh_filename, 0755); // write .log if (str_contains($log_format, 'L') && file_put_contents($log_filename, " ---- EXPECTED OUTPUT $wanted ---- ACTUAL OUTPUT $output ---- FAILED ") === false) { error("Cannot create test log - $log_filename"); error_report($file, $log_filename, $tested); } } if ($valgrind && $leaked && $cfg["show"]["mem"]) { show_file_block('mem', file_get_contents($memcheck_filename)); } show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames); foreach ($restype as $type) { $PHP_FAILED_TESTS[$type . 'ED'][] = array( 'name' => $file, 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 'output' => $output_filename, 'diff' => $diff_filename, 'info' => $info, ); } $diff = empty($diff) ? '' : preg_replace('/\e/', '', $diff); junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff); return $restype[0] . 'ED'; } function comp_line($l1, $l2, $is_reg) { if ($is_reg) { return preg_match('/^' . $l1 . '$/s', $l2); } else { return !strcmp($l1, $l2); } } function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps) { $equal = 0; while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { $idx1++; $idx2++; $equal++; $steps--; } if (--$steps > 0) { $eq1 = 0; $st = $steps / 2; for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); if ($eq > $eq1) { $eq1 = $eq; } } $eq2 = 0; $st = $steps; for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); if ($eq > $eq2) { $eq2 = $eq; } } if ($eq1 > $eq2) { $equal += $eq1; } else if ($eq2 > 0) { $equal += $eq2; } } return $equal; } function generate_array_diff($ar1, $ar2, $is_reg, $w) { $idx1 = 0; $cnt1 = @count($ar1); $idx2 = 0; $cnt2 = @count($ar2); $diff = array(); $old1 = array(); $old2 = array(); while ($idx1 < $cnt1 && $idx2 < $cnt2) { if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { $idx1++; $idx2++; continue; } else { $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10); $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10); if ($c1 > $c2) { $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; } else if ($c2 > 0) { $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; } else { $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; } } } reset($old1); $k1 = key($old1); $l1 = -2; reset($old2); $k2 = key($old2); $l2 = -2; while ($k1 !== null || $k2 !== null) { if ($k1 == $l1 + 1 || $k2 === null) { $l1 = $k1; $diff[] = current($old1); $k1 = next($old1) ? key($old1) : null; } else if ($k2 == $l2 + 1 || $k1 === null) { $l2 = $k2; $diff[] = current($old2); $k2 = next($old2) ? key($old2) : null; } else if ($k1 < $k2) { $l1 = $k1; $diff[] = current($old1); $k1 = next($old1) ? key($old1) : null; } else { $l2 = $k2; $diff[] = current($old2); $k2 = next($old2) ? key($old2) : null; } } while ($idx1 < $cnt1) { $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; } while ($idx2 < $cnt2) { $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; } return $diff; } function generate_diff($wanted, $wanted_re, $output) { $w = explode("\n", $wanted); $o = explode("\n", $output); $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); return implode(PHP_EOL, $diff); } function error($message) { echo "ERROR: {$message}\n"; exit(240); } function settings2array($settings, &$ini_settings) { foreach ($settings as $setting) { if (str_contains($setting, '=')) { $setting = explode("=", $setting, 2); $name = trim($setting[0]); $value = trim($setting[1]); if ($name == 'extension' || $name == 'zend_extension') { if (!isset($ini_settings[$name])) { $ini_settings[$name] = array(); } $ini_settings[$name][] = $value; } else { $ini_settings[$name] = $value; } } } } function settings2params($ini_settings) { $settings = ''; foreach($ini_settings as $name => $value) { if (is_array($value)) { foreach($value as $val) { $val = addslashes($val); $settings .= " -d \"$name=$val\""; } } else { if (IS_WINDOWS && !empty($value) && $value[0] == '"') { $len = strlen($value); if ($value[$len - 1] == '"') { $value[0] = "'"; $value[$len - 1] = "'"; } } else { $value = addslashes($value); } $settings .= " -d \"$name=$value\""; } } return $settings; } function compute_summary() { global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; $n_total = count($test_results); $n_total += $ignored_by_ext; $sum_results = array( 'PASSED' => 0, 'WARNED' => 0, 'SKIPPED' => 0, 'FAILED' => 0, 'BORKED' => 0, 'LEAKED' => 0, 'XFAILED' => 0, 'XLEAKED' => 0 ); foreach ($test_results as $v) { $sum_results[$v]++; } $sum_results['SKIPPED'] += $ignored_by_ext; $percent_results = array(); foreach ($sum_results as $v => $n) { $percent_results[$v] = (100.0 * $n) / $n_total; } } function get_summary($show_ext_summary, $show_html) { global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind; $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED']; if ($x_total) { $x_warned = (100.0 * $sum_results['WARNED']) / $x_total; $x_failed = (100.0 * $sum_results['FAILED']) / $x_total; $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total; $x_xleaked = (100.0 * $sum_results['XLEAKED']) / $x_total; $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total; $x_passed = (100.0 * $sum_results['PASSED']) / $x_total; } else { $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = $x_xleaked = 0; } $summary = ''; if ($show_html) { $summary .= "
\n";
    }

    if ($show_ext_summary) {
        $summary .= '
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped    : ' . sprintf('%4d', $exts_skipped) . '
Exts tested     : ' . sprintf('%4d', $exts_tested) . '
---------------------------------------------------------------------
';
    }

    $summary .= '
Number of tests : ' . sprintf('%4d', $n_total) . '          ' . sprintf('%8d', $x_total);

    if ($sum_results['BORKED']) {
        $summary .= '
Tests borked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------';
    }

    $summary .= '
Tests skipped   : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' --------
Tests warned    : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . '
Tests failed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed);

    if ($sum_results['XFAILED']) {
        $summary .= '
Expected fail   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed);
    }

    if ($valgrind) {
        $summary .= '
Tests leaked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked);
        if ($sum_results['XLEAKED']) {
            $summary .= '
Expected leak   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked);
        }
    }

    $summary .= '
Tests passed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
---------------------------------------------------------------------
Time taken      : ' . sprintf('%4d seconds', $end_time - $start_time) . '
=====================================================================
';
    $failed_test_summary = '';

    if (count($PHP_FAILED_TESTS['SLOW'])) {
        usort($PHP_FAILED_TESTS['SLOW'], function ($a, $b) {
            return $a['info'] < $b['info'] ? 1 : -1;
        });

        $failed_test_summary .= '
=====================================================================
SLOW TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) {
            $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n";
        }
        $failed_test_summary .= "=====================================================================\n";
    }

    if (count($PHP_FAILED_TESTS['XFAILED'])) {
        $failed_test_summary .= '
=====================================================================
EXPECTED FAILED TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
        }
        $failed_test_summary .= "=====================================================================\n";
    }

    if (count($PHP_FAILED_TESTS['BORKED'])) {
        $failed_test_summary .= '
=====================================================================
BORKED TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['info'] . "\n";
        }

        $failed_test_summary .= "=====================================================================\n";
    }

    if (count($PHP_FAILED_TESTS['FAILED'])) {
        $failed_test_summary .= '
=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
        }
        $failed_test_summary .= "=====================================================================\n";
    }
    if (count($PHP_FAILED_TESTS['WARNED'])) {
        $failed_test_summary .= '
=====================================================================
WARNED TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
        }

        $failed_test_summary .= "=====================================================================\n";
    }

    if (count($PHP_FAILED_TESTS['LEAKED'])) {
        $failed_test_summary .= '
=====================================================================
LEAKED TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
        }

        $failed_test_summary .= "=====================================================================\n";
    }

    if (count($PHP_FAILED_TESTS['XLEAKED'])) {
        $failed_test_summary .= '
=====================================================================
EXPECTED LEAK TEST SUMMARY
---------------------------------------------------------------------
';
        foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) {
            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
        }

        $failed_test_summary .= "=====================================================================\n";
    }

    if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) {
        $summary .= $failed_test_summary;
    }

    if ($show_html) {
        $summary .= "
"; } return $summary; } function show_start($start_time) { global $html_output, $html_file; if ($html_output) { fwrite($html_file, "

Time Start: " . date('Y-m-d H:i:s', $start_time) . "

\n"); fwrite($html_file, "
\n"); } echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; } function show_end($end_time) { global $html_output, $html_file; if ($html_output) { fwrite($html_file, "
\n"); fwrite($html_file, "

Time End: " . date('Y-m-d H:i:s', $end_time) . "

\n"); } echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; } function show_summary() { global $html_output, $html_file; if ($html_output) { fwrite($html_file, "
\n" . get_summary(true, true)); } echo get_summary(true, false); } function show_redirect_start($tests, $tested, $tested_file) { global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; if ($html_output) { fwrite($html_file, "---> $tests ($tested [$tested_file]) begin\n"); } if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; } else { clear_show_test(); } } function show_redirect_ends($tests, $tested, $tested_file) { global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; if ($html_output) { fwrite($html_file, "---> $tests ($tested [$tested_file]) done\n"); } if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { echo "REDIRECT $tests ($tested [$tested_file]) done\n"; } else { clear_show_test(); } } function show_test($test_idx, $shortname) { global $test_cnt; global $line_length; // swoole patch: pretty output $str = "TEST $test_idx/$test_cnt [$shortname]"; $line_length = strlen($str); echo $str; flush(); } function clear_show_test() { global $line_length; // Parallel testing global $workerID; if (!$workerID) { // Write over the last line to avoid random trailing chars on next echo echo str_repeat(" ", intval($line_length)), "\r"; } } function parse_conflicts(string $text) : array { // Strip comments $text = preg_replace('/#.*/', '', $text); return array_map('trim', explode("\n", trim($text))); } function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null) { global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS; // swoole patch: color print switch ($result) { case 'PASS': $color = SWOOLE_COLOR_GREEN; break; case 'LEAK': $result = 'ERROR'; $color = SWOOLE_COLOR_MAGENTA; break; case 'BORK': $color = SWOOLE_COLOR_BLUE; break; case 'SKIP': $color = SWOOLE_COLOR_YELLOW; break; default: $color = SWOOLE_COLOR_RED; } $result = "\033[3{$color}m[$result]\033[0m"; if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { echo "$result $tested [$tested_file] $extra\n"; } else if (!$SHOW_ONLY_GROUPS) { clear_show_test(); } if ($html_output) { if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) { $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']); $tested = "$tested"; } if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) { if (empty($extra)) { $extra = "skipif"; } $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']); $extra = "$extra"; } else if (empty($extra)) { $extra = " "; } if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) { $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']); $diff = "diff"; } else { $diff = " "; } if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) { $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']); $mem = "leaks"; } else { $mem = " "; } fwrite( $html_file, "" . "$result" . "$tested" . "$extra" . "$diff" . "$mem" . "\n" ); } } function junit_init() { // Check whether a junit log is wanted. global $workerID; $JUNIT = getenv('TEST_PHP_JUNIT'); if (empty($JUNIT)) { $GLOBALS['JUNIT'] = false; return; } if ($workerID) { $fp = null; } else if (!$fp = fopen($JUNIT, 'w')) { error("Failed to open $JUNIT for writing."); } $GLOBALS['JUNIT'] = array( 'fp' => $fp, 'name' => 'PHP', 'test_total' => 0, 'test_pass' => 0, 'test_fail' => 0, 'test_error' => 0, 'test_skip' => 0, 'test_warn' => 0, 'execution_time' => 0, 'suites' => array(), 'files' => array() ); } function junit_save_xml() { global $JUNIT; if (!junit_enabled()) return; $xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL; $xml .= sprintf( '' . PHP_EOL, $JUNIT['name'], $JUNIT['test_total'], $JUNIT['test_fail'], $JUNIT['test_error'], $JUNIT['test_skip'], $JUNIT['execution_time'] ); $xml .= junit_get_suite_xml(); $xml .= ''; fwrite($JUNIT['fp'], $xml); } function junit_get_suite_xml($suite_name = '') { global $JUNIT; $result = ""; foreach ($JUNIT['suites'] as $suite_name => $suite) { $result .= sprintf( '' . PHP_EOL, $suite['name'], $suite['test_total'], $suite['test_fail'], $suite['test_error'], $suite['test_skip'], $suite['execution_time'] ); if (!empty($suite_name)) { foreach ($suite['files'] as $file) { $result .= $JUNIT['files'][$file]['xml']; } } $result .= '' . PHP_EOL; } return $result; } function junit_enabled() { global $JUNIT; return !empty($JUNIT); } /** * @param array|string $type * @param string $file_name * @param string $test_name * @param int|string $time * @param string $message * @param string $details * @return void */ function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') { global $JUNIT; if (!junit_enabled()) return; $suite = junit_get_suitename_for($file_name); junit_suite_record($suite, 'test_total'); $time = $time ?? junit_get_timer($file_name); junit_suite_record($suite, 'execution_time', $time); $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) { return sprintf('[[0x%02x]]', ord($c[0])); }, $escaped_details); $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES); $JUNIT['files'][$file_name]['xml'] = "\n"; if (is_array($type)) { $output_type = $type[0] . 'ED'; $temp = array_intersect(array('XFAIL', 'XLEAK', 'FAIL', 'WARN'), $type); $type = reset($temp); } else { $output_type = $type . 'ED'; } if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) { junit_suite_record($suite, 'test_pass'); } elseif ('BORK' == $type) { junit_suite_record($suite, 'test_error'); $JUNIT['files'][$file_name]['xml'] .= "\n"; } elseif ('SKIP' == $type) { junit_suite_record($suite, 'test_skip'); $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; } elseif ('WARN' == $type) { junit_suite_record($suite, 'test_warn'); $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; } elseif ('FAIL' == $type) { junit_suite_record($suite, 'test_fail'); $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; } else { junit_suite_record($suite, 'test_error'); $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; } $JUNIT['files'][$file_name]['xml'] .= "\n"; } function junit_suite_record($suite, $param, $value = 1) { global $JUNIT; $JUNIT[$param] += $value; $JUNIT['suites'][$suite][$param] += $value; } function junit_get_timer($file_name) { global $JUNIT; if (!junit_enabled()) return 0; if (isset($JUNIT['files'][$file_name]['total'])) { return number_format($JUNIT['files'][$file_name]['total'], 4); } return 0; } function junit_start_timer($file_name) { global $JUNIT; if (!junit_enabled()) return; if (!isset($JUNIT['files'][$file_name]['start'])) { $JUNIT['files'][$file_name]['start'] = microtime(true); $suite = junit_get_suitename_for($file_name); junit_init_suite($suite); $JUNIT['suites'][$suite]['files'][$file_name] = $file_name; } } function junit_get_suitename_for($file_name) { return junit_path_to_classname(dirname($file_name)); } function junit_path_to_classname($file_name) { global $JUNIT; if (!junit_enabled()) return ''; $ret = $JUNIT['name']; $_tmp = array(); // lookup whether we're in the PHP source checkout $max = 5; if (is_file($file_name)) { $dir = dirname(realpath($file_name)); } else { $dir = realpath($file_name); } do { array_unshift($_tmp, basename($dir)); $chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h"; $dir = dirname($dir); } while (!file_exists($chk) && --$max > 0); if (file_exists($chk)) { if ($max) { array_shift($_tmp); } foreach ($_tmp as $p) { $ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p); } return $ret; } return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name); } function junit_init_suite($suite_name) { global $JUNIT; if (!junit_enabled()) return; if (!empty($JUNIT['suites'][$suite_name])) { return; } $JUNIT['suites'][$suite_name] = array( 'name' => $suite_name, 'test_total' => 0, 'test_pass' => 0, 'test_fail' => 0, 'test_error' => 0, 'test_skip' => 0, 'test_warn' => 0, 'files' => array(), 'execution_time' => 0, ); } function junit_finish_timer($file_name) { global $JUNIT; if (!junit_enabled()) return; if (!isset($JUNIT['files'][$file_name]['start'])) { error("Timer for $file_name was not started!"); } if (!isset($JUNIT['files'][$file_name]['total'])) { $JUNIT['files'][$file_name]['total'] = 0; } $start = $JUNIT['files'][$file_name]['start']; $JUNIT['files'][$file_name]['total'] += microtime(true) - $start; unset($JUNIT['files'][$file_name]['start']); } function junit_merge_results($junit) { global $JUNIT; $JUNIT['test_total'] += $junit['test_total']; $JUNIT['test_pass'] += $junit['test_pass']; $JUNIT['test_fail'] += $junit['test_fail']; $JUNIT['test_error'] += $junit['test_error']; $JUNIT['test_skip'] += $junit['test_skip']; $JUNIT['test_warn'] += $junit['test_warn']; $JUNIT['execution_time'] += $junit['execution_time']; $JUNIT['files'] += $junit['files']; foreach ($junit['suites'] as $name => $suite) { if (!isset($JUNIT['suites'][$name])) { $JUNIT['suites'][$name] = $suite; continue; } $SUITE =& $JUNIT['suites'][$name]; $SUITE['test_total'] += $suite['test_total']; $SUITE['test_pass'] += $suite['test_pass']; $SUITE['test_fail'] += $suite['test_fail']; $SUITE['test_error'] += $suite['test_error']; $SUITE['test_skip'] += $suite['test_skip']; $SUITE['test_warn'] += $suite['test_warn']; $SUITE['execution_time'] += $suite['execution_time']; $SUITE['files'] += $suite['files']; } } class RuntestsValgrind { protected $version = ''; protected $header = ''; protected $version_3_3_0 = false; protected $version_3_8_0 = false; protected $tool = null; public function getVersion() { return $this->version; } public function getHeader() { return $this->header; } public function __construct(array $environment, string $tool = 'memcheck') { $this->tool = $tool; $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); if (!$header) { error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n". "Please check if Valgrind is installed and the tool is named correctly."); } $count = 0; $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count); if ($count != 1) { error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed."); } $this->version = $version; $this->header = sprintf( "%s (%s)", trim($header), $this->tool); $this->version_3_3_0 = version_compare($version, '3.3.0', '>='); $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); } public function wrapCommand($cmd, $memcheck_filename, $check_all) { $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes"; if ($check_all) { $vcmd .= ' --smc-check=all'; } // swoole patch $vcmd .= ' --undef-value-errors=no'; /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */ if ($this->version_3_8_0) { /* valgrind 3.3.0+ doesn't have --log-file-exactly option */ return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd"; } elseif ($this->version_3_3_0) { return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd"; } else { return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd"; } } } // swoole patch: color const define('SWOOLE_TEST_TIMEOUT', file_exists('/.cienv') ? 10 : 30); define('SWOOLE_COLOR_RED', 1); define('SWOOLE_COLOR_GREEN', 2); define('SWOOLE_COLOR_YELLOW', 3); define('SWOOLE_COLOR_BLUE', 4); define('SWOOLE_COLOR_MAGENTA', 5); define('SWOOLE_COLOR_CYAN', 6); define('SWOOLE_COLOR_WHITE', 7); define('SWOOLE_TEST_OUTPUT_MAX_SIZE', 64 * 1024 * 1024); // swoole patch: libc check define('SWOOLE_IS_MUSL_LIBC', !empty(shell_exec("ldd 2>&1 | grep -i musl"))); main(); ================================================ FILE: tests/start.sh ================================================ #!/bin/bash __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) clear_php() { ps -A | grep \.php$ | grep -v phpstorm | grep -v php-fpm | awk '{print $1}' | xargs kill -9 > /dev/null 2>&1 } ## before tests clear_php if [ `ulimit -n` -le 16384 ]; then ulimit -n 16384 > /dev/null 2>&1 fi # run tests if [ -z "${TEST_PHP_EXECUTABLE}" ]; then export TEST_PHP_EXECUTABLE=`which php` fi if [ -z "${1}" ]; then glob="swoole_*" else if [ "${1}x" = "basex" ]; then glob="\ swoole_atomic \ swoole_event \ swoole_function \ swoole_global \ swoole_process \ swoole_process_pool \ swoole_table \ swoole_coroutine* \ swoole_channel_coro \ swoole_client_coro \ swoole_http_client_coro \ swoole_http2_client_coro \ swoole_server \ swoole_http_server \ swoole_websocket_server \ swoole_redis_server \ swoole_socket_coro \ swoole_runtime" if [ ${#} -gt 1 ]; then args="${@}" args="${args#* }" glob="${args} ${glob}" fi else glob="$@" if [ "${glob:0:6}" = "tests/" ]; then glob="${glob#tests/}" fi fi fi if [ $? -eq 0 ]; then PHPT=1 ${TEST_PHP_EXECUTABLE} -d "memory_limit=1024m" ${__DIR__}/run-tests ${glob} fi # after tests clear_php rm -f /tmp/swoole.log > /dev/null 2>&1 ================================================ FILE: tests/swoole_atomic/atomic.phpt ================================================ --TEST-- swoole_atomic: add/sub/get/cmpset --SKIPIF-- --FILE-- add(199), 200); Assert::same($atomic->sub(35), 165); Assert::same($atomic->get(), 165); Assert::assert($atomic->cmpset(165, 1)); Assert::assert(!$atomic->cmpset(1555, 0)); ?> --EXPECT-- ================================================ FILE: tests/swoole_atomic/dtor_in_child.phpt ================================================ --TEST-- swoole_atomic: destruct objects in child processe --SKIPIF-- --FILE-- wait(); echo "Child OK\n"; exit(0); }); $p->start(); usleep(200000); echo "Master OK\n"; $atomic->wakeup(1); $status = Swoole\Process::wait(); ?> --EXPECT-- Master OK Child OK ================================================ FILE: tests/swoole_atomic/multi_wakeup.phpt ================================================ --TEST-- swoole_atomic: multi wakeup --SKIPIF-- --FILE-- setWaitTimeout(5); $s = microtime(true); $pm->parentFunc = function () use ($pm, $s) { echo "WAKED\n"; $s = microtime(true) - $s; Assert::assert($s < 1); usleep(1000); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set(['worker_num' => 4, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { Assert::assert($pm->wakeup()); }); $server->on('request', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- WAKED ================================================ FILE: tests/swoole_atomic/wait.phpt ================================================ --TEST-- swoole_atomic: wakeup & wait --SKIPIF-- --FILE-- wait(); echo "Child OK\n"; }); $p->start(); $workers[$i] = $p; } usleep(200000); echo "Master OK\n"; $atomic->wakeup(N); for ($i = 0; $i < 4; $i++) { $status = Swoole\Process::wait(); } ?> --EXPECT-- Master OK Child OK Child OK Child OK Child OK ================================================ FILE: tests/swoole_atomic/wait_and_wakeup.phpt ================================================ --TEST-- swoole_atomic: wait & wakeup --SKIPIF-- --FILE-- wakeup(), $atomic->get()); $atomic = new Swoole\Atomic(0); var_dump($atomic->wakeup(), $atomic->get()); $atomic = new Swoole\Atomic(0); var_dump($atomic->wait(1), $atomic->get()); $atomic = new Swoole\Atomic(1); var_dump($atomic->wait(1), $atomic->get()); ?> --EXPECT-- bool(true) int(1) bool(true) int(1) bool(false) int(0) bool(true) int(0) ================================================ FILE: tests/swoole_atomic/wait_ex.phpt ================================================ --TEST-- swoole_atomic: wakeup & wait ex --SKIPIF-- --FILE-- set(['worker_num' => 4, 'log_file' => '/dev/null']); $server->on('WorkerStart', function (Swoole\Http\Server $server, int $wid) use ($atomic) { if ($wid === 0) { sleep(1); $atomic->wakeup(3); sleep(1); $server->shutdown(); } else { $s = microtime(true); echo "sleeping...\n"; $atomic->wait(-1); echo "I'm wide awake\n"; $s = microtime(true) - $s; time_approximate(1, $s, 0.2); } }); $server->on('Request', function () { }); $server->start(); ?> --EXPECT-- sleeping... sleeping... sleeping... I'm wide awake I'm wide awake I'm wide awake ================================================ FILE: tests/swoole_channel_coro/1.phpt ================================================ --TEST-- swoole_channel_coro: consumer first with select mode --SKIPIF-- --FILE-- pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ?> --EXPECT-- select yield push start main end select resume res: true pop [#0] ret:'data-0' push [#0] ret:true pop [#1] ret:'data-1' push [#1] ret:true pop [#2] ret:'data-2' push [#2] ret:true pop [#3] ret:'data-3' push [#3] ret:true pop [#4] ret:'data-4' push [#4] ret:true pop [#5] ret:'data-5' push [#5] ret:true pop [#6] ret:'data-6' push [#6] ret:true pop [#7] ret:'data-7' push [#7] ret:true pop [#8] ret:'data-8' push [#8] ret:true pop [#9] ret:'data-9' push [#9] ret:true ================================================ FILE: tests/swoole_channel_coro/10.phpt ================================================ --TEST-- swoole_channel_coro: 10 --SKIPIF-- --FILE-- push("data"); }); Assert::same($chan->pop(0.001), "data"); Assert::false($chan->pop(0.001)); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/2.phpt ================================================ --TEST-- swoole_channel_coro: consumer first without select mode --SKIPIF-- --FILE-- pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; Swoole\Event::wait(); ?> --EXPECT-- pop start push start pop [#0] ret:'data-0' push [#0] ret:true pop [#1] ret:'data-1' push [#1] ret:true pop [#2] ret:'data-2' push [#2] ret:true pop [#3] ret:'data-3' push [#3] ret:true pop [#4] ret:'data-4' push [#4] ret:true pop [#5] ret:'data-5' push [#5] ret:true pop [#6] ret:'data-6' push [#6] ret:true pop [#7] ret:'data-7' push [#7] ret:true pop [#8] ret:'data-8' push [#8] ret:true pop [#9] ret:'data-9' push [#9] ret:true main end ================================================ FILE: tests/swoole_channel_coro/3.phpt ================================================ --TEST-- swoole_channel_coro: product first with select mode --SKIPIF-- --FILE-- push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { $read_list = [$c1]; $write_list = null; echo "select yield\n"; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($read_list) { foreach($read_list as $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); echo "main end\n"; ?> --EXPECT-- push start push [#0] ret:true select yield select resume res: true pop [#0] ret:'data-0' main end pop [#1] ret:'data-1' push [#1] ret:true pop [#2] ret:'data-2' push [#2] ret:true pop [#3] ret:'data-3' push [#3] ret:true pop [#4] ret:'data-4' push [#4] ret:true pop [#5] ret:'data-5' push [#5] ret:true pop [#6] ret:'data-6' push [#6] ret:true pop [#7] ret:'data-7' push [#7] ret:true pop [#8] ret:'data-8' push [#8] ret:true pop [#9] ret:'data-9' push [#9] ret:true ================================================ FILE: tests/swoole_channel_coro/4.phpt ================================================ --TEST-- swoole_channel_coro: product first without select mode --SKIPIF-- --FILE-- push("data-$i"); echo "push [#$i] ret:" . var_export($ret, 1) . "\n"; } }); go(function () use ($c1, $num) { echo "pop start\n"; for ($i = 0; $i < $num; $i++) { $ret = $c1->pop(); echo "pop [#$i] ret:" . var_export($ret, 1) . "\n"; } }); echo "main end\n"; Swoole\Event::wait(); ?> --EXPECT-- push start push [#0] ret:true pop start push [#1] ret:true pop [#0] ret:'data-0' push [#2] ret:true pop [#1] ret:'data-1' push [#3] ret:true pop [#2] ret:'data-2' push [#4] ret:true pop [#3] ret:'data-3' push [#5] ret:true pop [#4] ret:'data-4' push [#6] ret:true pop [#5] ret:'data-5' push [#7] ret:true pop [#6] ret:'data-6' push [#8] ret:true pop [#7] ret:'data-7' push [#9] ret:true pop [#8] ret:'data-8' pop [#9] ret:'data-9' main end ================================================ FILE: tests/swoole_channel_coro/5.phpt ================================================ --TEST-- swoole_channel_coro: push with sleep --SKIPIF-- --FILE-- pop(); echo "pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("data-$i"); echo "push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ?> --EXPECT-- select yield push start main end select resume res: true pop [#0] ret:'data-0' push [#0] ret:true pop [#1] ret:'data-1' push [#1] ret:true start sleep end sleep pop [#2] ret:'data-2' push [#2] ret:true pop [#3] ret:'data-3' push [#3] ret:true pop [#4] ret:'data-4' push [#4] ret:true pop [#5] ret:'data-5' push [#5] ret:true pop [#6] ret:'data-6' push [#6] ret:true pop [#7] ret:'data-7' push [#7] ret:true pop [#8] ret:'data-8' push [#8] ret:true pop [#9] ret:'data-9' push [#9] ret:true ================================================ FILE: tests/swoole_channel_coro/6.phpt ================================================ --TEST-- swoole_channel_coro: push with sleep --SKIPIF-- --FILE-- pop(); var_dump($result); } echo 'pop over!'. PHP_EOL; }); go(function () use ($chan){ echo "coro2 start\n"; $retval = [2,23,2]; $chan->push($retval); echo "coro2 end\n"; }); go(function () use ($chan){ echo "coro3 start\n"; $eee = "hello word"; $chan->push($eee); echo "coro3 end\n"; }); echo 'master end' . PHP_EOL; Swoole\Event::wait(); ?> --EXPECT-- coro1 start coro2 start array(3) { [0]=> int(2) [1]=> int(23) [2]=> int(2) } coro2 end coro3 start string(10) "hello word" pop over! coro3 end master end ================================================ FILE: tests/swoole_channel_coro/7.phpt ================================================ --TEST-- swoole_channel_coro: push and pop --SKIPIF-- --FILE-- push($i); }); }; go(function ()use ($chan){ $bool = true; for ($i = 0; $i < 10; $i++) { $data = $chan->pop(); if ($data===false) { $bool = false; } var_dump($data); } }); Swoole\Event::wait(); ?> --EXPECT-- int(0) int(1) int(2) int(3) int(4) int(5) int(6) int(7) int(8) int(9) ================================================ FILE: tests/swoole_channel_coro/8.phpt ================================================ --TEST-- swoole_channel_coro: pop priority --SKIPIF-- --FILE-- pop(), 1)."\n"; }); }; Swoole\Timer::after(500, function () use ($chan) { for ($i = 0; $i < 6; $i++) { $chan->push($i); } }); go(function ()use ($chan){ for ($i = 0; $i < 4; $i++) { $chan->push($i); } for ($i = 0; $i < 6; $i++) { echo "[pop & push]".var_export($chan->pop($i), 1)."\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- [pop]0 [pop]1 [pop]2 [pop]3 [pop & push]0 [pop & push]1 [pop & push]2 [pop & push]3 [pop & push]4 [pop & push]5 ================================================ FILE: tests/swoole_channel_coro/9.phpt ================================================ --TEST-- swoole_channel_coro: pop priority --SKIPIF-- --FILE-- push($i); }); }; Swoole\Timer::after(200, function () use ($chan) { for ($i = 0; $i < 6; $i++) { $chan->push($i); } }); go(function () use ($chan){ for ($i = 0; $i < 2; $i++) { echo "[read]".var_export($chan->pop(), 1)."\n"; } for ($i = 0; $i < 8; $i++) { echo "[read & write]".var_export($chan->pop(), 1)."\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- [read]0 [read]1 [read & write]2 [read & write]3 [read & write]0 [read & write]1 [read & write]2 [read & write]3 [read & write]4 [read & write]5 ================================================ FILE: tests/swoole_channel_coro/basic.phpt ================================================ --TEST-- swoole_channel_coro: coro channel --SKIPIF-- --FILE-- push($i); echo "$i\n"; } }); co::create(function () use ($chan) { for($i=0; $i<10; $i++) { $data = $chan->pop(); Assert::assert(!empty($data)); } }); Swoole\Event::wait(); ?> --EXPECT-- 1 2 3 4 5 6 7 8 9 10 ================================================ FILE: tests/swoole_channel_coro/benchmark.phpt ================================================ --TEST-- swoole_channel_coro: 100W benchmark --SKIPIF-- --FILE-- enqueue($i); } $i = MAX_LOOPS; while (!$queue->isEmpty()) { Assert::same((--$i), $queue->dequeue()); } $time['splQueue'] = microtime(true) - $time['splQueue']; // channel go(function () use (&$time) { $time['channel_raw'] = microtime(true); $chan = new Chan(MAX_LOOPS); for ($i = MAX_LOOPS; $i--;) { $chan->push($i); } $i = MAX_LOOPS; while (!$chan->isEmpty()) { Assert::same((--$i), $chan->pop()); } $time['channel_raw'] = microtime(true) - $time['channel_raw']; }); // channel with scheduler $chan = new Chan; go(function () use (&$time, $chan) { co::sleep(0.1); $time['channel_scheduler'] = microtime(true); for ($i = MAX_LOOPS; $i--;) { $chan->push($i); } $chan->push(false); }); go(function () use (&$time, $chan) { $i = MAX_LOOPS; while (($ret = $chan->pop()) !== false) { Assert::same((--$i), $ret); } $time['channel_scheduler'] = microtime(true) - $time['channel_scheduler']; $chan->close(); }); Swoole\Event::wait(); var_dump($time); $diff = $time['channel_raw'] - $time['splQueue']; var_dump($diff); ?> --EXPECTF-- array(3) { ["splQueue"]=> float(%f) ["channel_raw"]=> float(%f) ["channel_scheduler"]=> float(%f) } float(%f) ================================================ FILE: tests/swoole_channel_coro/blocking_timeout.phpt ================================================ --TEST-- swoole_channel_coro: blocking and timeout --SKIPIF-- --FILE-- 0]); $chan = new chan(); go(function () use ($chan){ $data = $chan->pop(0.5); Assert::assert($data); $data = $chan->pop(0.5); Assert::false($data); }); go(function () use ($chan) { sleep(1); $chan->push(999955); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/bug_1947.phpt ================================================ --TEST-- swoole_channel_coro: channel by return value --SKIPIF-- --FILE-- pop(0.001); Assert::false($ret); }); function foo() { $chan = new \Swoole\Coroutine\Channel(); go(function () use ($chan) { // nothing }); return $chan; } ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/bug_clear_timer.phpt ================================================ --TEST-- swoole_channel_coro: the bug about timeout timer --SKIPIF-- --FILE-- push('foo'); // }); // go(function () use ($chan) { // $read = [ // $chan // ]; // $write = []; // $ret = chan::select($read, $write, 0.1); // Assert::true($ret); // Assert::same(count($read), 1); // }); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/chan_select_timeout.phpt ================================================ --TEST-- swoole_channel_coro: coro channel select timeout --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/chan_stats.phpt ================================================ --TEST-- swoole_channel_coro: coro channel stats --SKIPIF-- --FILE-- push(1); $chan->push(2); $chan->push("hello world"); $chan->push([1, 3, 4, 4, 6]); Assert::same($chan->stats()['queue_num'], 4); $chan->pop(); $chan->pop(); $chan->pop(); $chan->pop(); Assert::same($chan->stats()['queue_num'], 0); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/close.phpt ================================================ --TEST-- swoole_channel_coro: coro channel stats --SKIPIF-- --FILE-- pop(); if ($data == false) { break; } print("chan get data :$data\n"); } print("chan exit\n"); }); $frame = '11'; $chan->push($frame); co::sleep(0.2); print("chan close " . json_encode($chan->stats()) . "\n"); $chan->close(); co::sleep(0.2); print("chan END\n"); unset($a['1']); }); Swoole\Event::wait(); ?> --EXPECT-- chan get data :11 chan close {"consumer_num":1,"producer_num":0,"queue_num":0} chan exit chan END ================================================ FILE: tests/swoole_channel_coro/coro_wait.phpt ================================================ --TEST-- swoole_channel_coro: coroutine wait --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(!empty($data)); $json = json_decode($data, true); Assert::assert(is_array($json)); Assert::true(isset($json['www.qq.com'])); Assert::greaterThan($json['www.qq.com'], 1024); Assert::true(isset($json['www.163.com'])); Assert::greaterThan($json['www.163.com'], 1024); $pm->kill(); }); Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) { $chan = new chan(2); go(function () use ($chan) { $cli = new Client('www.qq.com', 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ 'Host' => 'www.qq.com', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); if ($ret) { $chan->push(['www.qq.com' => strlen($cli->body)]); } else { $chan->push(['www.qq.com' => 0]); } }); go(function () use ($chan) { $cli = new Client('www.163.com', 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ 'Host' => 'www.163.com', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); if ($ret) { $chan->push(['www.163.com' => strlen($cli->body)]); } else { $chan->push(['www.163.com' => 0]); } }); $result = []; for ($i = 0; $i < 2; $i++) { $result += $chan->pop(); } $resp->end(json_encode($result)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_channel_coro/discard.phpt ================================================ --TEST-- swoole_channel_coro: discard --SKIPIF-- --FILE-- add(function () { $chan = new Channel(1); $chan->push(1, -1); var_dump('push success'); $chan->push(1, -1); }); $scheduler->start(); var_dump('scheduler end'); ?> --EXPECTF-- string(12) "push success" string(13) "scheduler end" [%s] WARNING Channel::~Channel() (ERRNO 10003): channel is destroyed, 1 producers will be discarded ================================================ FILE: tests/swoole_channel_coro/fibonacci.phpt ================================================ --TEST-- swoole_channel_coro: fibonacci --SKIPIF-- --FILE-- push($a); } if ($read_list) { $ret = $c2->pop(); if ($ret === 1) { return 1; } } } }); } $num = 10; go(function () use ($c1, $c2, $num) { for ($i = 0; $i < $num; $i ++) { $ret = $c1->pop(); echo "fibonacci @$i $ret\n"; } $c2->push(1); }); fibonacci($c1, $c2); ?> --EXPECT-- fibonacci @0 1 fibonacci @1 1 fibonacci @2 2 fibonacci @3 3 fibonacci @4 5 fibonacci @5 8 fibonacci @6 13 fibonacci @7 21 fibonacci @8 34 fibonacci @9 55 ================================================ FILE: tests/swoole_channel_coro/http2.phpt ================================================ --TEST-- swoole_channel_coro: http2 mode --SKIPIF-- --FILE-- push(new stdClass()); // response }); go(function () use ($send, $rand) { $data = $send->pop(); if (Assert::assert($data === $rand)) { co::sleep(0.001); $send->push(true); // send ok } }); $ret = $send->push($rand); Assert::assert($ret); $response = $recv->pop(); Assert::isInstanceOf($response, stdClass::class); } }); } ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/hybird_chan.phpt ================================================ --TEST-- swoole_channel_coro: hybird channel --SKIPIF-- --FILE-- push("chan2-$i"); echo "chan 2 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$num) { $read_list = [$c1]; $write_list = null; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($read_list) { foreach($read_list as $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); echo "chan1 pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "chan1 push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("chan1-$i"); echo "chan1 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c2,$num) { echo "chan2 pop start\n"; for ($i=0;$i<$num;$i++) { $ret = $c2->pop(); echo "chan2 pop [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ?> --EXPECT-- chan 2 push [#0] ret:true chan 2 push [#1] ret:true chan 2 push [#2] ret:true chan 2 push [#3] ret:true chan 2 push [#4] ret:true chan 2 push [#5] ret:true chan 2 push [#6] ret:true chan 2 push [#7] ret:true chan 2 push [#8] ret:true chan 2 push [#9] ret:true chan1 push start chan2 pop start chan2 pop [#0] ret:'chan2-0' chan2 pop [#1] ret:'chan2-1' chan2 pop [#2] ret:'chan2-2' chan2 pop [#3] ret:'chan2-3' chan2 pop [#4] ret:'chan2-4' chan2 pop [#5] ret:'chan2-5' chan2 pop [#6] ret:'chan2-6' chan2 pop [#7] ret:'chan2-7' chan2 pop [#8] ret:'chan2-8' chan2 pop [#9] ret:'chan2-9' main end select resume res: true chan1 pop [#0] ret:'chan1-0' chan1 push [#0] ret:true chan1 pop [#1] ret:'chan1-1' chan1 push [#1] ret:true start sleep end sleep chan1 pop [#2] ret:'chan1-2' chan1 push [#2] ret:true chan1 pop [#3] ret:'chan1-3' chan1 push [#3] ret:true chan1 pop [#4] ret:'chan1-4' chan1 push [#4] ret:true chan1 pop [#5] ret:'chan1-5' chan1 push [#5] ret:true chan1 pop [#6] ret:'chan1-6' chan1 push [#6] ret:true chan1 pop [#7] ret:'chan1-7' chan1 push [#7] ret:true chan1 pop [#8] ret:'chan1-8' chan1 push [#8] ret:true chan1 pop [#9] ret:'chan1-9' chan1 push [#9] ret:true ================================================ FILE: tests/swoole_channel_coro/hybird_chan2.phpt ================================================ --TEST-- swoole_channel_coro: hybird channel select --SKIPIF-- --FILE-- push("chan2-$i"); echo "chan 2 push [#$i] ret:".var_export($ret,1)."\n"; } }); go(function () use ($c1,$c2,$num) { $ori_list = $read_list = [$c1,$c2]; $write_list = null; $result = chan::select($read_list, $write_list, 2); echo "select resume res: ".var_export($result,1)."\n"; if ($ori_list) { foreach ($ori_list as $chan => $ch) { for ($i=0;$i<$num;$i++) { $ret = $ch->pop(); $chan_id = $chan + 1; echo "chan{$chan_id} pop [#$i] ret:".var_export($ret,1)."\n"; } } } }); go(function () use ($c1,$num) { echo "chan1 push start\n"; for ($i=0;$i<$num;$i++) { if ($i == 2) { echo "start sleep\n"; co:sleep(1); echo "end sleep\n"; } $ret = $c1->push("chan1-$i"); echo "chan1 push [#$i] ret:".var_export($ret,1)."\n"; } }); echo "main end\n"; ?> --EXPECT-- chan 2 push [#0] ret:true chan 2 push [#1] ret:true chan 2 push [#2] ret:true chan 2 push [#3] ret:true chan 2 push [#4] ret:true chan 2 push [#5] ret:true chan 2 push [#6] ret:true chan 2 push [#7] ret:true chan 2 push [#8] ret:true chan 2 push [#9] ret:true select resume res: true chan1 push start main end chan1 pop [#0] ret:'chan1-0' chan1 push [#0] ret:true chan1 pop [#1] ret:'chan1-1' chan1 push [#1] ret:true start sleep end sleep chan1 pop [#2] ret:'chan1-2' chan1 push [#2] ret:true chan1 pop [#3] ret:'chan1-3' chan1 push [#3] ret:true chan1 pop [#4] ret:'chan1-4' chan1 push [#4] ret:true chan1 pop [#5] ret:'chan1-5' chan1 push [#5] ret:true chan1 pop [#6] ret:'chan1-6' chan1 push [#6] ret:true chan1 pop [#7] ret:'chan1-7' chan1 push [#7] ret:true chan1 pop [#8] ret:'chan1-8' chan1 push [#8] ret:true chan1 pop [#9] ret:'chan1-9' chan2 pop [#0] ret:'chan2-0' chan2 pop [#1] ret:'chan2-1' chan2 pop [#2] ret:'chan2-2' chan2 pop [#3] ret:'chan2-3' chan2 pop [#4] ret:'chan2-4' chan2 pop [#5] ret:'chan2-5' chan2 pop [#6] ret:'chan2-6' chan2 pop [#7] ret:'chan2-7' chan2 pop [#8] ret:'chan2-8' chan2 pop [#9] ret:'chan2-9' chan1 push [#9] ret:true ================================================ FILE: tests/swoole_channel_coro/hybird_chan3.phpt ================================================ --TEST-- swoole_channel_coro: hybird channel select --SKIPIF-- --FILE-- push($i); } }); for ($i=0; $i < $coro_num; $i++) { go(function () use ($i, $chan, $coro_num) { for ($j = 0; $j < $coro_num; $j ++) { $c = $chan->pop(); echo "@".$j . "->coro:" . $i . " pop chan id :" . var_export($c, 1) . PHP_EOL; co::sleep(0.2); $ret = $chan->push($c); } }); } Swoole\Event::wait(); ?> --EXPECTF-- @0->coro:%d pop chan id :%d @0->coro:%d pop chan id :%d @0->coro:%d pop chan id :%d @0->coro:%d pop chan id :%d @1->coro:%d pop chan id :%d @1->coro:%d pop chan id :%d @1->coro:%d pop chan id :%d @1->coro:%d pop chan id :%d @2->coro:%d pop chan id :%d @2->coro:%d pop chan id :%d @2->coro:%d pop chan id :%d @2->coro:%d pop chan id :%d @3->coro:%d pop chan id :%d @3->coro:%d pop chan id :%d @3->coro:%d pop chan id :%d @3->coro:%d pop chan id :%d ================================================ FILE: tests/swoole_channel_coro/lock.phpt ================================================ --TEST-- swoole_channel_coro: lock --SKIPIF-- --FILE-- push(true); $this->chan = $chan; } function lock() { return $this->chan->pop(); } function unlock() { return $this->chan->push(true); } } class Test { static $num = 2; static function process(CoLock $lock) { co::sleep(0.001); //这里需要操作全局对象,有可能会有上下文的问题 //使用 chan 实现协程锁 $lock->lock(); if (Test::$num > 0) { co::sleep(0.02); Test::$num--; $lock->unlock(); } else { $lock->unlock(); echo "fail\n"; } } } go(function () { $lock = new CoLock; $n = 3; while ($n--) { go('Test::process', $lock); } }); Swoole\Event::wait(); ?> --EXPECT-- fail ================================================ FILE: tests/swoole_channel_coro/no_ctor.phpt ================================================ --TEST-- swoole_channel_coro: no ctor --SKIPIF-- --FILE-- pop(); }); }); Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/pool.phpt ================================================ --TEST-- swoole_channel_coro: connection pool --SKIPIF-- --FILE-- pool = new \Swoole\Coroutine\Channel($size); for ($i = 0; $i < $size; $i++) { $redis = new \redis(); $res = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); if ($res == false) { throw new \RuntimeException("failed to connect redis server."); } else { $this->put($redis); } } } public function get(): \redis { return $this->pool->pop(); } public function put(\redis $redis) { $this->pool->push($redis); } public function close(): void { $this->pool->close(); $this->pool = null; } } $count = 0; \Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); go(function () { $pool = new RedisPool(); // max concurrency num is more than max connections // but it's no problem, channel will help you with scheduling for ($c = 0; $c < MAX_CONCURRENCY_MID; $c++) { go(function () use ($pool, $c) { for ($n = 0; $n < MAX_REQUESTS; $n++) { $redis = $pool->get(); if (Assert::assert($redis->set("awesome-{$c}-{$n}", 'swoole'))) { if (Assert::assert($redis->get("awesome-{$c}-{$n}") === 'swoole')) { if (Assert::assert($redis->delete("awesome-{$c}-{$n}"))) { global $count; $count++; } } } $pool->put($redis); } }); } }); Swoole\Event::wait(); Assert::same($count, MAX_CONCURRENCY_MID * MAX_REQUESTS); ?> --EXPECT-- ================================================ FILE: tests/swoole_channel_coro/pop_after_close.phpt ================================================ --TEST-- swoole_channel_coro: pop after close --SKIPIF-- --FILE-- push($i)) { echo "push ok\n"; } } $chan->close(); }); go(function () use ($chan) { while (true) { $data = $chan->pop(); var_dump($data); if (!$data) { break; } } }); }); ?> --EXPECT-- push ok push ok int(1) push ok int(2) int(3) bool(false) ================================================ FILE: tests/swoole_channel_coro/pop_close1.phpt ================================================ --TEST-- swoole_channel_coro: pop close 1 --SKIPIF-- --FILE-- close(); go(function () use ($c1) { $ret = $c1->pop(); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECTF-- pop ret:false error:-2 ================================================ FILE: tests/swoole_channel_coro/pop_timeout1.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 1 --SKIPIF-- --FILE-- pop(1); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECTF-- pop ret:false error:-1 ================================================ FILE: tests/swoole_channel_coro/pop_timeout2.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 2 --SKIPIF-- --FILE-- pop(1); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); go(function () use ($c1) { co::sleep(2); echo "sleep 2\n"; $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)."\n"; }); ?> --EXPECTF-- pop ret:false error:-1 sleep 2 chan push ret:true ================================================ FILE: tests/swoole_channel_coro/pop_timeout3.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 3 --SKIPIF-- --FILE-- pop(1); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); go(function () use ($c1) { co::sleep(0.5); $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); Swoole\Event::wait(); ?> --EXPECTF-- pop ret:'chan-1' error:0 chan push ret:true error:0 ================================================ FILE: tests/swoole_channel_coro/pop_timeout4.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 4 --SKIPIF-- --FILE-- pop(1); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); go(function () use ($c1) { $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECTF-- pop ret:'chan-1' error:0 chan push ret:true error:0 ================================================ FILE: tests/swoole_channel_coro/pop_timeout5.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout hanging up --SKIPIF-- --FILE-- pop(); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECTF-- ================================================ FILE: tests/swoole_channel_coro/pop_timeout6.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 6 --SKIPIF-- --FILE-- pop(); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); go(function () use ($c1) { $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECT-- pop ret:'chan-1' error:0 chan push ret:true error:0 ================================================ FILE: tests/swoole_channel_coro/pop_timeout7.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 7 --SKIPIF-- --FILE-- pop(0.5); echo "pop ret:".var_export($ret,1)." error:".$c1->errCode."\n"; $ret = $c1->pop(1); echo "pop ret:".var_export($ret,1)."\n"; }); go(function () use ($c1) { co::sleep(1); echo "sleep 1\n"; $ret = $c1->push("chan-1"); echo "chan push ret:".var_export($ret,1)."\n"; }); Swoole\Event::wait(); ?> --EXPECTF-- pop ret:false error:-1 sleep 1 pop ret:'chan-1' chan push ret:true ================================================ FILE: tests/swoole_channel_coro/pop_timeout8.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 8 --SKIPIF-- --FILE-- c = new chan(); echo "__construct\n"; } function __destruct() { go(function(){ echo "__destruct\n"; $ret = $this->c->pop(0.5); echo "pop ret:".var_export($ret,1)." error:".$this->c->errCode."\n"; }); } } $t = new T(); unset($t); ?> --EXPECTF-- __construct __destruct pop ret:false error:-1 ================================================ FILE: tests/swoole_channel_coro/push_close1.phpt ================================================ --TEST-- swoole_channel_coro: push close 1 --SKIPIF-- --FILE-- close(); go(function () use ($c1) { $ret = $c1->push(1); echo "push ret:".var_export($ret,1)." error:".$c1->errCode."\n"; }); ?> --EXPECTF-- push ret:false error:-2 ================================================ FILE: tests/swoole_channel_coro/push_timeout1.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 1 --SKIPIF-- --FILE-- push('foo', 0.001); Assert::true($ret); $ret = $channel->push('foo', 0.001); Assert::true($ret); }); for ($n = MAX_REQUESTS; $n--;) { go(function () use ($channel) { $ret = $channel->push('foo', 0.001); Assert::false($ret); Assert::same($channel->errCode, SWOOLE_CHANNEL_TIMEOUT); }); } go(function () use ($channel) { $ret = $channel->pop(); Assert::same($ret, 'foo'); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_channel_coro/push_timeout2.phpt ================================================ --TEST-- swoole_channel_coro: pop timeout 2 --SKIPIF-- --FILE-- pop(0.1), 1); Assert::same($chan->pop(0.1), 'swoole'); }); go(function () use ($chan) { Assert::assert($chan->push(1, 0.1)); Assert::assert(!$chan->push(2, 0.1)); Assert::assert($chan->push('swoole', 1)); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_channel_coro/push_timeout3.phpt ================================================ --TEST-- swoole_channel_coro: push timeout 3 --SKIPIF-- --FILE-- push(1, 0.1)); Assert::assert(!$channel->push(1, 0.1)); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_channel_coro/push_timeout4.phpt ================================================ --TEST-- swoole_channel_coro: push timeout 3 --SKIPIF-- --FILE-- push(1, 0.1)); Assert::assert($channel->push(1, 0.1)); }); go(function () use ($channel) { Assert::same($channel->pop(0.1), 1); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_channel_coro/type.phpt ================================================ --TEST-- swoole_channel_coro: type test --SKIPIF-- --FILE-- 'ok'], (object)['exit' => 'ok'], STDIN ]; $chan = new Swoole\Coroutine\Channel; go(function () use ($chan, $exit_status_list) { foreach ($exit_status_list as $val) { Assert::assert($chan->push($val)); } }); go(function () use ($chan, $exit_status_list) { foreach ($exit_status_list as $_val) { $val = $chan->pop(); Assert::same($val, $_val); } }); Swoole\Event::wait(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_async/base.phpt ================================================ --TEST-- swoole_client_async: Swoole\Async\Client connect & send & close --SKIPIF-- --FILE-- on("connect", function(Swoole\Async\Client $cli) { Assert::true($cli->isConnected()); $cli->send(RandStr::gen(1024, RandStr::ALL)); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data){ $recv_len = strlen($data); // print("receive: len $recv_len"); $cli->send(RandStr::gen(1024, RandStr::ALL)); $cli->close(); Assert::false($cli->isConnected()); }); $cli->on("error", function(Swoole\Async\Client $cli) { print("error"); }); $cli->on("close", function(Swoole\Async\Client $cli) { Swoole\Event::exit(); echo "SUCCESS"; }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 0.2); Swoole\Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_async/big_package_memory_leak.phpt ================================================ --TEST-- swoole_client_async: big_package_memory_leak --SKIPIF-- --FILE-- set(['socket_buffer_size' => 2 * 1024 * 1024]); $cli->on("connect", function (Swoole\Async\Client $cli) { $cli->send(str_repeat("\0", 1024 * 1024 * 1.9)); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { $cli->send($data); }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "error"; }); $cli->on("close", function (Swoole\Async\Client $cli) use ($closeServer) { echo "closed\n"; $closeServer(); }); $cli->connect('127.0.0.1', $port); Assert::same(memory_get_usage(true), $mem); echo "SUCCESS\n"; Swoole\Event::wait(); ?> --EXPECT-- SUCCESS closed ================================================ FILE: tests/swoole_client_async/buffer_full.phpt ================================================ --TEST-- swoole_client_async: onBufferFull & onBufferEmpty --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { Co::set(['log_level' => 5, 'display_errors' => false]); $client = new Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 1 * 1024 * 1024,]); $client->buffer = array(); $countBufferEmpty = 0; $countBufferFull = 0; $client->on("connect", function (Client $cli) { for ($i = 0; $i < 1024; $i++) { $data = str_repeat('A', 8192); if ($cli->send($data) === false and $cli->errCode == 1008) { $cli->buffer[] = $data; } } }); $client->on("receive", function (Client $cli, $data) { $cli->send(pack('N', 8) . 'shutdown'); $cli->close(); Assert::same($data, md5_file(TEST_IMAGE)); }); $client->on("error", function ($cli) { echo "Connect failed\n"; }); $client->on("close", function ($cli) { }); $client->on("bufferEmpty", function (Client $cli) use (&$countBufferEmpty) { $countBufferEmpty++; foreach ($cli->buffer as $k => $data) { if ($cli->send($data) === false and $cli->errCode == 1008) { break; } else { unset($cli->buffer[$k]); } } if (count($cli->buffer) == 0) { $cli->close(); } }); $client->on("bufferFull", function (Client $cli) use (&$countBufferFull) { $countBufferFull++; }); $client->connect(TCP_SERVER_HOST, $port, 0.5); Swoole\Event::wait(); Assert::greaterThanEq($countBufferEmpty, 1); Assert::greaterThanEq($countBufferFull, 1); }; $pm->childFunc = function () use ($pm, $port) { $socket = stream_socket_server("tcp://0.0.0.0:{$port}", $errno, $errstr) or die("$errstr ($errno)
\n"); $pm->wakeup(); while ($conn = stream_socket_accept($socket)) { for ($i = 0; $i < 4; $i++) { usleep(500000); for ($j = 0; $j < 256; $j++) { $data = fread($conn, 8192); } } fclose($conn); break; } fclose($socket); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_async/connect_dns.phpt ================================================ --TEST-- swoole_client_async: connect & dns --SKIPIF-- --FILE-- on("connect", function (Swoole\Async\Client $cli) { Assert::true($cli->isConnected()); $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { Assert::assert(strlen($data) > 0); $cli->close(); Assert::false($cli->isConnected()); echo "DONE\n"; }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "ERROR\n"; }); $cli->on("close", function (Swoole\Async\Client $cli) { echo "SUCCESS\n"; }); $cli->connect("www.baidu.com", 80, 2.0); Swoole\Event::wait(); ?> --EXPECT-- SUCCESS DONE ================================================ FILE: tests/swoole_client_async/connect_refuse.phpt ================================================ --TEST-- swoole_client_async: connect refuse --SKIPIF-- --FILE-- on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); $cli->connect('127.0.0.1', 65535); ?> --EXPECT-- error ================================================ FILE: tests/swoole_client_async/connect_refuse_udg.phpt ================================================ --TEST-- swoole_client_async: connect refuse with unix dgram --SKIPIF-- --FILE-- on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); @$cli->connect("/test.sock", 0, 0.5, 1); Swoole\Event::wait(); ?> --EXPECT-- error ================================================ FILE: tests/swoole_client_async/connect_refuse_unix.phpt ================================================ --TEST-- swoole_client_async: connect refuse with unix stream --SKIPIF-- --FILE-- on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); @$cli->connect("/test.sock"); Swoole\Event::wait(); ?> --EXPECT-- error ================================================ FILE: tests/swoole_client_async/connect_timeout.phpt ================================================ --TEST-- swoole_client_async: connect_host_not_found --SKIPIF-- --FILE-- on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); $cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); $cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); $cli->connect("192.0.0.1", 9000, 0.1); ?> --EXPECT-- error ================================================ FILE: tests/swoole_client_async/connect_twice.phpt ================================================ --TEST-- swoole_client_async: connect twice --SKIPIF-- --FILE-- --EXPECT-- error ================================================ FILE: tests/swoole_client_async/enableSSL.phpt ================================================ --TEST-- swoole_client_async: enableSSL --SKIPIF-- --FILE-- on("connect", function (Swoole\Async\Client $cli) { Assert::true($cli->isConnected()); echo 'connected' . PHP_EOL; $cli->enableSSL(function ($cli) { echo "SSL READY\n"; $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); }); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { Assert::assert(strlen($data) > 0); Assert::contains($data, 'www.baidu.com'); $cli->close(); Assert::false($cli->isConnected()); echo "DONE\n"; }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "ERROR\n"; }); $cli->on("close", function (Swoole\Async\Client $cli) { echo "SUCCESS\n"; }); $cli->connect("www.baidu.com", 443, 2.0); Swoole\Event::wait(); ?> --EXPECT-- connected SSL READY SUCCESS DONE ================================================ FILE: tests/swoole_client_async/enableSSL_bad_callback.phpt ================================================ --TEST-- swoole_client_async: enableSSL with bad callback --SKIPIF-- --FILE-- enableSSL(); } catch (Exception $e) { Assert::contains($e->getMessage(), 'require `onSslReady` callback'); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_async/enableSSL_before_connect.phpt ================================================ --TEST-- swoole_client_async: enableSSL before connect --SKIPIF-- --FILE-- enableSSL(function ($cli) { echo "SSL READY\n"; $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); }); Assert::false($res); ?> --EXPECTF-- Warning: Swoole\Async\Client::enableSSL(): client is not connected to server in %s on line %d ================================================ FILE: tests/swoole_client_async/eof.phpt ================================================ --TEST-- swoole_client_async: eof protocol [async] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); $client->set(['open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "\r\n\r\n"]); $client->on("connect", function (Swoole\Async\Client $cli) { $cli->send("recv\r\n\r\n"); }); $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { static $i = 0; $i++; Assert::assert($pkg != false); Assert::assert(str_ends_with($pkg, "\r\n\r\n")); //小包 if ($i <= 1000) { Assert::assert($pkg and strlen($pkg) <= 2048); if ($i == 1000) { echo "SUCCESS\n"; } } //慢速发送 elseif ($i <= 1100) { Assert::assert($pkg and strlen($pkg) <= 8192); if ($i == 1100) { echo "SUCCESS\n"; } } //大包 else { $_pkg = unserialize(substr($pkg, 0, strlen($pkg) - 4)); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i - 1100 - 1); Assert::same(md5($_pkg['data']), $_pkg['md5']); Assert::lengthBetween($_pkg['data'], 20000, 256 * 1024 * 1.5); if ($i == 2100) { echo "SUCCESS\n"; $cli->close(); Swoole\Process::kill($pid); } } }); $client->on("error", function (Swoole\Async\Client $cli) { echo "ERROR\n"; }); $client->on("close", function (Swoole\Async\Client $cli) { echo "CLOSE\n"; Swoole\Event::exit(); }); if (!$client->connect('127.0.0.1', $port, 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'package_max_length' => 1024 * 1024 * 2, 'socket_buffer_size' => 128 * 1024 * 1024, 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, 'send_yield' => true, )); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { //小包 for ($i = 0; $i < 1000; $i++) { $serv->send($fd, str_repeat('A', rand(100, 2000)) . "\r\n\r\n"); } //慢速发送 for ($i = 0; $i < 100; $i++) { $serv->send($fd, str_repeat('A', rand(1000, 2000))); usleep(rand(10000, 50000)); $serv->send($fd, str_repeat('A', rand(2000, 4000)) . "\r\n\r\n"); } //大包 for ($i = 0; $i < 1000; $i++) { $data = base64_encode(random_bytes(random_int(20000, 256 * 1024))); $md5 = md5($data); $serv->send($fd, serialize(['i' => $i, 'md5' => $md5, 'data' => $data]) . "\r\n\r\n"); } }); $serv->start(); }; $pm->async = true; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS SUCCESS CLOSE ================================================ FILE: tests/swoole_client_async/eof_close.phpt ================================================ --TEST-- swoole_client_async: eof protocol [async] [close] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); $client->set(['open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "\r\n\r\n"]); $client->on("connect", function (Swoole\Async\Client $cli) { $cli->send("recv\r\n\r\n"); }); $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid, $pm) { echo "RECEIVED\n"; $cli->close(); $pm->kill(); }); $client->on("error", function (Swoole\Async\Client $cli) { print("error"); }); $client->on("close", function (Swoole\Async\Client $cli) { echo "CLOSED\n"; }); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'package_max_length' => 1024 * 1024 * 2, //2M 'socket_buffer_size' => 128 * 1024 * 1024, "worker_num" => 1, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $serv->send($fd, str_repeat('A', rand(100, 2000)) . "\r\n\r\n"); }); $serv->start(); }; $pm->async = true; $pm->childFirst(); $pm->run(); ?> --EXPECT-- RECEIVED CLOSED ================================================ FILE: tests/swoole_client_async/getSocket_bug.phpt ================================================ --TEST-- swoole_client_async: getSocket debug --SKIPIF-- --FILE-- on("connect", function (Swoole\Async\Client $cli) use ($timer) { // getSocket BUG $cli->getSocket(); $cli->getSocket(); echo "SUCCESS\n"; /* @$cli->getSocket(); $err = error_get_last(); Assert::same($err["message"], "swoole_client_async::getSocket(): unable to obtain socket family Error: Bad file descriptor[9]."); */ $cli->close(); Swoole\Timer::clear($timer); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "error\n"; }); $cli->on("close", function (Swoole\Async\Client $cli) { }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 1); Swoole\Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_async/getpeername.phpt ================================================ --TEST-- swoole_client_async: getsockpeername --SKIPIF-- --FILE-- parentFunc = function ($pid) { $cli = new \Swoole\Async\Client(SWOOLE_SOCK_UDP); $cli->on("connect", function (\Swoole\Async\Client $cli) { Assert::true($cli->isConnected()); $cli->send("test"); }); $cli->on("receive", function (\Swoole\Async\Client $cli, $data) { $i = $cli->getpeername(); Assert::assert($i !== false); $cli->send('shutdown'); $cli->close(); }); $cli->on("close", function (\Swoole\Async\Client $cli) { echo "SUCCESS\n"; }); $r = $cli->connect(UDP_SERVER_HOST, UDP_SERVER_PORT, 1); Assert::assert($r); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new \Swoole\Server(UDP_SERVER_HOST, UDP_SERVER_PORT, SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Packet", function (\Swoole\Server $serv, $data, $clientInfo) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } $serv->sendto($clientInfo['address'], $clientInfo['port'], $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_async/getsockname.phpt ================================================ --TEST-- swoole_client_async: Swoole\Async\Client getsockname --SKIPIF-- --FILE-- on("connect", function (Swoole\Async\Client $cli) use ($timer) { Assert::true($cli->isConnected()); $i = $cli->getsockname(); Assert::assert($i !== false); Assert::same($i["host"], '127.0.0.1'); $cli->close(); Swoole\Timer::clear($timer); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "error"; }); $cli->on("close", function (Swoole\Async\Client $cli) { echo "SUCCESS"; Swoole\Event::exit(); }); $cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 1); Swoole\Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_async/length_protocol.phpt ================================================ --TEST-- swoole_client_async: length protocol [async] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $client->on("connect", function (Swoole\Async\Client $cli) { $cli->send("recv\r\n\r\n"); }); $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { static $i = 0; $i++; //小包 if ($i <= 1000) { Assert::assert($pkg and strlen($pkg) <= 2048); if ($i == 1000) { echo "SUCCESS\n"; } return; } //慢速发送 elseif ($i <= 1100) { Assert::assert($pkg and strlen($pkg) <= 8192); if ($i == 1100) { echo "SUCCESS\n"; } return; } //大包 else { Assert::assert($pkg != false); $_pkg = unserialize(substr($pkg, 4)); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i - 1100 - 1); Assert::lengthBetween($_pkg['data'], 20000, 256 * 1024); if ($i == 2100) { echo "SUCCESS\n"; $cli->close(); Swoole\Process::kill($pid); } } }); $client->on("error", function (Swoole\Async\Client $cli) { print("error"); }); $client->on("close", function (Swoole\Async\Client $cli) { Swoole\Event::exit(); }); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'send_yield' => true, 'log_file' => '/tmp/swoole.log', )); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { //小包 for ($i = 0; $i < 1000; $i++) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, pack('N', strlen($data)) . $data); } //慢速发送 for ($i = 0; $i < 100; $i++) { $data = str_repeat('A', rand(3000, 6000)); $n = rand(1000, 2000); $serv->send($fd, pack('N', strlen($data)) . substr($data, 0, $n)); usleep(rand(10000, 50000)); $serv->send($fd, substr($data, $n)); } //大包 for ($i = 0; $i < 1000; $i++) { $data = serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]); $serv->send($fd, pack('N', strlen($data)) . $data); } }); $serv->start(); }; $pm->async = true; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS SUCCESS ================================================ FILE: tests/swoole_client_async/length_protocol_func.phpt ================================================ --TEST-- swoole_client_async: length protocol func --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_func' => function ($data) { $n = strpos($data, '|'); if ($n == false) { return -1; } else { return intval(substr($data, 0, $n)) + $n + 1; } }, ]); $client->on("connect", function (Swoole\Async\Client $cli) { $int = rand(1000, 5000); $data = json_encode(['data' => RandStr::gen($int), 'index' => 2, 'len' => $int]); $cli->send(pack('N', strlen($data) + 4) . $data); }); $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { Assert::assert($pkg != false and strlen($pkg) > 100); Swoole\Process::kill($pid); $cli->close(); }); $client->on("error", function (Swoole\Async\Client $cli) { print("error"); }); $client->on("close", function (Swoole\Async\Client $cli) { Swoole\Event::exit(); }); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, strlen($data) . "|" . $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_async/port_invalid.phpt ================================================ --TEST-- swoole_client_async: port invalid --SKIPIF-- --FILE-- on("connect", function (Swoole\Async\Client $cli) { }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { }); $cli->on("error", function (Swoole\Async\Client $cli) { }); $cli->on("close", function (Swoole\Async\Client $cli) { }); Assert::false(@$cli->connect("www.baidu.com", null, 2.0)); Assert::same(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_async/sendfile.phpt ================================================ --TEST-- swoole_client_async: async sendfile --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); $client->on("connect", function (Swoole\Async\Client $cli) { $cli->send(pack('N', filesize(TEST_IMAGE))); $ret = $cli->sendfile(TEST_IMAGE); Assert::assert($ret); }); $client->on("receive", function (Swoole\Async\Client $cli, $data) { $cli->send(pack('N', 8) . 'shutdown'); $cli->close(); Assert::same($data, md5_file(TEST_IMAGE)); }); $client->on("error", function ($cli) { echo "Connect failed\n"; }); $client->on("close", function ($cli) { }); $client->connect(TCP_SERVER_HOST, $port, 0.5); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm, $port) { $serv = new \Swoole\Server(TCP_SERVER_HOST, $port, SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'dispatch_mode' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 2000000, ]); $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (\Swoole\Server $serv, $fd, $rid, $data) { if (substr($data, 4, 8) == 'shutdown') { $serv->shutdown(); return; } $serv->send($fd, md5(substr($data, 4))); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_async/sleep_wake.phpt ================================================ --TEST-- swoole_client_async: Swoole\Async\Client sleep & sleep --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new \Swoole\Async\Client(SWOOLE_SOCK_TCP); $cli->on("connect", function (Swoole\Async\Client $cli) { Assert::true($cli->isConnected()); $r = $cli->sleep(); Assert::assert($r); swoole_timer_after(200, function () use ($cli) { $r = $cli->wakeup(); Assert::assert($r); }); $cli->send(RandStr::gen(1024, RandStr::ALL)); }); $cli->on("receive", function (Swoole\Async\Client $cli, $data) { $recv_len = strlen($data); $cli->send(RandStr::gen(1024, RandStr::ALL)); $cli->close(); Assert::false($cli->isConnected()); }); $cli->on("error", function (Swoole\Async\Client $cli) { echo "error"; }); $cli->on("close", function (Swoole\Async\Client $cli) { echo "SUCCESS"; }); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.1); Swoole\Event::wait(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { include __DIR__ . "/../include/api/tcp_server.php"; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_coro/bug_2346.phpt ================================================ --TEST-- swoole_client_coro: #2346 method timeout --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_eof_split' => false, 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => 4, 'package_body_offset' => 8, 'package_max_length' => 2 * 1024 * 1024 ]); if ($client->connect('127.0.0.1', $pm->getFreePort(), 0.1)) { // 0.2 $s = microtime(true); Assert::assert(@!$client->recv(0.2)); Assert::same($client->errCode, SOCKET_ETIMEDOUT); approximate(0.2, microtime(true) - $s); // -1 & 0.3 go(function () use ($client) { co::sleep(0.3); $client->close(); }); Assert::assert(@!$client->recv(-1)); // connection closed Assert::same($client->errCode, SOCKET_ECANCELED); approximate(0.5, microtime(true) - $s); // canceled echo "DONE\n"; } }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/close.phpt ================================================ --TEST-- swoole_client_coro: close actively by client --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $cli->connect('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->socket instanceof Swoole\Coroutine\Socket); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)).$data); co::sleep(0.2); $retData = $cli->recv(); Assert::assert(is_string($retData) and strlen($retData) > 0); /** use valgrind to check memory */ $cli->close(); Assert::eq($cli->socket, null); Assert::assert(!$cli->connected); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('B', 1025); $serv->send($fd, pack('N', strlen($data)) . $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/close_in_other_co.phpt ================================================ --TEST-- swoole_client_coro: close in other coroutine --SKIPIF-- --FILE-- bind('127.0.0.1', 9601)); Assert::assert($sock->listen(512)); $conn = $sock->accept(); Assert::assert($conn); Assert::isInstanceOf($conn, Swoole\Coroutine\Socket::class); Co::yield(); }); $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); go(function () use ($client) { $client->connect('127.0.0.1', 9601); $data = @$client->recv(); //socket is closed Assert::assert(!$data ); Assert::eq($client->errCode, SOCKET_ECANCELED); }); go(function () use ($client, $cid) { co::sleep(.01); $client->close(); co::sleep(.01); co::resume($cid); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/close_resume.phpt ================================================ --TEST-- swoole_client_coro: (length protocol) resume in onClose callback --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $cli->connect('127.0.0.1', $pm->getFreePort()); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)).$data); co::sleep(0.2); $retData = $cli->recv(); Assert::assert(is_string($retData) and strlen($retData) > 0); $retData = $cli->recv(); Assert::same($retData, ''); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('B', 1025); $serv->send($fd, pack('N', strlen($data)) . $data); $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/close_socket_property.phpt ================================================ --TEST-- swoole_client_coro: close socket property --SKIPIF-- --FILE-- connect('www.baidu.com', 80)); Assert::true($cli->connected); Assert::true($cli->socket->close()); Assert::false($cli->close()); Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); Assert::false($cli->connected); Assert::null($cli->socket); Assert::true($cli->connect('www.baidu.com', 80)); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/close_twice.phpt ================================================ --TEST-- swoole_client_coro: close twice --SKIPIF-- --FILE-- connect('www.baidu.com', 80); Assert::true($cli->close()); Assert::false($cli->close()); Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/connect_dns_timeout.phpt ================================================ --TEST-- swoole_client_coro: connect dns timeout --SKIPIF-- --FILE-- 0.005]); Co\run(function () { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); Assert::assert(!@$cli->connect('xxx.66xx.6855.xxx.xx'.rand(1000, 9999) . time(), 80)); Assert::same($cli->errCode, SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/connect_timeout.phpt ================================================ --TEST-- swoole_client_coro: connect timeout --SKIPIF-- --FILE-- 0.1 ]); $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $s = microtime(true); Assert::assert(!@$cli->connect('140.207.135.104', 1)); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); $s = microtime(true) - $s; phpt_var_dump($s); time_approximate($s, 0.1, 0.2); $s = microtime(true); Assert::assert(!@$cli->connect('140.207.135.104', 1, $random_timeout = mt_rand(100, 1000) / 1000)); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); $s = microtime(true) - $s; phpt_var_dump($s); time_approximate($random_timeout, $s, 0.2); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/connect_with_dns.phpt ================================================ --TEST-- swoole_client_coro: connect with dns --SKIPIF-- --FILE-- connect('www.qq.com', 80)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/dtls.phpt ================================================ --TEST-- swoole_client_coro: dtls --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run( function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP | SWOOLE_SSL); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); Assert::same($client->recv(), "Swoole hello world"); $pm->kill(); } ); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); $serv->set([ //'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->on('packet', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/enableSSL.phpt ================================================ --TEST-- swoole_client_coro: enableSSL --SKIPIF-- --FILE-- connect('www.baidu.com', 443); $client->enableSSL(); $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; if (!$client->send($http)) { echo "ERROR\n"; } $content = ''; while (true) { $read = $client->recv(); if (empty($read)) { break; } $content .= $read; } $client->close(); Assert::assert(strpos($content, 'map.baidu.com') !== false); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/eof.phpt ================================================ --TEST-- swoole_client_coro: tcp client with eof --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $port2->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data\r\n\r\n"); }); $http->set(array( //'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1))) { fail: $response->end("ERROR\n"); return; } if (!$cli->send("hello\r\n\r\n")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_client_coro/eof_02.phpt ================================================ --TEST-- swoole_client_coro: tcp client with eof [02] --SKIPIF-- --FILE-- pool = new SplQueue(); } public function put($mysql) { $this->pool->enqueue($mysql); } public function get() { //有空闲连接 if (count($this->pool) > 0) { return $this->pool->dequeue(); } $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP); $client->set(array( 'open_eof_split' => true, //打开EOF_SPLIT检测 'package_eof' => "\r\n", )); $res = $client->connect('127.0.0.1', 8000, 3); if ($res == false) { echo "create connect failed, errCode=".$client->errCode."\n"; return false; } else { return $client; } } } $pm->parentFunc = function ($pid) use ($pm) { go(function () { $pool = new MyPool(); for ($j = 0; $j < 2; $j++) { $con = []; for ($i = 0; $i < 4; $i++) { $client = $pool->get(); $client->send('hello' . $i . "\r\n"); $con[] = $client; } co::sleep(0.1); foreach ($con as $key => $value) { echo "recv:" . trim($value->recv()) . "\n"; $pool->put($value); } } }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('0.0.0.0', 8000, SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set(array( 'open_eof_split' => true, 'package_eof' => "\r\n", )); $serv->on('receive', function ($serv, $fd, $rid, $data) { $ret = "server reply {" . trim($data) . "} \r\n"; $serv->send($fd, $ret); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- recv:server reply {hello0} recv:server reply {hello1} recv:server reply {hello2} recv:server reply {hello3} recv:server reply {hello0} recv:server reply {hello1} recv:server reply {hello2} recv:server reply {hello3} ================================================ FILE: tests/swoole_client_coro/eof_03.phpt ================================================ --TEST-- swoole_client_coro: eof with smtp qq --SKIPIF-- --FILE-- set([ 'open_eof_check' => true, 'package_eof' => "\r\n", ]); $client->connect('smtp.qq.com', 25, 5); go(function () use ($client) { $n = 9; while ($n--) { $data = $client->recv(); echo $data; if (empty($data)) { break; } } }); $client->send("ehlo smtp.qq.com\r\n"); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- 220 smtp.qq.com Esmtp QQ Mail Server 250-smtp.qq.com 250-PIPELINING 250-SIZE 73400320 250-STARTTLS 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN 250-MAILCOMPRESS 250 8BITMIME DONE ================================================ FILE: tests/swoole_client_coro/eof_04.phpt ================================================ --TEST-- swoole_client_coro: eof with multi packages --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_TCP); $client->set([ 'open_eof_check' => true, 'package_eof' => "\r\n", ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("ERROR\n"); } go(function () use ($pm, $client) { $n = $pm->getRandomDataSize(); while ($n--) { $data = $client->recv(); if (empty($data)) { break; } Assert::same(rtrim($data, "\r\n"), $pm->getRandomData()); } }); }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->set(['log_file' => '/dev/null']); $server->on('connect', function (Swoole\Server $server, int $fd) use ($pm) { do { $data = ''; for ($n = mt_rand(1, $pm->getRandomDataSize()); $n--;) { $data .= $pm->getRandomData() . "\r\n"; } $server->send($fd, $data); } while ($pm->getRandomDataSize() > 0); }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/export_socket.phpt ================================================ --TEST-- swoole_client_coro: close socket property --SKIPIF-- --FILE-- connect('www.baidu.com', 80)); Assert::true($cli->connected); $socket = $cli->exportSocket(); $socket->close(); Assert::false($cli->recv()); Assert::false($cli->close()); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/fixed_package.phpt ================================================ --TEST-- swoole_client_coro: fixed package --SKIPIF-- --FILE-- setRandomFunc('mt_rand'); $pm->initRandomDataEx(1, MAX_REQUESTS, 0, 65535); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_length_check' => true, 'package_length_func' => function (string $data) { if (strlen($data) >= 2) { return 2; } return 0; } ]); if ($client->connect('127.0.0.1', $pm->getFreePort(), 1)) { for ($n = MAX_REQUESTS; $n--;) { $data = $client->recv(); if (!$data) { echo "ERROR\n"; break; } Assert::same($data, pack('n', $pm->getRandomData())); } } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on('connect', function (Swoole\Server $server, int $fd) use ($pm) { for ($n = MAX_REQUESTS; $n--;) { $server->send($fd, pack('n', $pm->getRandomData())); } }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/getpeername.phpt ================================================ --TEST-- swoole_client_coro: getpeername --SKIPIF-- --FILE-- connect('www.baidu.com', 80); $info = $conn->getpeername(); Assert::eq($info['host'], System::gethostbyname('www.baidu.com')); Assert::eq($info['port'], 80); } ); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/getsockname.phpt ================================================ --TEST-- swoole_client_coro: getsockname --SKIPIF-- --FILE-- connect('www.baidu.com', 80); $info = $conn->getsockname(); Assert::assert(filter_var($info['host'], FILTER_VALIDATE_IP)); Assert::greaterThan($info['port'], 0); } ); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/isConnected.phpt ================================================ --TEST-- swoole_client_coro: is connect --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); Assert::false($cli->isConnected()); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR\n"; } Assert::true($cli->isConnected()); $cli->close(); Assert::false($cli->isConnected()); $pm->kill(); }); Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $serv->set(array( 'log_file' => '/dev/null' )); $serv->on("WorkerStart", function (Server $serv) { global $pm; $pm->wakeup(); }); $serv->on('Receive', function () { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/length_01.phpt ================================================ --TEST-- swoole_client_coro: (length protocol) wrong packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $cli->connect('127.0.0.1', $pm->getFreePort()); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)) . $data); $retData = $cli->recv(); Assert::assert($retData != false); $len = unpack('Nlen', $retData)['len']; Assert::same(strlen($retData), $len + 4); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $len = rand(512*1024, 1024*1024 - 4); $data = pack('N',$len ).str_repeat('C', $len); $chunks = str_split($data, 8192 * 16); foreach ($chunks as $c) { $serv->send($fd, $c); usleep(rand(10000, 99999)); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_coro/length_02.phpt ================================================ --TEST-- swoole_client_coro: (length protocol) wrong packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $port) { go(function () use ($port) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $cli->connect('127.0.0.1', $port); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)).$data); $retData = $cli->recv(); Assert::same($retData, ''); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $serv->send($fd, pack('N', 1223)); $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_coro/length_03.phpt ================================================ --TEST-- swoole_client_coro: length protocol 03 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $client->send("recv\r\n\r\n"); //小包 for ($i = 0; $i < 1000; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 2048); } echo "SUCCESS\n"; //慢速发送 for ($i = 0; $i < 100; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 8192); } echo "SUCCESS\n"; //大包 for ($i = 0; $i < 1000; $i++) { $pkg = $client->recv(); Assert::assert($pkg != false); $_pkg = unserialize(substr($pkg, 4)); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i); Assert::assert(strlen($_pkg['data']) > 8192 and strlen($_pkg['data']) <= 256 * 1024); } echo "SUCCESS\n"; $client->close(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'package_max_length' => 1024 * 1024 * 2, //2M 'socket_buffer_size' => 256 * 1024 * 1024, "worker_num" => 1, 'log_file' => '/tmp/swoole.log', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { //小包 for ($i = 0; $i < 1000; $i++) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, pack('N', strlen($data)) . $data); } //慢速发送 for ($i = 0; $i < 100; $i++) { $data = str_repeat('A', rand(3000, 6000)); $n = rand(1000, 2000); $serv->send($fd, pack('N', strlen($data)) . substr($data, 0, $n)); usleep(rand(10000, 50000)); $serv->send($fd, substr($data, $n)); } //大包 for ($i = 0; $i < 1000; $i++) { $data = serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]); $serv->send($fd, pack('N', strlen($data)) . $data); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS SUCCESS ================================================ FILE: tests/swoole_client_coro/length_04.phpt ================================================ --TEST-- swoole_client_coro: (length protocol) no body --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $cli->connect('127.0.0.1', $pm->getFreePort()); $cli->send(pack('n', 2)); $count = N; while($count--) { $data = $cli->recv(); $header = unpack('nlen', $data); Assert::same(strlen($data), 2); Assert::same($header['len'], 2); } }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $header = unpack('nlen', $data); Assert::same(strlen($data), 2); Assert::same($header['len'], 2); $count = N; while($count--) { $serv->send($fd, pack('n', 2)); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_coro/length_protocol_func.phpt ================================================ --TEST-- swoole_client_coro: length protocol func --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function() use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_func' => function ($data) { $n = strpos($data, '|'); if ($n == false) { return -1; } else { return intval(substr($data, 0, $n)) + $n + 1; } }, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $int = rand(1000, 5000); $data = json_encode(['data' => RandStr::gen($int), 'index' => 2, 'len' => $int]); $client->send(pack('N', strlen($data) + 4) . $data); $pkg = $client->recv(); Assert::assert($pkg != false and strlen($pkg) > 100); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, strlen($data) . "|" . $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/length_types.phpt ================================================ --TEST-- swoole_client_coro: tcp package length check --SKIPIF-- --FILE-- initRandomData(count(tcp_length_types()) * MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { foreach (tcp_length_types() as $length_type => $type_length) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_eof_split' => false, 'open_length_check' => true, 'package_length_type' => $length_type, 'package_length_offset' => 0, 'package_body_offset' => $type_length ]); if ($client->connect('127.0.0.1', $pm->getFreePort(), 0.1)) { for ($n = MAX_REQUESTS; $n--;) { $data = $pm->getRandomData(); $recv = substr($client->recv(-1), $type_length); if (!Assert::assert($recv === $data)) { echo "ERROR\n"; break; } } } } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on('connect', function (Swoole\Server $server, int $fd) use ($pm) { $length_type = array_keys(tcp_length_types())[$fd - 1]; for ($n = MAX_REQUESTS; $n--;) { Assert::assert($server->send($fd, tcp_pack($pm->getRandomData(), $length_type))); } }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/read_and_write.phpt ================================================ --TEST-- swoole_client_coro: send and recv with channel --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); $client->connect("127.0.0.1", $pm->getFreePort()); $send_queue = new chan(150); $recv_queue = new chan(150); //读协程 go(function () use ($recv_queue, $send_queue, $client) { while(true) { $data = $client->recv(); if (empty($data)) { //连接关闭了,结束 $send_queue->close(); $recv_queue->close(); $client->close(); break; } else { $recv_queue->push($data); } } echo "read-co stop\n"; }); //写协程 go(function () use ($send_queue, $client) { while(true) { $data = $send_queue->pop(); if (empty($data)) { //通道已关闭 break; } else { $client->send($data); } } echo "write-co stop\n"; }); //启动 $n 个协程做消费者 $n = 100; while ($n--) { go(function () use ($recv_queue, $n) { for ($i = 0; $i < 100; $i++) { //收到了数据 $data = $recv_queue->pop(); } //echo "consumer-co $n stop\n"; }); } //启动 $n 个协程随机发送数据 $n = 100; while ($n--) { go(function () use ($send_queue, $n) { for ($i = 0; $i < 100; $i++) { //投递任务 $send_queue->push("hello world $i ".rand(1000000, 9999999)."\r\n"); } //echo "producer $n stop\n"; }); } }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, 'open_eof_split' => true, 'package_eof' => "\r\n", ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { static $i = 0; $serv->send($fd, "Swoole: $data"); $i++; if ($i == 10000) { $serv->close($fd); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- write-co stop read-co stop ================================================ FILE: tests/swoole_client_coro/reconnect.phpt ================================================ --TEST-- swoole_client_coro: reconnect 1 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $flag = 0; $client = new Client(SWOOLE_SOCK_TCP); reconnect: if (!$client->connect('127.0.0.1', 9501)) { /** * if we want to reconnect server, we should call $client->close() first */ Assert::eq($client->errCode, SOCKET_EISCONN); Assert::eq($client->errMsg, swoole_strerror(SOCKET_EISCONN)); } $pm->kill(); $data = $client->recv(); if (empty($data)) { if ($flag === 0) { $flag += 1; goto reconnect; } } echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', 9501); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on('start', function () use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function () { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/reconnect_2.phpt ================================================ --TEST-- swoole_client_coro: reconnect 2 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $flag = 0; $client = new Client(SWOOLE_SOCK_TCP); $n = 2; while ($n--) { Assert::true($client->connect('127.0.0.1', 9501)); Assert::true($client->close()); } echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', 9501); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on('start', function () use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function () { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/reconnect_3.phpt ================================================ --TEST-- swoole_client_coro: reconnect 3 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $flag = 0; $client = new Client(SWOOLE_SOCK_TCP); $n = 2; while ($n--) { Assert::true($client->connect('127.0.0.1', 9501)); go(function () use ($client) { while (1) { if (!$client->recv()) { break; } } Assert::true($client->close()); }); Assert::false($client->close()); Assert::eq($client->errCode, SWOOLE_ERROR_CO_SOCKET_CLOSE_WAIT); } echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', 9501); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on('start', function () use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function () { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/recv_after_close.phpt ================================================ --TEST-- swoole_client_coro: recv after close --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort()); $cli->send("hello world\n"); co::sleep(0.2); $retData = $cli->recv(); Assert::assert($retData === false); Assert::assert($cli->errCode === SOCKET_ECONNRESET); $cli->close(); Assert::assert(!$cli->connected); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/recv_bad_packet.phpt ================================================ --TEST-- swoole_client_coro: recv bad packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 0.2) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < 3; $i++) { $sid = strval(rand(100000, 999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recv(); Assert::isEmpty($data); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 4, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $len = rand(1024, 8192); $send_data = str_repeat('A', $len); //bad packet $serv->send($fd, pack('N', SIZE) . $send_data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/recv_timeout.phpt ================================================ --TEST-- swoole_client_coro: recv timeout --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort(), -1); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)) . $data); $retData = @$cli->recv(0.5); Assert::false($retData); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { //no response }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_coro/recv_timeout2.phpt ================================================ --TEST-- swoole_client_coro: recv timeout [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.5); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)) . $data); $s = microtime(true); $retData = @$cli->recv(); Assert::false($retData); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); approximate(0.5, microtime(true) - $s); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { //no response }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_client_coro/recvfrom.phpt ================================================ --TEST-- swoole_client_coro: recvfrom 1 --SKIPIF-- --FILE-- bind('127.0.0.1', $free_port); $peer = null; echo $socket->recvfrom($peer); $socket->sendto($peer['address'], $peer['port'], "server"); }); go(function () use ($free_port) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $cli->sendto('127.0.0.1', $free_port, "hello\n"); $addr = null; $port = null; $cli->recvfrom(1024, $addr, $port); Assert::same($addr, '127.0.0.1'); Assert::same($port, $free_port); }); Swoole\Event::wait(); ?> --EXPECT-- hello ================================================ FILE: tests/swoole_client_coro/recvfrom_2.phpt ================================================ --TEST-- swoole_client_coro: recvfrom 2 --SKIPIF-- --FILE-- set([ 'bind_address' => '127.0.0.1', 'bind_port' => $free_port, ]); $n = N; while ($n--) { $data = $cli->recvfrom(1024, $addr, $port); Assert::same($data, 'hello'); } echo "DONE\n"; }); go(function () use ($free_port) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); $n = N; while ($n--) { $socket->sendto('127.0.0.1', $free_port, "hello"); Co::sleep(0.01); } }); Swoole\Event::wait(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/recvfrom_timeout.phpt ================================================ --TEST-- swoole_client_coro: timeout of udp client --SKIPIF-- --FILE-- set([ 'timeout' => 0.2, ]); if (!Assert::assert($cli->connect('192.0.0.1', $port))) { return; } Assert::assert($cli->send("hello")); // default timeout $s = microtime(true); $ret = @$cli->recvfrom(1024, $peer); $s = microtime(true) - $s; Assert::assert($s > 0.2 && $s < 0.5, $s); Assert::eq($ret, false); Assert::eq($cli->errCode, SOCKET_ETIMEDOUT); $cli->close(); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/recvfrom_timeout2.phpt ================================================ --TEST-- swoole_client_coro: timeout of udp client[2] --SKIPIF-- --FILE-- sendto('192.0.0.1', $port, 'hello')); $cli->set([ 'timeout' => 0.2, ]); // default timeout $s = microtime(true); $ret = @$cli->recvfrom(1024, $peer); $s = microtime(true) - $s; Assert::assert($s > 0.2 && $s < 0.5, $s); Assert::eq($ret, false); Assert::eq($cli->errCode, SOCKET_ETIMEDOUT); $cli->close(); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/send_big.phpt ================================================ --TEST-- swoole_client_coro: send --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $client->connect("127.0.0.1", $pm->getFreePort(), 0.5); Assert::assert($r); set_socket_coro_buffer_size($client->exportSocket(), 65536); $header = "POST /post.php HTTP/1.1\r\n"; $header .= "Host: weibo.com\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ja;q=0.2\r\n"; $_postData = ['data' => urlencode(RandStr::getBytes(128*1024)), 'message' => '', 'code' => 120]; $_postBody = http_build_query($_postData)."_END\r\n\r\n"; $header .= "Content-Length: " . strlen($_postBody); Assert::assert($client->send($header)); Assert::assert($client->send($_postBody)); $data = $client->recv(5); Assert::same($data, "HTTP/1.1 200 OK\r\n\r\n"); $client->close(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'kernel_socket_recv_buffer_size' => 65536, 'kernel_socket_send_buffer_size' => 65536, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd) { }); $serv->on('receive', function ($serv, $fd, $tid, $data) { usleep(5000); if (substr($data, -8, 8) == "_END\r\n\r\n") { $serv->send($fd, "HTTP/1.1 200 OK\r\n\r\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/sendfile.phpt ================================================ --TEST-- swoole_client_coro: sendfile --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $client->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::assert($r); $client->send(pack('N', filesize(TEST_IMAGE))); $ret = $client->sendfile(TEST_IMAGE); Assert::assert($ret); $data = $client->recv(); $client->send(pack('N', 8) . 'shutdown'); $client->close(); Assert::same($data, md5_file(TEST_IMAGE)); }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'dispatch_mode' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 2000000, ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) { if (substr($data, 4, 8) == 'shutdown') { $serv->shutdown(); return; } $serv->send($fd, md5(substr($data, 4))); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/sendto.phpt ================================================ --TEST-- swoole_client_coro: sendto --SKIPIF-- --FILE-- bind('127.0.0.1', $port); $peer = null; $ret = $socket->recvfrom($peer); Assert::assert($ret, 'hello'); $ret = $socket->recvfrom($peer); Assert::assert($ret, 'hello'); echo "DONE\n"; }); go(function () use ($port) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $cli->sendto('127.0.0.1', $port, "hello\n"); $cli->sendto('localhost', $port, "hello\n"); Assert::false($cli->sendto('error_domain', $port, "hello\n")); Assert::assert($cli->errCode, 704); Assert::assert($cli->errMsg, 'DNS Lookup resolve failed'); }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_coro/ssl.phpt ================================================ --TEST-- swoole_client_coro: ssl client --SKIPIF-- --FILE-- connect('www.baidu.com', 443)) { echo "ERROR\n"; } $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; if (!$cli->send($http)) { echo "ERROR\n"; } $content = ''; while (true) { $read = $cli->recv(); if (empty($read)) { break; } $content .= $read; } $cli->close(); Assert::assert(strpos($content, 'map.baidu.com') !== false); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/ssl_verify.phpt ================================================ --TEST-- swoole_client_coro: ssl verify --SKIPIF-- --FILE-- TEST_LOG_FILE]); Coroutine\run(function () { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set([ 'ssl_cafile' => SSL_FILE_DIR . '/mosquitto.org.crt', 'ssl_verify_peer' => true, ]); $ret = $client->connect('test.mosquitto.org', 8883); Assert::true($ret); $client2 = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client2->set([ 'ssl_cafile' => SSL_FILE_DIR . '/mosquitto.org.crt', 'ssl_verify_peer' => true, ]); $ret = $client2->connect('baidu.com', 443); Assert::false($ret); Assert::eq($client2->errCode, SWOOLE_ERROR_SSL_VERIFY_FAILED); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_coro/tcp_client.phpt ================================================ --TEST-- swoole_client_coro: tcp client --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set([]); $port2->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data"); }); $http->set(array( 'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1))) { fail: $response->end("ERROR\n"); return; } if (!$cli->send("hello")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_client_coro/tcp_nodelay.phpt ================================================ --TEST-- swoole_client_coro: tcp client --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set([]); $port2->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data"); }); $http->set(array( 'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->set(['open_tcp_nodelay' => false]); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1))) { fail: $response->end("ERROR\n"); return; } if (!$cli->send("hello")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_client_coro/timeout.phpt ================================================ --TEST-- swoole_client_coro: timeout of udp client --SKIPIF-- --FILE-- bind('127.0.0.1', $port); $peer = null; $socket->recvfrom($peer); echo "recvfrom client\n"; }); go(function () use ($port) { co::set([ 'socket_connect_timeout' => 0.5, 'socket_timeout' => 0.1 ]); $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); if (!Assert::assert($cli->connect('127.0.0.1', $port))) { return; } Assert::assert($cli->send("hello")); // default timeout $s = microtime(true); $ret = @$cli->recv(); $s = microtime(true) - $s; Assert::assert($s > 0.08 && $s < 0.12, $s); Assert::assert(!$ret, var_dump_return($ret)); // custom timeout $s = microtime(true); $ret = @$cli->recv(0.5); $s = microtime(true) - $s; Assert::assert($s > 0.45 && $s < 0.55, $s); Assert::assert(!$ret, var_dump_return($ret)); // default timeout $s = microtime(true); $ret = @$cli->recv(); $s = microtime(true) - $s; Assert::assert($s > 0.08 && $s < 0.12, $s); Assert::assert(!$ret, var_dump_return($ret)); $cli->close(); echo "TIMEOUT\n"; }); Swoole\Event::wait(); ?> --EXPECT-- recvfrom client TIMEOUT ================================================ FILE: tests/swoole_client_coro/udp_client.phpt ================================================ --TEST-- swoole_client_coro: udp client --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_UDP); $port2->set([]); $port2->on('Packet', function ($serv, $data, $client) { $serv->sendto($client['address'], $client['port'], "Swoole: $data"); }); $http->set(array( 'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1))) { fail: $response->end("ERROR\n"); return; } if (!$cli->send("hello")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_client_coro/udp_recv_failed.phpt ================================================ --TEST-- swoole_client_coro: udp recv failed --SKIPIF-- --FILE-- connect('127.0.0.1', get_one_free_port(), 3); echo "connect ret:".var_export($ret,1)."\n"; $ret = $cli->send("hello"); echo "send ret:".var_export($ret,1)."\n"; $ret = @$cli->recv(); echo "recv ret:".var_export($ret,1)."\n"; Assert::same($cli->errCode, SOCKET_ECONNREFUSED); $cli->close(); }); ?> --EXPECT-- connect ret:true send ret:5 recv ret:false ================================================ FILE: tests/swoole_client_coro/unsock_dgram.phpt ================================================ --TEST-- swoole_client_coro: unix socket dgram --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () { $client = new Client(SWOOLE_SOCK_UNIX_DGRAM); $client->set([ 'bind_address' => __DIR__ . '/client.sock', 'bind_port' => 0, ]); $r = $client->connect(UNIXSOCK_PATH, 0, -1); if ($r === false) { echo "ERROR"; exit; } $client->send("SUCCESS"); echo $client->recv(); $client->close(); }); @unlink(UNIXSOCK_PATH); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(UNIXSOCK_PATH, 0, SWOOLE_PROCESS, SWOOLE_SOCK_UNIX_DGRAM); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("packet", function (Server $serv, $data, $addr) { $serv->send($addr['address'], 'SUCCESS'.PHP_EOL); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_coro/unsock_stream.phpt ================================================ --TEST-- swoole_client_coro: unix socket stream --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function (){ $client = new Client(SWOOLE_SOCK_UNIX_STREAM); $r = $client->connect(UNIXSOCK_PATH, 0, -1); if ($r === false) { echo "ERROR"; exit; } $client->send("SUCCESS"); usleep(100 * 1000); echo $client->recv() . "\n"; $client->close(); }); @unlink(UNIXSOCK_PATH); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(UNIXSOCK_PATH, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_STREAM); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { $serv->send($fd, 'SUCCESS'); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/bind_address.phpt ================================================ --TEST-- swoole_client_sync: bind address --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set([ 'bind_address' => '127.0.0.1', 'bind_port' => $pm->getFreePort(), ]); Assert::false($client->connect('127.0.0.1', 9501)); Assert::eq($client->errCode, SOCKET_EADDRINUSE); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_client_sync/connect_1.phpt ================================================ --TEST-- swoole_client_sync: connect 1 - 1 --SKIPIF-- --FILE-- connect("11.11.11.11", 80, 0.5); Assert::false($r); Assert::eq($cli->errCode, SOCKET_ETIMEDOUT); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/connect_2.phpt ================================================ --TEST-- swoole_client_sync: connect 1 - 2 --SKIPIF-- --FILE-- connect(TEST_DOMAIN_3, 443); Assert::assert($r); $cli->close(); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/connect_3.phpt ================================================ --TEST-- swoole_client_sync: connect 1 - 3 nonblocking connect & select --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $r = $cli->connect('127.0.0.1', $pm->getFreePort(), 1); Assert::assert($r); $r = $w = $e = [$cli]; $n = swoole_client_select($r, $w, $e, 0); Assert::same($n, 1); Assert::same(count($w), 1); Assert::same(count($e), 0); Assert::same(count($r), 0); $cli->close(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/enableSSL.phpt ================================================ --TEST-- swoole_client_sync: enableSSL --SKIPIF-- --FILE-- connect("www.baidu.com", 443, 2.0)); if ($cli->enableSSL()) { echo "SSL READY\n"; $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); } $resp = ''; while (true) { $data = $cli->recv(); if ($data == false) { break; } $resp .= $data; } Assert::assert(strlen($resp) > 0); Assert::contains($resp, 'www.baidu.com'); $cli->close(); echo "DONE\n"; ?> --EXPECT-- SSL READY DONE ================================================ FILE: tests/swoole_client_sync/enableSSL_2.phpt ================================================ --TEST-- swoole_client_sync: enableSSL --SKIPIF-- --FILE-- connect("www.baidu.com", 443, 2.0)); try { $cli->enableSSL(function (){}); } catch (\Throwable $e) { Assert::contains($e->getMessage(), 'not support `onSslReady` callback'); } ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/eof.phpt ================================================ --TEST-- swoole_client_sync: eof protocol [sync] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); if (!$client->connect('127.0.0.1', $port, 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $client->send("recv\r\n\r\n"); //小包 for ($i = 0; $i < 1000; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 2048); } echo "SUCCESS\n"; //慢速发送 for ($i = 0; $i < 100; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 8192); } echo "SUCCESS\n"; //大包 for ($i = 0; $i < 1000; $i++) { $pkg = $client->recv(); Assert::assert($pkg != false); $_pkg = swoole_substr_unserialize($pkg, 0, strlen($pkg) - 4); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i); Assert::assert(strlen($_pkg['data']) > 8192 and strlen($_pkg['data']) <= 256 * 1024); } echo "SUCCESS\n"; $client->close(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'package_max_length' => 1024 * 1024 * 2, //2M 'socket_buffer_size' => 256 * 1024 * 1024, "worker_num" => 1, 'log_file' => '/tmp/swoole.log', )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { //小包 for ($i = 0; $i < 1000; $i++) { $serv->send($fd, str_repeat('A', rand(100, 2000)) . "\r\n\r\n"); } //慢速发送 for ($i = 0; $i < 100; $i++) { $serv->send($fd, str_repeat('A', rand(1000, 2000))); usleep(rand(10000, 50000)); $serv->send($fd, str_repeat('A', rand(2000, 4000)) . "\r\n\r\n"); } //大包 for ($i = 0; $i < 1000; $i++) { $serv->send($fd, serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]) . "\r\n\r\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS SUCCESS ================================================ FILE: tests/swoole_client_sync/eof_close.phpt ================================================ --TEST-- swoole_client_sync: eof protocol [sync] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port, $pkg, $pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set(['open_eof_check' => true, "package_eof" => EOF]); if (!$client->connect('127.0.0.1', $port, 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $client->send("recv\r\n\r\n"); $recvPkg = $client->recv(); Assert::assert($recvPkg != false); $_pkg = swoole_substr_unserialize($recvPkg, 0, strlen($recvPkg) - 4); Assert::assert(is_array($_pkg)); Assert::eq($_pkg['data'], $pkg); $recvPkg = $client->recv(); Assert::same($recvPkg, ''); echo "SUCCESS\n"; $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $port, $pkg) { $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'package_max_length' => 1024 * 1024 * 2, 'socket_buffer_size' => 256 * 1024 * 1024, 'log_file' => TEST_LOG_FILE, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pkg) { $serv->send($fd, serialize(['data' => $pkg]) . EOF); $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/eof_timeout.phpt ================================================ --TEST-- swoole_client_sync: eof timeout --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1), 0.5)) { fail: echo "ERROR\n"; Swoole\Process::kill($pid); return; } //no eof, should be timeout here if (!$cli->send("hello")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } echo "OK\n"; Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $port2->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data\r\n\r\n"); }); $http->set([ //'log_file' => '/dev/null' ]); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ERROR ================================================ FILE: tests/swoole_client_sync/http_proxy.phpt ================================================ --TEST-- swoole_client_sync: http client with http_proxy --SKIPIF-- --FILE-- set([ 'timeout' => 30, 'http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT ]); client_http_v10_get($cli); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/keep1.phpt ================================================ --TEST-- swoole_client_sync: long connection --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client1 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = @$client1->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = @$client2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); Assert::assert($client1->sock != $client2->sock); @$client1->close(true); $client2->send("hello"); echo $client2->recv(); @$client2->close(true); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data\n"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Swoole hello ================================================ FILE: tests/swoole_client_sync/keep2.phpt ================================================ --TEST-- swoole_client_sync: long connection[2] --SKIPIF-- --FILE-- connect(TCP_SERVER_HOST, 9999, 0.5); Assert::false($r); @$client1->close(); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = @$client2->connect(TCP_SERVER_HOST, 9999, 0.5); Assert::false($r); Assert::same($client1->sock, $client2->sock); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/keep3.phpt ================================================ --TEST-- swoole_client_sync: long connection[3] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client1 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = $client1->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client1->send("hello"); echo $client1->recv(); $client1->close(); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = $client2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client2->send("hello"); echo $client2->recv(); $client2->close(); Assert::same($client1->sock, $client2->sock); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data\n"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Swoole hello Swoole hello ================================================ FILE: tests/swoole_client_sync/keep4.phpt ================================================ --TEST-- swoole_client_sync: long connection[4] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client1 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC, SWOOLE_SOCK_SYNC, "id"); $r = @$client1->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC, SWOOLE_SOCK_SYNC, "id"); $r = @$client2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); Assert::assert($client1->sock != $client2->sock); @$client1->close(true); $client2->send("hello"); echo $client2->recv(); @$client2->close(true); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data\n"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Swoole hello ================================================ FILE: tests/swoole_client_sync/keep5.phpt ================================================ --TEST-- swoole_client_sync: long connection[5] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client1 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $client1->id="id"; $r = @$client1->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $client2->id="id"; $r = @$client2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); Assert::assert($client1->sock != $client2->sock); @$client1->close(true); $client2->send("hello"); echo $client2->recv(); @$client2->close(true); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data\n"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Swoole hello ================================================ FILE: tests/swoole_client_sync/keep6.phpt ================================================ --TEST-- swoole_client_sync: long connection[6] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client1 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); $r = @$client1->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client1->send("hello"); echo $client1->recv(); $client1->close(); usleep(10000); $client2 = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_KEEP | SWOOLE_SYNC); /** * recreate socket */ $r = $client2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::true($r); $client2->send("hello"); echo $client2->recv(); $client2->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data\n"); usleep(5000); $serv->close($fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Swoole hello Swoole hello ================================================ FILE: tests/swoole_client_sync/length_protocol.phpt ================================================ --TEST-- swoole_client_sync: length protocol [sync] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if (!$client->connect('127.0.0.1', $port, 5, 0)) { echo 'Over flow. errno=' . $client->errCode; exit("\n"); } $client->send("recv\r\n\r\n"); // 小包 for ($i = 0; $i < N_1; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 2048); } echo "SUCCESS\n"; // 慢速发送 for ($i = 0; $i < N_2; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 8192); } echo "SUCCESS\n"; // 大包 for ($i = 0; $i < N_3; $i++) { $pkg = $client->recv(); Assert::assert($pkg != false); $_pkg = unserialize(substr($pkg, 4)); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i); Assert::assert(strlen($_pkg['data']) > 8192 and strlen($_pkg['data']) <= 256 * 1024); } echo "SUCCESS\n"; $client->close(); Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set([ 'package_max_length' => 1024 * 1024 * 2, // 2M 'socket_buffer_size' => 256 * 1024 * 1024, 'worker_num' => 1, 'log_file' => '/tmp/swoole.log', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { // 小包 for ($i = 0; $i < N_1; $i++) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, pack('N', strlen($data)) . $data); } // 慢速发送 for ($i = 0; $i < N_2; $i++) { $data = str_repeat('A', rand(3000, 6000)); $n = rand(1000, 2000); $serv->send($fd, pack('N', strlen($data)) . substr($data, 0, $n)); usleep(rand(10000, 50000)); $serv->send($fd, substr($data, $n)); } // 大包 for ($i = 0; $i < N_3; $i++) { $data = serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]); $serv->send($fd, pack('N', strlen($data)) . $data); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS SUCCESS ================================================ FILE: tests/swoole_client_sync/length_protocol_02.phpt ================================================ --TEST-- swoole_client_sync: length protocol 02 [sync] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $int = rand(1000, 5000); $data = json_encode(['data' => RandStr::gen($int), 'index' => 2, 'len' => $int]); $client->send(pack('N', strlen($data) + 4) . $data); $pkg = $client->recv(); Assert::assert($pkg != false and strlen($pkg) > 100); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, pack('N', strlen($data) + 4) . $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/length_protocol_func.phpt ================================================ --TEST-- swoole_client_sync: length protocol func --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_func' => function ($data) { $n = strpos($data, '|'); if ($n == false) { return -1; } else { return intval(substr($data, 0, $n)) + $n + 1; } }, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $int = rand(1000, 5000); $data = json_encode(['data' => RandStr::gen($int), 'index' => 2, 'len' => $int]); $client->send(pack('N', strlen($data) + 4) . $data); $pkg = $client->recv(); Assert::assert($pkg != false and strlen($pkg) > 100); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 0, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, strlen($data) . "|" . $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/recv_in_task.phpt ================================================ --TEST-- swoole_client_sync: recv in task worker --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', 0, SWOOLE_BASE); $http->set(['worker_num' => 1, 'task_worker_num'=>1, 'log_file' => '/dev/null']); $http->on('workerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { if (!$server->taskworker) { // start logic in task $server->task(1, 0); } }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { usleep(100 * 1000); $response->end('OK'); }); $http->on('Task', function(Swoole\Server $serv, int $task_id, int $src_worker_id, $data) use ($pm) { // trigger timer constantly in order to trigger the EINTR internally Swoole\Timer::tick(1, function(){}); // send request $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $p = $serv->ports[0]->port; Assert::assert($cli->connect('127.0.0.1', $p, 3)); $request = "GET / HTTP/1.1\r\n\r\n"; $cli->send($request); $response = @$cli->recv(); // the server will block by 100ms, so it will surely get EINTR internally by the task timer if (!$response) { if ($cli->errCode == SOCKET_EINTR) { echo "EINTR\n"; } } else { echo "SUCCESS\n"; } $pm->wakeup(); }); $http->on('Finish', function(){}); $http->addProcess(new Swoole\Process(function (Swoole\Process $p) { })); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/recv_timeout.phpt ================================================ --TEST-- swoole_client_sync: recv timeout --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $r = $client->connect(TCP_SERVER_HOST, $pm->getFreePort(), 0.5); Assert::assert($r); $client->send(pack('N', filesize(TEST_IMAGE))); $data = @$client->recv(); Assert::false($data); Assert::same($client->errCode, SOCKET_EAGAIN); $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) { //do nothing }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/recv_with_open_eof_check.phpt ================================================ --TEST-- swoole_client_sync: recv witch open_eof_check and check errCode --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new \Swoole\Client(SWOOLE_SOCK_TCP); $client->set([ 'open_eof_check' => true, 'package_eof' => "\n", 'package_max_length' => 1024 * 1024 * 2, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { throw new Exception("connect failed"); } $data = @$client->recv(1024 * 1024 * 2); Assert::false($data); Assert::eq(SOCKET_EAGAIN, $client->errCode); $client->close(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_client_sync/select.phpt ================================================ --TEST-- swoole_client_sync: select --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $clients = []; for($i=0; $i< 4; $i++) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $ret = $client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0); if(!$ret) { echo "Connect Server fail.errCode=".$client->errCode; } else { $client->send("HELLO WORLD\n"); $clients[$client->sock] = $client; } } $s = microtime(true); while (!empty($clients)) { $write = $error = array(); $read = array_values($clients); $n = swoole_select($read, $write, $error, TIMEOUT); if ($n > 0) { foreach ($read as $index => $c) { echo "Recv #{$c->sock}: " . $c->recv() . "\n"; unset($clients[$c->sock]); } continue; } else if ($n == 0) { echo "TIMEOUT\n"; } else { echo "ERROR\n"; } break; } Assert::greaterThanEq(microtime(true) - $s, TIMEOUT); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- TIMEOUT ================================================ FILE: tests/swoole_client_sync/select_null.phpt ================================================ --TEST-- swoole_client_sync: select --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect(TCP_SERVER_HOST, $pm->getFreePort(), -1)) { exit("connect failed. Error: {$client->errCode}\n"); } $r = [$client]; $w = $e = null; $client->send("hello world\n"); swoole_select($r, $w, $e); echo $client->recv(); $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, "hello world\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- hello world ================================================ FILE: tests/swoole_client_sync/send_recv.phpt ================================================ --TEST-- swoole_client_sync: send & recv --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); $request = "GET / HTTP/1.1\r\n\r\n"; Assert::same($cli->send($request), strlen($request)); usleep(100 * 1000); $response = $cli->recv(); Assert::assert($response && substr($response, 0, 4) === 'HTTP'); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_client_sync/sendfile.phpt ================================================ --TEST-- swoole_client_sync: sync sendfile --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $r = $client->connect(TCP_SERVER_HOST, $port, 0.5); Assert::assert($r); $client->send(pack('N', filesize(TEST_IMAGE))); $ret = $client->sendfile(TEST_IMAGE); Assert::assert($ret); $data = $client->recv(); $client->send(pack('N', 8) . 'shutdown'); $client->close(); Assert::same($data, md5_file(TEST_IMAGE)); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server(TCP_SERVER_HOST, $port, SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'dispatch_mode' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 2000000, ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) { if (substr($data, 4, 8) == 'shutdown') { $serv->shutdown(); return; } $serv->send($fd, md5(substr($data, 4))); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/sendto.phpt ================================================ --TEST-- swoole_client_sync: sendto --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $cli = new Client(SWOOLE_SOCK_UDP); Assert::true($cli->sendto('127.0.0.1', $pm->getFreePort(), "packet-1")); Assert::true($cli->sendto('localhost', $pm->getFreePort(), "packet-2")); Assert::false($cli->sendto('error_domain', $pm->getFreePort(), "hello")); Assert::assert($cli->errCode, SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED); Assert::true($cli->sendto('localhost', $pm->getFreePort(), "packet-3")); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); $socket->bind('127.0.0.1', $pm->getFreePort()); $pm->wakeup(); $peer = null; $ret = $socket->recvfrom($peer); Assert::eq($ret, 'packet-1'); $ret = $socket->recvfrom($peer); Assert::eq($ret, 'packet-2'); $ret = $socket->recvfrom($peer); Assert::eq($ret, 'packet-3'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Client::sendto(): sendto to server[error_domain:%d] failed. Error: DNS Lookup resolve failed[704] in %ssendto.php on line %d DONE ================================================ FILE: tests/swoole_client_sync/socks5_proxy.phpt ================================================ --TEST-- swoole_client_sync: http client with socks5 proxy --SKIPIF-- --FILE-- set([ 'timeout' => 30, 'socks5_host' => SOCKS5_PROXY_HOST, 'socks5_port' => SOCKS5_PROXY_PORT ]); client_http_v10_get($cli); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/ssl_recv_timeout.phpt ================================================ --TEST-- swoole_client_sync: ssl recv timeout --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); $r = $cli->connect('127.0.0.1', $pm->getFreePort(), 5); Assert::assert($r); $cli->send("hello world\n"); $time = time(); $data = $cli->recv(1024); Assert::assert((time() - $time) < 2); Assert::same($data, "Swoole hello world\n"); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/sync_send_recv.phpt ================================================ --TEST-- swoole_client_sync: udp sync client send & recv --SKIPIF-- --FILE-- connect(UDP_SERVER_HOST, UDP_SERVER_PORT); $data = "UdpSendto"; $client->send($data); usleep(100 * 1000); $message = $client->recv(); echo "FromServer:$message\n"; echo "SUCCESS\n"; ?> --EXPECTF-- FromServer:UdpSendto SUCCESS ================================================ FILE: tests/swoole_client_sync/udg_send_timeout.phpt ================================================ --TEST-- swoole_client_sync: udg send timeout --SKIPIF-- --FILE-- connect(SOCKET_FILE, 0, 0.3); $s = microtime(true); while (true) { $re = $ret = $client->sendto(SOCKET_FILE, 0, str_repeat('B', N)); if ($re == false) { break; } } unlink(SOCKET_FILE); Assert::lessThan(microtime(true) - $s, 0.8); ?> --EXPECT-- ================================================ FILE: tests/swoole_client_sync/udp_client_sendto.phpt ================================================ --TEST-- swoole_client_sync: udp sync client sendto --SKIPIF-- --FILE-- connect(UDP_SERVER_HOST, UDP_SERVER_PORT); $ret = $client->sendto(UDP_SERVER_HOST, UDP_SERVER_PORT, "TestUdpClientSendto"); $message = $client->recv(); echo "FromServer:{$message}\n"; echo "SUCCESS"; ?> --EXPECT-- FromServer:TestUdpClientSendto SUCCESS ================================================ FILE: tests/swoole_coroutine/after_start_server_1.phpt ================================================ --TEST-- swoole_coroutine: co::create after server start [1] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::notEmpty(!empty($data)); Assert::same($data, SECRET); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', "worker_num" => 1, ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(SECRET); }); $http->start(); echo "server shutdown\n"; go(function () use ($pm) { Co::readFile(__FILE__); echo "co shutdown\n"; }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- server shutdown co shutdown ================================================ FILE: tests/swoole_coroutine/all_asleep.phpt ================================================ --TEST-- swoole_coroutine: all asleep --SKIPIF-- --FILE-- true, ]); function test1() { $f = function () { Co::yield(); }; $f(); } function test2() { Co::yield(); } Co\run(function () { go(function () { test1(); }); go(function () { test2(); }); Co::sleep(0.1); }); echo "DONE\n"; ?> --EXPECTF-- ================================================================= [FATAL ERROR]: all coroutines (count: 2) are asleep - deadlock! ================================================================= [Coroutine-3] ----------------------------------------------------------------- %A %A %A [Coroutine-2] ----------------------------------------------------------------- %A %A %A %A ================================================================= DONE ================================================ FILE: tests/swoole_coroutine/array_walk.phpt ================================================ --TEST-- swoole_coroutine: array_walk --SKIPIF-- --FILE-- --EXPECTF-- DONE ================================================ FILE: tests/swoole_coroutine/array_walk_deep.phpt ================================================ --TEST-- swoole_coroutine: array_walk --SKIPIF-- --FILE-- --EXPECTF-- DONE ================================================ FILE: tests/swoole_coroutine/async_callback/event_cycle.phpt ================================================ --TEST-- swoole_coroutine/async_callback: event cycle --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/async_callback/signal.phpt ================================================ --TEST-- swoole_coroutine/async_callback: signal --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/async_callback/timer.phpt ================================================ --TEST-- swoole_coroutine/async_callback: timer --SKIPIF-- --FILE-- --EXPECT-- tick tick tick tick tick ================================================ FILE: tests/swoole_coroutine/autoload.phpt ================================================ --TEST-- swoole_coroutine: autoload --SKIPIF-- --FILE-- --EXPECTF-- object(SwooleTestClassA)#%d (0) { } object(SwooleTestClassA)#%d (0) { } ================================================ FILE: tests/swoole_coroutine/autoload_not_found.phpt ================================================ --TEST-- swoole_coroutine: autoload not found --SKIPIF-- --FILE-- getMessage(), 'Class "SwooleTestClassA2" not found'); echo "DONE\n"; } }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/autoload_not_found_not_in_coroutine.phpt ================================================ --TEST-- swoole_coroutine: autoload not found and not in coroutine --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Error: Class "SwooleTestClassA2" not found in %s:%d Stack trace: #0 %s(%d): %s #1 %s(%d): %s #2 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/autoload_not_in_coroutine.phpt ================================================ --TEST-- swoole_coroutine: autoload not in coroutine --SKIPIF-- --FILE-- --EXPECTF-- object(SwooleTestClassA)#%d (0) { } object(SwooleTestClassA)#%d (0) { } ================================================ FILE: tests/swoole_coroutine/bailout/co_redis_in_shutdown_function.phpt ================================================ --TEST-- swoole_coroutine/bailout: call co redis in shutdown function --SKIPIF-- --FILE-- connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); register_shutdown_function(function () use ($redis) { $redis->get('key'); }); usleep(10000); }); Event::wait(); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: API must be called in the coroutine in %s:%d Stack trace: #0 %s(%d): Redis->get('key') #1 [internal function]: {closure%S}() #2 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/bailout/error.phpt ================================================ --TEST-- swoole_coroutine/bailout: error --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Error in %s:%d Stack trace: %A thrown in %s on line %d shutdown ================================================ FILE: tests/swoole_coroutine/bailout/error_in.phpt ================================================ --TEST-- swoole_coroutine/bailout: error in the coroutine --SKIPIF-- --FILE-- connect('192.0.0.1', 80); defer($func); defer('bbb'); defer(function () use ($func) { echo 'ccc' . PHP_EOL; var_dump($func); }); }); function bbb() { echo 'bbb' . PHP_EOL; } go(function () { $fp = stream_socket_client("tcp://127.0.0.1:3306", $errno, $errstr, 1); echo fread($fp, 8192) . PHP_EOL; }); go(function () { a(); }); Event::wait(); ?> --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function a() in %s:%d Stack trace: %A thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/bailout/error_internal.phpt ================================================ --TEST-- swoole_coroutine/bailout: error --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted %s --EXPECTF_85-- Fatal error: Allowed memory size of %d bytes exhausted %s Stack trace: #0 %s(%d): str_repeat('A', %d) #1 [internal function]: {%s:%d}() #2 {main} ================================================ FILE: tests/swoole_coroutine/bailout/error_internal2.phpt ================================================ --TEST-- swoole_coroutine/bailout: error --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted %s --EXPECTF_85-- Fatal error: Allowed memory size of %d bytes exhausted %s Stack trace: #0 %s(%d): str_repeat('A', %d) #1 [internal function]: {closure:{closure:%s:%d}:%d}() #2 {main} ================================================ FILE: tests/swoole_coroutine/bailout/error_out.phpt ================================================ --TEST-- swoole_coroutine/bailout: error out of the coroutine --SKIPIF-- --FILE-- connect('192.0.0.1', 80); defer($func); defer('bbb'); defer(function () use ($func) { echo 'ccc' . PHP_EOL; var_dump($func); }); }); function bbb() { echo 'bbb' . PHP_EOL; } go(function () { $fp = stream_socket_client("tcp://127.0.0.1:3306", $errno, $errstr, 1); echo fread($fp, 8192) . PHP_EOL; }); a(); Event::wait(); ?> --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function a() in %s:%d Stack trace: #0 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/bailout/exit.phpt ================================================ --TEST-- swoole_coroutine/bailout: error --SKIPIF-- --FILE-- getMessage() . "\n"; } }); }); $process->start(); $status = $process::wait(); if (Assert::isArray($status)) { list($pid, $code, $signal) = array_values($status); Assert::greaterThan($pid, 0); Assert::same($code, 0); Assert::same($signal, 0); } ?> --EXPECT-- swoole exit shutdown ================================================ FILE: tests/swoole_coroutine/before_create_server_1.phpt ================================================ --TEST-- swoole_coroutine: co::create before server create [1] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::notEmpty($data); Assert::same($data, SECRET); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { Co::sleep(0.1); $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', "worker_num" => 1, ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(SECRET); }); $http->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/before_create_server_2.phpt ================================================ --TEST-- swoole_coroutine: co::create before server create [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::notEmpty(!empty($data)); Assert::same($data, SECRET); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { co::sleep(0.1); echo "co shutdown\n"; }); Swoole\Event::wait(); $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', "worker_num" => 1, ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(SECRET); }); $http->start(); echo "server shutdown\n"; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- co shutdown server shutdown ================================================ FILE: tests/swoole_coroutine/before_create_server_3.phpt ================================================ --TEST-- swoole_coroutine: co::create before server create [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::notEmpty(!empty($data)); Assert::same($data, SECRET); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { Co::readFile(__FILE__); echo "co shutdown\n"; }); Swoole\Event::wait(); $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', "worker_num" => 1, ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(SECRET); }); $http->start(); echo "server shutdown\n"; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- co shutdown server shutdown ================================================ FILE: tests/swoole_coroutine/bug_2387.phpt ================================================ --TEST-- swoole_coroutine: call_user_func_array --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/list"); Assert::assert(!empty($data) && count(json_decode($data, true)) > 0); $pm->kill(); }); \Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $config = [ 'host' => MYSQL_SERVER_HOST, 'port' => MYSQL_SERVER_PORT, 'user' => MYSQL_SERVER_USER, 'password' => MYSQL_SERVER_PWD, 'database' => MYSQL_SERVER_DB, 'timeout' => 0.5, 'charset' => 'utf8mb4', 'strict_type' => true, 'pool_size' => '3', 'pool_get_timeout' => 0.5, ]; $httpServer = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $httpServer->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'hook_flags' => SWOOLE_HOOK_ALL, ]); $httpServer->on('WorkerStart', function (Swoole\Http\Server $server) use ($pm, $config) { try { MysqlPool::getInstance($config); } catch (\Exception $e) { echo $e->getMessage() . PHP_EOL; $server->shutdown(); } catch (\Throwable $throwable) { echo $throwable->getMessage() . PHP_EOL; $server->shutdown(); } $pm->wakeup(); }); $httpServer->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { if ($request->server['path_info'] == '/list') { go(function () use ($request, $response) { try { $pool = MysqlPool::getInstance(); $mysql = $pool->get(); defer(function () use ($mysql) { MysqlPool::getInstance()->put($mysql); echo "size = " . MysqlPool::getInstance()->getLength() . PHP_EOL; }); $result = $mysql->query("show tables"); $response->end(json_encode($result)); } catch (\Exception $e) { $response->end($e->getMessage()); } }); } }); $httpServer->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- size = 3 ================================================ FILE: tests/swoole_coroutine/bug_5699.phpt ================================================ --TEST-- swoole_coroutine: Github bug #5699 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/c_stack_size.phpt ================================================ --TEST-- swoole_coroutine: c_stack_size --SKIPIF-- --FILE-- 1 * M]); for ($n = 100; $n--;) { go(function () { co::sleep(0.001); }); } // echo "1M\n"; $info = co::stats(); Assert::assert($info['c_stack_size'] == M); Assert::assert($info['coroutine_num'] == 100); Assert::assert($info['coroutine_peak_num'] == 100); co::set(['c_stack_size' => 1 * K]); // will be extend for ($n = 100; $n--;) { go(function () { co::sleep(0.001); }); } // echo "256K\n"; $info = co::stats(); Assert::assert($info['c_stack_size'] == 64 * K); Assert::assert($info['coroutine_num'] == 200); Assert::assert($info['coroutine_peak_num'] == 200); co::set(['c_stack_size' => 511 * K]); // will be aligned for ($n = 100; $n--;) { go(function () { co::sleep(0.001); }); } // echo "512K\n"; $info = co::stats(); Assert::assert($info['c_stack_size'] == 512 * K); Assert::assert($info['coroutine_num'] == 300); Assert::assert($info['coroutine_peak_num'] == 300); co::set(['c_stack_size' => 1 * G]); // will be limit for ($n = 100; $n--;) { go(function () { co::sleep(0.001); }); } // echo "16M\n"; $info = co::stats(); Assert::assert($info['c_stack_size'] == 16 * M); Assert::assert($info['coroutine_num'] == 400); Assert::assert($info['coroutine_peak_num'] == 400); co::set(['c_stack_size' => -1]); // will be limit for ($n = 100; $n--;) { go(function () { co::sleep(0.001); }); } // echo "16M\n"; $info = co::stats(); Assert::assert($info['c_stack_size'] == 16 * M); Assert::assert($info['coroutine_num'] == 500); Assert::assert($info['coroutine_peak_num'] == 500); ?> DONE --EXPECTF-- DONE ================================================ FILE: tests/swoole_coroutine/call_not_exists_func.phpt ================================================ --TEST-- swoole_coroutine: call_not_exists_func --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { Assert::throws(function () use ($pm) { httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); }, Exception::class); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1]); $http->on('workerStart', function () use ($pm) { none(); $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { co::sleep(0.001); throw new Exception('whoops'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function none() in %s:%d Stack trace: %A thrown in %s on line %d [%s] ERROR php_swoole_server_rshutdown() (ERRNO %d): Fatal error: Uncaught Error: Call to undefined function none() in %s:%d Stack trace: %A thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/call_user_func_array.phpt ================================================ --TEST-- swoole_coroutine: call_user_func_array --SKIPIF-- --FILE-- --EXPECT-- foo bar ================================================ FILE: tests/swoole_coroutine/call_user_func_array2.phpt ================================================ --TEST-- swoole_coroutine: call_user_func_array 2 --SKIPIF-- --FILE-- --EXPECT-- bar end ================================================ FILE: tests/swoole_coroutine/call_with_args.phpt ================================================ --TEST-- swoole_coroutine: coro with args --SKIPIF-- --FILE-- test; foreach ($args as $index => $arg) { $recv = new Chan; $send = new Chan; $data = $args[$index]; go(function () use ($recv, $data) { co::sleep(0.001); $recv->push($data); // response }); go(function () use ($send, $data) { $data = $send->pop(); if (Assert::assert($data === $data)) { co::sleep(0.001); $send->push(true); // send ok } }); $ret = $send->push($data); Assert::assert($ret); $response = $recv->pop(); Assert::same($response, $data); } } } $php_args = [ 'undef', null, true, false, 1, 1.1, str_repeat('exit', 1), array_merge(['exit' => 'ok'], []), (object)['exit' => 'ok'], STDIN, 0 ]; go([new TestCo, 'foo'], ...$php_args); ?> --EXPECTF-- ================================================ FILE: tests/swoole_coroutine/callback.phpt ================================================ --TEST-- swoole_coroutine: coro callback --SKIPIF-- --FILE-- test; } } for ($c = MAX_CONCURRENCY; $c--;) { go([new TestCo, 'foo']); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_coroutine/cancel/channel_pop.phpt ================================================ --TEST-- swoole_coroutine/cancel: pop from channel --SKIPIF-- --FILE-- pop(100), false); Assert::assert(Coroutine::isCanceled()); Assert::eq($chan->errCode, SWOOLE_CHANNEL_CANCELED); echo "Done\n"; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/channel_push.phpt ================================================ --TEST-- swoole_coroutine/cancel: push to channel --SKIPIF-- --FILE-- push("hello world [1]", 100)); Assert::eq(Coroutine::isCanceled(), false); Assert::eq($chan->errCode, SWOOLE_CHANNEL_OK); Assert::eq($chan->push("hello world [2]", 100), false); Assert::eq(Coroutine::isCanceled(), true); Assert::eq($chan->errCode, SWOOLE_CHANNEL_CANCELED); echo "Done\n"; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/error.phpt ================================================ --TEST-- swoole_coroutine/cancel: error --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/cancel/gethostbyname.phpt ================================================ --TEST-- swoole_coroutine/cancel: gethostbyname --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/kill.phpt ================================================ --TEST-- swoole_coroutine/cancel: kill --SKIPIF-- --FILE-- --EXPECT-- co 2 running co 2 running co 1 end ================================================ FILE: tests/swoole_coroutine/cancel/sleep.phpt ================================================ --TEST-- swoole_coroutine/cancel: sleep --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/socket.phpt ================================================ --TEST-- swoole_coroutine/cancel: socket --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); // server $cid = go(function () use ($socket) { while (true) { $peer = null; $data = $socket->recvfrom($peer); Assert::assert(empty($data)); Assert::assert($socket->errCode == SOCKET_ECANCELED); break; } echo "DONE\n"; }); // client co::sleep(0.1); Assert::true(Coroutine::cancel($cid)); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/cancel/suspend.phpt ================================================ --TEST-- swoole_coroutine/cancel: suspend --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/throw_exception.phpt ================================================ --TEST-- swoole_coroutine/cancel: throw exception --SKIPIF-- --FILE-- --EXPECT-- co 2 running co 2 running string(9) "cancelled" co 1 end ================================================ FILE: tests/swoole_coroutine/cancel/wait.phpt ================================================ --TEST-- swoole_coroutine/cancel: wait/waitpid --SKIPIF-- --FILE-- start(); run(function () use ($proc) { $cid = Coroutine::getCid(); go(function () use ($cid) { System::sleep(0.002); Assert::true(Coroutine::cancel($cid)); }); $retval = System::wait(); echo "Done\n"; Process::kill($proc->pid, SIGKILL); Assert::eq($retval, false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_CO_CANCELED); }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/wait_event.phpt ================================================ --TEST-- swoole_coroutine/cancel: waitEvent --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/cancel/wait_signal.phpt ================================================ --TEST-- swoole_coroutine/cancel: waitSignal --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/check.phpt ================================================ --TEST-- swoole_coroutine: check if is in the coroutine --SKIPIF-- --FILE-- push('foo'); $chan->push('bar'); Assert::assert(0); // never here }, function () { (new Chan)->pop(); Assert::assert(0); // never here }, function () { Co::readFile(__FILE__); Assert::assert(0); // never here }, function () { Co::writeFile(TEST_LOG_FILE, 'foo'); Assert::assert(0); // never here }, function () { Co::gethostbyname('www.swoole.com'); Assert::assert(0); // never here }, function () { Co::getaddrinfo('www.swoole.com'); Assert::assert(0); // never here }, // function () { // Co::statvfs(__DIR__); // }, function () { Co::exec('echo'); Assert::assert(0); // never here }, function () { swoole_async_dns_lookup_coro('127.0.0.1'); Assert::assert(0); // never here }, function () { (new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP))->connect('127.0.0.1', 1234); Assert::assert(0); // never here }, function () { (new Co\Client(SWOOLE_SOCK_TCP))->connect('127.0.0.1', 1234); Assert::assert(0); // never here }, function () { (new Co\Http\Client('127.0.0.1', 1234))->get('/'); Assert::assert(0); // never here }, ]; if (class_exists(Co\Http2\Client::class)) { $map[] = function () { (new Co\Http2\Client('127.0.0.1', 1234))->connect(); Assert::assert(0); // never here }; } $info_list = []; foreach ($map as $i => $f) { $GLOBALS['f'] = $f; if (RUN_IN_CHILD == false) { $f(); continue; } $process = new Swoole\Process(function () { function a() { b(); } function b() { c(); } function c() { try { $GLOBALS['f'](); } catch (Error $e) { // can not be caught } } a(); }, true); $process->start(); $info = $process->read(8192); $process::wait(); if (Assert::contains($info, 'Swoole\\Error')) { $_info = trim($info); if (PHP_VERSION_ID >= 80400) { $_info = preg_replace('/(\#0.+?: )[^\n]+/', '$1%s', $_info, 1); $_info = preg_replace('/(: )[^\n]+( in )/', '$1%s$2', $_info, 1); $_info = preg_replace('/closure:[^(:]+:?\(?\d+\)?/', 'closure', $_info); $_info = preg_replace('/\/[^(:]+:?\(?\d+\)?/', '%s:%d', $_info); } else { $_info = preg_replace('/(\#0.+?: )[^\n]+/', '$1%s', $_info, 1); $_info = preg_replace('/(: )[^\n]+( in )/', '$1%s$2', $_info, 1); $_info = preg_replace('/\/[^(:]+:?\(?\d+\)?/', '%s:%d', $_info); } $info_list[] = $_info; if (!Assert::assert($info_list[0] === $_info)) { var_dump($map[$i]); var_dump($info_list[0]); var_dump($info); exit; } } } echo current($info_list); Swoole\Event::wait(); ?> --EXPECT-- Fatal error: %s in %s:%d Stack trace: #0 %s:%d: %s #1 %s:%d: {closure}() #2 %s:%d: c() #3 %s:%d: b() #4 %s:%d: a() #5 [internal function]: {closure}(Object(Swoole\Process)) #6 %s:%d: Swoole\Process->start() #7 {main} thrown in %s:%d ================================================ FILE: tests/swoole_coroutine/cid.phpt ================================================ --TEST-- swoole_coroutine: cid increment --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/create_after_rshutdown.phpt ================================================ --TEST-- swoole_coroutine: create coroutine after RSHTUDOWN --SKIPIF-- --FILE-- --EXPECTF-- DONE ================================================ FILE: tests/swoole_coroutine/current.phpt ================================================ --TEST-- swoole_coroutine: current cid --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dead_lock.phpt ================================================ --TEST-- swoole_coroutine: dead lock --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($n = 3; $n--;) { $ret = Process::wait(false); Assert::isEmpty($ret); switch_process(); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $pm->wakeup(); Coroutine::set([ 'exit_condition' => function () { return Coroutine::stats()['coroutine_num'] === 0; } ]); Coroutine::create(function () { $channel = new Coroutine\Channel; $channel->pop(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/defer/defer.phpt ================================================ --TEST-- swoole_coroutine/defer: coro defer --SKIPIF-- --FILE-- --EXPECT-- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ================================================ FILE: tests/swoole_coroutine/defer/defer_close.phpt ================================================ --TEST-- swoole_coroutine/defer: coro defer --SKIPIF-- --FILE-- resource = null; } }; defer(function () use ($obj) { $obj->close(); }); $obj->resource = $file = fopen(__FILE__, 'r+'); defer(function () use ($obj) { Assert::assert(is_resource($obj->resource)); fclose($obj->resource); echo "closed\n"; }); throw new Exception('something wrong'); echo "never here\n"; }); Swoole\Event::wait(); ?> --EXPECTF-- Fatal error: Uncaught Exception: something wrong in %s:%d Stack trace: %A thrown in %s on line %d closed ================================================ FILE: tests/swoole_coroutine/defer/defer_exception.phpt ================================================ --TEST-- swoole_coroutine/defer: coro defer with exception --SKIPIF-- --FILE-- --EXPECTF-- 3 zha Fatal error: Uncaught Exception: something wrong in %s:%d Stack trace: %A thrown in %s/tests/swoole_coroutine/defer/defer_exception.php on line %d defer 2 defer 1 ================================================ FILE: tests/swoole_coroutine/defer/defer_in_try.phpt ================================================ --TEST-- swoole_coroutine/defer: coro defer --SKIPIF-- --FILE-- resource = null; } }; defer(function () use ($obj) { $obj->close(); }); $obj->resource = $file = fopen(__FILE__, 'r+'); defer(function () use ($obj) { Assert::assert(is_resource($obj->resource)); fclose($obj->resource); echo "closed\n"; }); throw new Exception('something wrong'); echo "never here\n"; } catch (Exception $e) { echo "catch it\n"; } finally { echo "finally done\n"; } }); ?> --EXPECT-- catch it finally done closed ================================================ FILE: tests/swoole_coroutine/destruct/destruct1.phpt ================================================ --TEST-- swoole_coroutine/destruct: destruct1 --SKIPIF-- --FILE-- test(); unset($t); echo "end\n"; ?> --EXPECT-- call function coro start end coro exit ================================================ FILE: tests/swoole_coroutine/destruct/destruct2.phpt ================================================ --TEST-- swoole_coroutine/destruct: destruct2 --SKIPIF-- --FILE-- test(); echo "end\n"; ?> --EXPECTF-- call function end Fatal error: go(): can not use coroutine in __destruct after php_request_shutdown %s --EXPECTF_85-- call function end Fatal error: go(): can not use coroutine in __destruct after php_request_shutdown %s Stack trace: #0 %s(%d): go(Object(Closure)) #1 [internal function]: T2->__destruct() #2 {main} ================================================ FILE: tests/swoole_coroutine/destruct/destruct3.phpt ================================================ --TEST-- swoole_coroutine/destruct: destruct 3 --SKIPIF-- --FILE-- dtor(); } }; Co\go(function () use ($t) { Co::sleep(0.01); $t->test(); $GLOBALS['obj'] = $t; }); Swoole\Event::wait(); ?> --EXPECTF-- test dtor Fatal error: Uncaught Exception: error in %s:%d Stack trace: #0 [internal function]: class@anonymous->{closure%S() #1 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/dnslookup_1.phpt ================================================ --TEST-- swoole_coroutine: swoole_async_dns_lookup_coro --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_2.phpt ================================================ --TEST-- swoole_coroutine: async dns lookup timeout --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_3.phpt ================================================ --TEST-- swoole_coroutine: async dns lookup [3] --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_4.phpt ================================================ --TEST-- swoole_coroutine: bad dns server --SKIPIF-- --FILE-- '192.0.0.1:10053']); $host = Swoole\Coroutine::dnsLookup('www.baidu.com', 0.5); Assert::eq($host, false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_5.phpt ================================================ --TEST-- swoole_coroutine: async dns lookup [5] --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_ipv6.phpt ================================================ --TEST-- swoole_coroutine: dns Lookup IPv6 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/dnslookup_query_hosts.phpt ================================================ --TEST-- swoole_coroutine: dnslookup query hosts --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/empty.phpt ================================================ --TEST-- swoole_coroutine: coro empty --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[1] exit ================================================ FILE: tests/swoole_coroutine/eval.phpt ================================================ --TEST-- swoole_coroutine: eval --SKIPIF-- --FILE-- --EXPECT-- start 1 start 2 main end end 1 end 2 ================================================ FILE: tests/swoole_coroutine/exception/core_error.phpt ================================================ --TEST-- swoole_coroutine/exception: fatal error --SKIPIF-- --FILE-- --EXPECTF-- Parse error: syntax error, unexpected identifier "xde" in %s on line %d shutdown ================================================ FILE: tests/swoole_coroutine/exception/defer1.phpt ================================================ --TEST-- swoole_coroutine/exception: defer 1 --SKIPIF-- --FILE-- start(); $status = Process::wait(); if (Assert::isArray($status)) { list($pid, $code, $signal) = array_values($status); Assert::greaterThan($pid, 0); $out = $process->read(); Assert::contains($out, 'co-1 begin'); Assert::contains($out, 'co-2 begin'); Assert::contains($out, 'defer task begin'); Assert::contains($out, 'shutdown'); Assert::contains($out, 'Fatal error: Uncaught Exception'); Assert::notContains($out, 'co-1 end'); Assert::same($code, 255); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_coroutine/exception/defer2.phpt ================================================ --TEST-- swoole_coroutine/exception: defer 2 --SKIPIF-- --FILE-- --EXPECTF-- co-1 begin co-2 begin co-2 end defer task begin Fatal error: Uncaught Exception in %s:%d Stack trace: #0 [internal function]: {closure%S}(NULL) #1 {main} thrown in %s on line %d shutdown ================================================ FILE: tests/swoole_coroutine/exception/dtor.phpt ================================================ --TEST-- swoole_coroutine/exception: throw in destructor --SKIPIF-- --FILE-- --EXPECTF-- co-1 begin test Fatal error: Uncaught Exception in %s:%d Stack trace: #0 %s(%d): class@anonymous->__destruct() %A thrown in %s on line %d shutdown ================================================ FILE: tests/swoole_coroutine/exception/empty.phpt ================================================ --TEST-- swoole_coroutine/exception: IO empty Exception --SKIPIF-- --FILE-- getMessage(), "\n"; } finally { echo "finally.\n"; } }); echo "end\n"; ?> --EXPECT-- start Caught exception: coro Exception finally. end ================================================ FILE: tests/swoole_coroutine/exception/error.phpt ================================================ --TEST-- swoole_coroutine/exception: error --SKIPIF-- --FILE-- --EXPECTF-- start Fatal error: Uncaught Exception: coro Exception %s Stack trace: %A thrown in %s/tests/swoole_coroutine/exception/error.php on line %d shutdown ================================================ FILE: tests/swoole_coroutine/exception/error1.phpt ================================================ --TEST-- swoole_coroutine/exception: double catch --SKIPIF-- --FILE-- getMessage(), "\n"; } finally { echo "finally in.\n"; } }); } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n"; } finally { echo "finally out.\n"; } echo "end\n"; ?> --EXPECT-- start Caught exception: coro Exception finally in. finally out. end ================================================ FILE: tests/swoole_coroutine/exception/error2.phpt ================================================ --TEST-- swoole_coroutine/exception: zend_error --SKIPIF-- --FILE-- 1, 'location' => $wsdl, 'features' => 1, 'exceptions' => true, ], []); $client = new \SoapClient($wsdl, $option); } catch (\Exception $e) { echo $e->getMessage(); } }); echo "end\n"; ?> --EXPECTF-- start SOAP-ERROR: Parsing WSDL: Couldn't load from '%s' : failed to load external entity "%s" end ================================================ FILE: tests/swoole_coroutine/exception/error3.phpt ================================================ --TEST-- swoole_coroutine/exception: internal_function error --SKIPIF-- --FILE-- --FILE-- start(); $status = Process::wait(); if (Assert::isArray($status)) { list($pid, $code, $signal) = array_values($status); Assert::greaterThan($pid, 0); $out = $process->read(); Assert::contains($out, 'Uncaught Error: Call to undefined function call_func_not_exists()'); Assert::contains($out, 'shutdown'); Assert::notContains($out, 'co end'); Assert::same($code, 255); } ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine/exception/nested.phpt ================================================ --TEST-- swoole_coroutine/exception: exception before yield --SKIPIF-- --FILE-- getMessage(), "\n"; } finally { echo "finally 2\n"; } }); echo "after go1 sleep\n"; } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n"; } finally { echo "finally 1\n"; } }); echo "end\n"; ?> --EXPECT-- start sub start Caught exception: sub coro Exception finally 2 after go1 sleep finally 1 end ================================================ FILE: tests/swoole_coroutine/exception/yield.phpt ================================================ --TEST-- swoole_coroutine/exception: exception after yield --SKIPIF-- --FILE-- getMessage(), "\n"; } finally { echo "finally.\n"; } }); echo "end\n"; ?> --EXPECT-- start end after sleep Caught exception: coro Exception finally. ================================================ FILE: tests/swoole_coroutine/exception/yield1.phpt ================================================ --TEST-- swoole_coroutine/exception: exception before yield --SKIPIF-- --FILE-- getMessage(), "\n"; } finally { echo "finally.\n"; } }); echo "end\n"; ?> --EXPECT-- start Caught exception: coro Exception finally. end ================================================ FILE: tests/swoole_coroutine/exception.phpt ================================================ --TEST-- swoole_coroutine: throw exception --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { co::sleep(0.001); throw new Exception('whoops'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: Uncaught Exception: whoops in %s:%d Stack trace: %A thrown in %s on line %d [%s] ERROR php_swoole_server_rshutdown() (ERRNO %d): Fatal error: Uncaught Exception: whoops in %s:%d Stack trace: %A thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/execute_time.phpt ================================================ --TEST-- swoole_coroutine: getExecuteTime --SKIPIF-- --FILE-- 0) { $a = 9999 ^ 10000; $i--; } $execution_time = Swoole\Coroutine::getExecuteTime(); go(function(){ $time = 2; Swoole\Runtime::enableCoroutine($flags = false); sleep($time); $execution_time = Swoole\Coroutine::getExecuteTime(); Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL); sleep($time); Assert::assert(Swoole\Coroutine::getExecuteTime() - $execution_time < 1000); }); go(function(){ $time = 2; Swoole\Runtime::enableCoroutine($flags = false); sleep($time); $execution_time = Swoole\Coroutine::getExecuteTime(); Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL); sleep($time); go(function(){ $time = 2; Swoole\Runtime::enableCoroutine($flags = false); sleep($time); $execution_time = Swoole\Coroutine::getExecuteTime(); Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL); sleep($time); Assert::assert(Swoole\Coroutine::getExecuteTime() - $execution_time < 1000); }); Assert::assert(Swoole\Coroutine::getExecuteTime() - $execution_time < 1000); }); Assert::assert(Swoole\Coroutine::getExecuteTime() - $execution_time < 1000); echo 'DONE'; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/exists.phpt ================================================ --TEST-- swoole_coroutine: exists --SKIPIF-- --FILE-- --EXPECT-- bool(true) bool(false) bool(false) ================================================ FILE: tests/swoole_coroutine/exit.phpt ================================================ --TEST-- swoole_coroutine: exit --SKIPIF-- --FILE-- --EXPECTF-- NULL NULL bool(true) bool(false) int(1) float(1.1) string(4) "exit" array(1) { ["exit"]=> string(2) "ok" } object(stdClass)#%d (%d) { ["exit"]=> string(2) "ok" } resource(%d) of type (stream) int(0) ================================================ FILE: tests/swoole_coroutine/exit_84.phpt ================================================ --TEST-- swoole_coroutine: exit --SKIPIF-- --FILE-- --EXPECTF-- int(1) string(4) "exit" int(0) ================================================ FILE: tests/swoole_coroutine/exit_exception_backtrace.phpt ================================================ --TEST-- swoole_coroutine: exit exception backtrace --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Swoole\ExitException: swoole exit in %s/tests/swoole_coroutine/exit_exception_backtrace.php:%d Stack trace: #0 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): char(%d) #1 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): bar('%s...') #2 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): foo() %A thrown in %s/tests/swoole_coroutine/exit_exception_backtrace.php on line %d ================================================ FILE: tests/swoole_coroutine/exit_exception_backtrace_84.phpt ================================================ --TEST-- swoole_coroutine: exit exception backtrace --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Swoole\ExitException: swoole exit in %s:%d Stack trace: #0 %s(%d): exit() #1 %s(%d): char(%d) #2 %s(%d): bar('%s') #3 %s(%d): foo() #4 [internal function]: {closure:%s:%d}() #5 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/fatal_error.phpt ================================================ --TEST-- swoole_coroutine: fatal error --SKIPIF-- --FILE-- true, ]); register_shutdown_function(function () { echo "shutdown\n"; }); Co\run(function () { test_not_found(); }); echo "DONE\n"; ?> --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function test_not_found() in %s:%d Stack trace: %A thrown in %s on line %d shutdown ================================================ FILE: tests/swoole_coroutine/forbidden_case/array_map.phpt ================================================ --TEST-- swoole_coroutine/forbidden_case: coro array map --SKIPIF-- --FILE-- --EXPECT-- func start main end func end co end ================================================ FILE: tests/swoole_coroutine/forbidden_case/call_user.phpt ================================================ --TEST-- swoole_coroutine/forbidden_case: coro call user func --SKIPIF-- --FILE-- --EXPECT-- func start main end func end co end ================================================ FILE: tests/swoole_coroutine/forbidden_case/invoke.phpt ================================================ --TEST-- swoole_coroutine/forbidden_case: coro invoke --SKIPIF-- --FILE-- invoke(); echo "co end\n"; }); function foo() { echo "func start\n"; co::sleep(.001); echo "func end\n"; } echo "main end\n"; ?> --EXPECT-- func start main end func end co end ================================================ FILE: tests/swoole_coroutine/getContext.phpt ================================================ --TEST-- swoole_coroutine: getContext --SKIPIF-- --FILE-- resource2 = new Resource; func(function () { Co::getContext()['resource3'] = new Resource; Co::yield(); Co::getContext()['resource3']->resource4 = new Resource; Co::getContext()->resource5 = new Resource; }); }); Co::resume(2); ?> --EXPECT-- Resource#1 constructed Resource#2 constructed Resource#3 constructed Coroutine#1 exit Resource#2 destructed Resource#1 destructed Resource#4 constructed Resource#5 constructed Coroutine#2 exit Resource#5 destructed Resource#3 destructed Resource#4 destructed ================================================ FILE: tests/swoole_coroutine/getElasped.phpt ================================================ --TEST-- swoole_coroutine: getElapsed --SKIPIF-- --FILE-- --EXPECT-- int(-1) int(-1) bool(true) ================================================ FILE: tests/swoole_coroutine/getPcid.phpt ================================================ --TEST-- swoole_coroutine: getPcid --SKIPIF-- --FILE-- --EXPECT-- bool(false) int(-1) int(1) int(2) int(3) int(3) int(3) int(1) int(-1) bool(false) ================================================ FILE: tests/swoole_coroutine/getPcid_by_random_cid.phpt.phpt ================================================ --TEST-- swoole_coroutine: getPcid by random cid --SKIPIF-- --FILE-- --EXPECT-- bool(false) bool(false) int(-1) int(-1) bool(false) int(1) int(-1) int(2) int(-1) int(1) int(2) bool(false) int(1) bool(false) bool(false) ================================================ FILE: tests/swoole_coroutine/gethostbyname.phpt ================================================ --TEST-- swoole_coroutine: gethostbyname and dns cache --SKIPIF-- --FILE-- null, 'www.youtube.com' => null, 'www.facebook.com' => null, ] : [ 'www.baidu.com' => null, 'www.taobao.com' => null, 'www.qq.com' => null, ]; $first_time = microtime(true); for ($n = MAX_CONCURRENCY_LOW; $n--;) { foreach ($map as $host => &$ip) { $ip = System::gethostbyname($host); Assert::assert(preg_match(IP_REGEX, $ip)); } } unset($ip); $first_time = microtime(true) - $first_time; phpt_var_dump($map); $cache_time = microtime(true); for ($n = MAX_CONCURRENCY_LOW; $n--;) { foreach ($map as $host => $ip) { $_ip = System::gethostbyname($host); Assert::same($ip, $_ip); } } $cache_time = microtime(true) - $cache_time; $no_cache_time = microtime(true); for ($n = MAX_CONCURRENCY_LOW; $n--;) { swoole_clear_dns_cache(); $ip = System::gethostbyname(array_rand($map)); Assert::assert(preg_match(IP_REGEX, $ip)); } $no_cache_time = microtime(true) - $no_cache_time; $chan = new Chan(MAX_CONCURRENCY_LOW); $no_cache_multi_time = microtime(true); for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($map, $chan) { swoole_clear_dns_cache(); $ip = System::gethostbyname(array_rand($map)); Assert::assert(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)); $chan->push(Assert::assert(preg_match(IP_REGEX, $ip))); }); } for ($c = MAX_CONCURRENCY_LOW; $c--;) { $chan->pop(); } $no_cache_multi_time = microtime(true) - $no_cache_multi_time; phpt_var_dump($first_time, $cache_time, $no_cache_time, $no_cache_multi_time); if (!IS_IN_CI) { Assert::assert($cache_time < 0.01); Assert::assert($cache_time < $first_time); Assert::assert($cache_time < $no_cache_time); Assert::assert($cache_time < $no_cache_multi_time); Assert::assert($no_cache_multi_time < $no_cache_time); } }); ?> --EXPECTF-- ================================================ FILE: tests/swoole_coroutine/iterator.phpt ================================================ --TEST-- swoole_coroutine: iterator --SKIPIF-- --FILE-- current()); $i->next(); var_dump($i->current()); $i->rewind(); go(function () use ($i) { Co::sleep(0.1); // 1 var_dump($i->current()); $i->next(); var_dump($i->current()); // 3 $i3 = Co::list(); var_dump($i3->current()); $i3->next(); var_dump($i3->current()); }); // 2 $i2 = Co::list(); var_dump($i2->current()); $i2->next(); var_dump($i2->current()); $i2->rewind(); }); ?> --EXPECT-- int(1) NULL int(2) int(1) int(1) NULL int(2) NULL ================================================ FILE: tests/swoole_coroutine/join/1.phpt ================================================ --TEST-- swoole_coroutine/join: 1 --SKIPIF-- --FILE-- --EXPECT-- ALL DONE ================================================ FILE: tests/swoole_coroutine/join/2.phpt ================================================ --TEST-- swoole_coroutine/join: 2 --SKIPIF-- --FILE-- --EXPECT-- ALL DONE ================================================ FILE: tests/swoole_coroutine/join/3.phpt ================================================ --TEST-- swoole_coroutine/join: 3 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/join/4.phpt ================================================ --TEST-- swoole_coroutine/join: 4 --SKIPIF-- --FILE-- --EXPECT-- DEFER CALLBACK DONE ================================================ FILE: tests/swoole_coroutine/join/5.phpt ================================================ --TEST-- swoole_coroutine/join: 5 --SKIPIF-- --FILE-- --EXPECT-- DONE 2 DONE 1 ================================================ FILE: tests/swoole_coroutine/join/6.phpt ================================================ --TEST-- swoole_coroutine/join: 6 --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/join/7.phpt ================================================ --TEST-- swoole_coroutine/join: 7 --SKIPIF-- --FILE-- --EXPECT-- DEFER CALLBACK DONE ================================================ FILE: tests/swoole_coroutine/join/8.phpt ================================================ --TEST-- swoole_coroutine/join: 8 --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/kernel_coroutine.phpt ================================================ --TEST-- swoole_coroutine: kernel coroutine --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_coroutine/list_and_backtrace.phpt ================================================ --TEST-- swoole_coroutine: getBackTrace form listCoroutines --SKIPIF-- --FILE-- asort(); foreach ($list as $cid) { var_dump($cid); var_dump(Co::getBackTrace($cid)); } }); go(function () use ($main) { go(function () { Co::sleep(0.001); }); go(function () { Co::readFile(__FILE__); }); go(function () { Co::getaddrinfo('localhost'); }); go(function () use ($main) { Co::resume($main); }); }); }); }); }); Swoole\Event::wait(); ?> --EXPECTF-- int(1) array(%d) { %A } int(2) array(%d) { %A } int(3) array(%d) { %A } int(4) array(%d) { %A } int(5) array(%d) { %A } int(6) array(%d) { %A } int(7) array(%d) { %A } int(8) array(%d) { %A } int(9) array(%d) { %A } ================================================ FILE: tests/swoole_coroutine/max_num.phpt ================================================ --TEST-- swoole_coroutine: cid map max num --SKIPIF-- --FILE-- MAX_N ]); for ($c = MAX_N + 1; $c--;) { $ret = go(function () { co::sleep(0.001); }); } $info = co::stats(); Assert::same($info['c_stack_size'], 2097152); Assert::same($info['coroutine_num'], MAX_N); Assert::same($info['coroutine_peak_num'], MAX_N); ?> --EXPECTF-- Warning: go(): exceed max number of coroutine %d in %s/tests/swoole_coroutine/max_num.php on line %d ================================================ FILE: tests/swoole_coroutine/max_num_limit.phpt ================================================ --TEST-- swoole_coroutine: max num limit --SKIPIF-- --FILE-- --EXPECTF-- %d ================================================ FILE: tests/swoole_coroutine/nested1.phpt ================================================ --TEST-- swoole_coroutine: coro channel --SKIPIF-- --FILE-- --EXPECT-- co[1] start end co[2] start co[1] exit co[2] exit ================================================ FILE: tests/swoole_coroutine/nested2.phpt ================================================ --TEST-- swoole_coroutine: coro nested2 --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start end co[1] exit co[2] exit ================================================ FILE: tests/swoole_coroutine/nested3.phpt ================================================ --TEST-- swoole_coroutine: coro nested3 --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start end co[2] exit co[1] exit ================================================ FILE: tests/swoole_coroutine/nested_empty.phpt ================================================ --TEST-- swoole_coroutine: coro nested empty --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start co[2] exit co[1] exit ================================================ FILE: tests/swoole_coroutine/nested_uid.phpt ================================================ --TEST-- swoole_coroutine: coro nested strict --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/new_process.phpt ================================================ --TEST-- swoole_coroutine: new process --SKIPIF-- --FILE-- start(); }); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: must be forked outside the coroutine in %s:%d Stack trace: #0 %s(5): Swoole\Process->start() %A thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine/new_server.phpt ================================================ --TEST-- swoole_coroutine: new server --SKIPIF-- --FILE-- on('receive', function () { }); $server->start(); }); ?> --EXPECTF-- Warning: Swoole\Server::start(): The event-loop has already been created, unable to start %s in %s on line %d ================================================ FILE: tests/swoole_coroutine/no_inline_func.phpt ================================================ --TEST-- swoole_coroutine: coro not inline function --SKIPIF-- --FILE-- --EXPECT-- start start func end end func return func params ================================================ FILE: tests/swoole_coroutine/output/concurrency.phpt ================================================ --TEST-- swoole_coroutine/output: concurrency --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/output/create.phpt ================================================ --TEST-- swoole_coroutine/output: main output global --SKIPIF-- --FILE-- --EXPECT-- 2 1 0 ================================================ FILE: tests/swoole_coroutine/output/in_nested_co.phpt ================================================ --TEST-- swoole_coroutine/output: use ob_* in nest co --SKIPIF-- --FILE-- --EXPECT-- 1 2 3 4 ================================================ FILE: tests/swoole_coroutine/output/ob_main.phpt ================================================ --TEST-- swoole_coroutine/output: main output global --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/output/output_control.phpt ================================================ --TEST-- swoole_coroutine/output: ob_* in coroutine --SKIPIF-- --FILE-- --EXPECT-- foo bar ================================================ FILE: tests/swoole_coroutine/parallel1.phpt ================================================ --TEST-- swoole_coroutine: coro parallel1 --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start end co[1] exit co[2] exit ================================================ FILE: tests/swoole_coroutine/parallel2.phpt ================================================ --TEST-- swoole_coroutine: coro parallel2 --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start end co[2] exit co[1] exit ================================================ FILE: tests/swoole_coroutine/parallel3.phpt ================================================ --TEST-- swoole_coroutine: coro parallel3 --SKIPIF-- --FILE-- --EXPECT-- co[1] start co[2] start co[3] start end co[2] exit co[1] exit co[3] exit ================================================ FILE: tests/swoole_coroutine/pdo_error_handing.phpt ================================================ --TEST-- swoole_coroutine: error handing bug by pdo --SKIPIF-- --FILE-- --EXPECTF-- Warning: fopen(%s): %s to open stream: No such file or directory in %s on line %d ================================================ FILE: tests/swoole_coroutine/private_access.phpt ================================================ --TEST-- swoole_coroutine: $this private access in PHP70 (EG(scope)) --SKIPIF-- --FILE-- foo(); class Bar { static private $s_private = 's_private'; static protected $s_protect = 's_protect'; static public $s_public = 's_public'; private $private = 'private'; protected $protect = 'protect'; public $public = 'public'; public function foo() { \Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); go(function () { var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); co::sleep(.001); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); }); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); go(function () { var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); $mysql = new mysqli(); $res = $mysql->connect( MYSQL_SERVER_HOST, MYSQL_SERVER_USER, MYSQL_SERVER_PWD, MYSQL_SERVER_DB, MYSQL_SERVER_PORT, ); Assert::assert($res); $ret = $mysql->query('show tables', 1)->fetch_all(); Assert::assert(is_array($ret)); Assert::assert(count($ret) > 0); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); }); $cid = go(function () { var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); Co::yield(); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); }); go(function () use ($cid) { var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); Co::resume($cid); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); }); go(function () { var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); Co::sleep(0.001); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); }); var_dump(self::$s_private); var_dump(self::$s_protect); var_dump(self::$s_public); var_dump($this->private); var_dump($this->protect); var_dump($this->public); } } ?> --EXPECT-- string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" string(9) "s_private" string(9) "s_protect" string(8) "s_public" string(7) "private" string(7) "protect" string(6) "public" ================================================ FILE: tests/swoole_coroutine/resume_loop.phpt ================================================ --TEST-- swoole_coroutine: resume loop --SKIPIF-- --FILE-- 0) { Co::resume(array_shift($cos)); } }); } Co::resume(array_shift($cos)); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/scheduler.phpt ================================================ --TEST-- swoole_coroutine: coroutine scheduler --SKIPIF-- --FILE-- --EXPECT-- co1 co2 co3 co4 ================================================ FILE: tests/swoole_coroutine/signal_listener.phpt ================================================ --TEST-- swoole_coroutine: signal listener --SKIPIF-- --FILE-- false]); $pm = new ProcessManager; $pm->parentFunc = function () use ($pm) { for ($n = 3; $n--;) { $ret = Process::wait(false); Assert::isEmpty($ret); switch_process(); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $pm->wakeup(); Coroutine::set([ 'exit_condition' => function () { return Coroutine::stats()['signal_listener_num'] === 0; } ]); Process::signal(SIGTERM, function () { echo 'SIGTERM' . PHP_EOL; Process::signal(SIGTERM, null); exit(123); }); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(123); ?> --EXPECT-- SIGTERM ================================================ FILE: tests/swoole_coroutine/stats.phpt ================================================ --TEST-- swoole_coroutine: current stats --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine/timeout.phpt ================================================ --TEST-- swoole_coroutine: set timeout --SKIPIF-- --FILE-- add(); Assert::true(Coroutine::setTimeLimit(2.5)); sleep(1); $waitGroup->done(); }); go(function() use ($waitGroup) { try { $waitGroup->add(); Assert::true(Coroutine::setTimeLimit(1.5)); sleep(2); } catch (TimeoutException $e) { echo "timeout"; } finally { $waitGroup->done(); } }); $waitGroup->wait(); }); ?> --EXPECT-- timeout ================================================ FILE: tests/swoole_coroutine/use_process.phpt ================================================ --TEST-- swoole_coroutine: user process --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_eof_check' => true, 'package_eof' => "\r\n\r\n", ]); $r = $client->connect('127.0.0.1', $pm->getFreePort(), -1); if ($r === false) { echo 'ERROR'; exit; } $client->send('SUCCESS'); for ($i = 0; $i < TIMES; $i++) { $ret = $client->recv(); Assert::same(strlen($ret), SIZE + 4); } $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $proc = new Process(function ($process) use ($serv) { $data = json_decode($process->read(), true); for ($i = 0; $i < TIMES / 2; $i++) { go(function () use ($serv, $data, $i) { // echo "user sleep start\n"; co::sleep(0.01); // echo "user sleep end\n"; $serv->send($data['fd'], str_repeat('A', SIZE) . "\r\n\r\n"); // echo "user process $i send ok\n"; }); } }, false, true); $serv->addProcess($proc); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) use ($proc) { $proc->write(json_encode([ 'fd' => $fd, ])); for ($i = 0; $i < TIMES / 2; $i++) { go(function () use ($serv, $fd, $i) { // echo "worker sleep start\n"; co::sleep(0.01); // echo "worker sleep end\n"; $serv->send($fd, str_repeat('A', SIZE) . "\r\n\r\n"); // echo "worker send $i ok\n"; }); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine/user_coroutine.phpt ================================================ --TEST-- swoole_coroutine: user coroutine --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data= httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(strlen($data) > 1024); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(array( 'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Swoole\Coroutine::create(function () use ($response) { $url = 'http://news.bitauto.com/xinche/'; $components = parse_url($url); if (!isset($components['host'])) { throw new \Exception("{$url} parse no host"); } $host = $components['host']; $ip = swoole_async_dns_lookup_coro($host); $port = isset($components['port']) ? $components['port'] : 80; $client = new HttpClient($ip, $port); $client->setHeaders([ 'Host' => $host, 'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0', ]); $client->set(['timeout' => 10]); $client->get(isset($components['path']) ? $components['path'] : '/'); $response->end($client->body); }); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine/user_coroutine_2.phpt ================================================ --TEST-- swoole_coroutine: user coroutine --SKIPIF-- --FILE-- --EXPECT-- OK ================================================ FILE: tests/swoole_coroutine_lock/lock.phpt ================================================ --TEST-- swoole_coroutine_lock: lock --SKIPIF-- --FILE-- 32, 'iouring_entries' => 20000, 'iouring_flag' => SWOOLE_IOURING_SQPOLL ]); } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_ALL); run(function () use ($pm) { $waitGroup = new WaitGroup(); go(function () use ($pm, $waitGroup) { $waitGroup->add(); $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=1", []); $respData = json_decode($resp, true); var_dump($respData); $waitGroup->done(); }); go(function () use ($pm, $waitGroup) { $waitGroup->add(); $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=2", []); $respData = json_decode($resp, true); var_dump($respData); $waitGroup->done(); }); go(function () use ($pm, $waitGroup) { $waitGroup->add(); $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=3", []); $respData = json_decode($resp, true); var_dump($respData); $waitGroup->done(); }); $waitGroup->wait(); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { swoole_async_set([ 'log_file' => '/dev/null', ]); $lock = new Lock(true); Assert::false($lock->lock()); Assert::false($lock->unlock()); Assert::eq($lock->errCode, SWOOLE_ERROR_CO_OUT_OF_COROUTINE); $serv = new Server('127.0.0.1', $pm->getFreePort()); $serv->set([ 'log_file' => '/dev/null', 'worker_num' => 4, 'enable_coroutine' => true, 'hook_flags' => SWOOLE_HOOK_ALL ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($lock) { $resp->header('Content-Type', 'text/plain'); if ($req->get['value'] == 1 || $req->get['value'] == 2) { $lock->lock(); if ($req->get['value'] == 1) { sleep(1); } $resp->end(json_encode(['result' => 'lock' . $req->get['value']]) . PHP_EOL); $lock->unlock(); } else { $resp->end(json_encode(['result' => 'value 3']) . PHP_EOL); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(1) { ["result"]=> string(7) "value 3" } array(1) { ["result"]=> string(5) "lock1" } array(1) { ["result"]=> string(5) "lock2" } DONE ================================================ FILE: tests/swoole_coroutine_lock/trylock.phpt ================================================ --TEST-- swoole_coroutine_lock: trylock --SKIPIF-- --FILE-- 32, 'iouring_entries' => 20000, 'iouring_flag' => SWOOLE_IOURING_SQPOLL ]); } Runtime::enableCoroutine(SWOOLE_HOOK_ALL); run(function () { $lock = new Lock(false); Assert::eq($lock->lock(LOCK_NB), true); go(function () use ($lock) { Assert::eq($lock->lock(LOCK_NB), false); $s = microtime(true); Assert::eq($lock->lock(), true); Assert::assert(microtime(true) - $s >= 0.05); echo "co2 end\n"; }); System::sleep(0.05); Assert::eq($lock->unlock(), true); echo "co1 end\n"; }); echo "DONE\n"; ?> --EXPECT-- co1 end co2 end DONE ================================================ FILE: tests/swoole_coroutine_lock/trylock2.phpt ================================================ --TEST-- swoole_coroutine_lock: coroutine try lock --SKIPIF-- --FILE-- 32, 'iouring_entries' => 20000, 'iouring_flag' => SWOOLE_IOURING_SQPOLL ]); } $lock = new Lock(false); run(function () use ($argv, $lock) { $waitGroup = new WaitGroup(); go(function () use ($waitGroup, $lock) { $waitGroup->add(); $lock->lock(); usleep(100000); var_dump(1); $lock->unlock(); $waitGroup->done(); }); go(function () use ($waitGroup, $lock) { $waitGroup->add(); if (!$lock->lock(LOCK_NB)) { var_dump('lock failed'); } $waitGroup->done(); }); $waitGroup->wait(); }); ?> --EXPECTF-- string(11) "lock failed" int(1) ================================================ FILE: tests/swoole_coroutine_scheduler/getOptions.phpt ================================================ --TEST-- swoole_coroutine_scheduler: getOptions --SKIPIF-- --FILE-- set(['max_coroutine' => 100, 'deadlock_check_max_stack' => 64, ]); $options = $sch->getOptions(); ASsert::isArray($options); Assert::eq($options['deadlock_check_max_stack'], 64); ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine_scheduler/hook_flags.phpt ================================================ --TEST-- swoole_coroutine_scheduler: hook_flags --SKIPIF-- --FILE-- set(['hook_flags' => SWOOLE_HOOK_ALL,]); $sch->add(function ($t, $n) { usleep($t); echo "$n\n"; }, 200000, 'A'); $sch->add(function ($t, $n) { usleep($t); echo "$n\n"; }, 100000, 'B'); $sch->add(function () { var_dump(Co::getCid()); }); $sch->start(); ?> --EXPECTF-- int(%d) B A ================================================ FILE: tests/swoole_coroutine_scheduler/hook_flags_2.phpt ================================================ --TEST-- swoole_coroutine_scheduler: hook_flags --SKIPIF-- --FILE-- set(['hook_flags' => SWOOLE_HOOK_ALL,]); $sch->add(function () { Assert::contains(file_get_contents(URL), KEYWORDS); }); $sch->start(); Assert::contains(file_get_contents(URL), KEYWORDS); // 2 $sch = new Swoole\Coroutine\Scheduler(); $sch->set(['hook_flags' => SWOOLE_HOOK_ALL,]); $sch->add(function () { Assert::contains(file_get_contents(URL), KEYWORDS); }); $sch->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine_scheduler/parallel.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume1 --SKIPIF-- --FILE-- set(['max_coroutine' => 100]); $sch->parallel(10, function ($t, $n) { Co::sleep($t); echo "Co ".Co::getCid()."\n"; }, 0.05, 'A'); $sch->start(); ?> --EXPECTF-- Co %d Co %d Co %d Co %d Co %d Co %d Co %d Co %d Co %d Co %d ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/disable.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: swoole_coroutine_scheduler/disable --SKIPIF-- --FILE-- true, 'hook_flags' => 0, ]); $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec, $start) { Swoole\Coroutine::disableScheduler(); echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; $m = microtime(1); usleep(500000); if (($m-$start) * 1000 > 500) { echo "coro 1 exec more 500ms and break\n"; break; } } echo "coro 1 can exit\n"; Swoole\Coroutine::enableScheduler(); }); $end = microtime(1); $msec = ($end - $start) * 1000; go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 1 exec more 500ms and break coro 1 can exit coro 2 set flag = false end ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/disable2.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: swoole_coroutine_scheduler/disable2 --SKIPIF-- --FILE-- true]); $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec, $start) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/do-while.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: do-while with opcache enable --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(true); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(true); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/do-while2.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: do-while without opcache enable --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; do { $i++; } while ($flag); echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/do-while3.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: do-while --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default ); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/for.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: for --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; for (;;) { if (!$flag) { break; } $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/for2.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: for --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; for (; ;) { if (!$flag) { break; } $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/goto.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: goto --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; loop: $i++; if (!$flag) { goto end; } goto loop; end: echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/goto2.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: goto2 --SKIPIF-- --FILE-- true]); $default = TEST_MAX_CPU_EXEC_DURATION; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag) { echo "coro 1 start to loop\n"; $i = 0; loop: $i++; if (!$flag) { goto end; } goto loop; end: echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/timer.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: child coroutine timer --SKIPIF-- --FILE-- true]); go(function (){ $exit = false; while (true){ $res = Swoole\Coroutine::stats(); $num = $res['coroutine_num']; if ($num < 10){ go(function () use(&$exit){ Swoole\Coroutine::sleep(1); $exit = true; }); } if ($exit) { break; } } echo "coro exit\n"; }); echo "main end\n"; Swoole\Event::wait(); ?> --EXPECTF-- main end coro exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/while.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: while with opcache enable --SKIPIF-- --FILE-- true]); $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/while2.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: while without opcache enable --SKIPIF-- --FILE-- true]); $default = 10; $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/preemptive/while3.phpt ================================================ --TEST-- swoole_coroutine_scheduler/preemptive: while --SKIPIF-- --FILE-- true]); $start = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec) { echo "coro 1 start to loop\n"; $i = 0; while ($flag) { $i++; } echo "coro 1 can exit\n"; }); $end = microtime(1); $msec = ($end - $start) * 1000; USE_VALGRIND || Assert::lessThanEq(abs($msec - $max_msec), $default); go(function () use (&$flag) { echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Swoole\Event::wait(); ?> --EXPECTF-- start coro 1 start to loop coro 2 set flag = false end coro 1 can exit ================================================ FILE: tests/swoole_coroutine_scheduler/repeat.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume1 --SKIPIF-- --FILE-- --EXPECT-- scheduler 1: begin scheduler 1: end sleep: begin sleep: end scheduler 2: begin scheduler 2: end DONE ================================================ FILE: tests/swoole_coroutine_scheduler/resume1.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume1 --SKIPIF-- --FILE-- --EXPECT-- start coro 1 start to resume 1 @1 resume coro 1 @1 start to resume 1 @2 resume coro 1 @2 main ================================================ FILE: tests/swoole_coroutine_scheduler/resume2.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume2 --SKIPIF-- --FILE-- --EXPECT-- start coro 1 start coro 2 start to resume 2 resume coro 2 start to resume 1 resume coro 1 main ================================================ FILE: tests/swoole_coroutine_scheduler/resume3.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume3 --SKIPIF-- --FILE-- --EXPECT-- start coro 1 start coro 2 start to resume 1 resume coro 1 start to resume 2 resume coro 2 main ================================================ FILE: tests/swoole_coroutine_scheduler/resume4.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume4 --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Swoole\Error: API must be called in the coroutine in %s:%d Stack trace: #0 %s(3): Swoole\Coroutine::yield() #1 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_coroutine_scheduler/resume5.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume4 --SKIPIF-- --FILE-- --EXPECTF-- start to create coro coro 2 before yield main resume after yield ================================================ FILE: tests/swoole_coroutine_scheduler/resume6.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume4 --SKIPIF-- --FILE-- --EXPECTF-- coro 1 start main 1 coro 2 start coro 1 end coro 2 end main 2 ================================================ FILE: tests/swoole_coroutine_scheduler/start.phpt ================================================ --TEST-- swoole_coroutine_scheduler: user yield and resume1 --SKIPIF-- --FILE-- set(['max_coroutine' => 100]); $sch->add(function ($t, $n) { Co::sleep($t); echo "$n\n"; }, 0.2, 'A'); $sch->add(function ($t, $n) { Co::sleep($t); echo "$n\n"; }, 0.1, 'B'); $sch->add(function () { var_dump(Co::getCid()); }); $sch->start(); ?> --EXPECTF-- int(%d) B A ================================================ FILE: tests/swoole_coroutine_scheduler/start_in_server_shutdown.phpt ================================================ --TEST-- swoole_coroutine_scheduler: start in server onShutdown callback --SKIPIF-- --FILE-- set(['worker_num' => 2, 'log_level' => SWOOLE_LOG_WARNING,]); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($file) { if ($worker_id == 1) { Swoole\Timer::after(200, function () use ($server, $file) { System::sleep(0.1); echo "[1] Co " . Co::getCid() . "\n"; Assert::same(System::readFile(__FILE__), $file); $server->shutdown(); }); } }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); $server->on('shutdown', function () use ($file) { $sch = new Co\Scheduler; $sch->add(function ($t, $n) use ($file) { System::sleep($t); echo "[2] Co " . Co::getCid() . "\n"; Assert::same(System::readFile(__FILE__), $file); }, 0.05, 'A'); $sch->start(); }); $server->start(); ?> --EXPECTF-- [1] Co 2 [2] Co 1 ================================================ FILE: tests/swoole_coroutine_scheduler/start_in_server_worker_stop.phpt ================================================ --TEST-- swoole_coroutine_scheduler: start in server onShutdown callback --SKIPIF-- --FILE-- set(['worker_num' => 2, 'log_level' => SWOOLE_LOG_WARNING,]); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($file) { if ($worker_id == 1) { Swoole\Timer::after(200, function () use ($server, $file) { System::sleep(0.1); echo "[1] Co " . Co::getCid() . "\n"; Assert::same(System::readFile(__FILE__), $file); $server->shutdown(); }); } }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); $server->on('workerStop', function (Swoole\Server $server, int $worker_id) use ($file) { if ($worker_id == 1) { $sch = new Co\Scheduler; $sch->add(function ($t, $n) use ($file) { System::sleep($t); echo "[2] Co " . Co::getCid() . "\n"; Assert::same(System::readFile(__FILE__), $file); }, 0.05, 'A'); $sch->start(); } }); $server->start(); ?> --EXPECT-- [1] Co 2 [2] Co 3 ================================================ FILE: tests/swoole_coroutine_system/aio_thread_num.phpt ================================================ --TEST-- swoole_coroutine_system: gethostbyname --SKIPIF-- --FILE-- swoole_cpu_num(), 'aio_max_wait_time' => 0.0005 ]); run(function () { System::readFile(__FILE__); }); $sch = new Swoole\Coroutine\Scheduler(); $sch->set(['dns_cache_capacity' => 0]); $sch->add(function () { static $worker_num = 0; $n = 100; while ($n--) { $worker_num = max($worker_num, Coroutine::stats()['aio_worker_num']); Swoole\Coroutine\System::sleep(0.001); } Assert::greaterThan($worker_num, swoole_cpu_num()); phpt_var_dump($worker_num); }); $sch->parallel(swoole_cpu_num() * [4, 16, 32, 64][PRESSURE_LEVEL], function () { System::sleep(mt_rand(1, 5) / 1000); $result = Swoole\Coroutine\System::getaddrinfo('www.baidu.com'); Assert::notEmpty($result); }); $sch->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_coroutine_system/getaddrinfo.phpt ================================================ --TEST-- swoole_coroutine_system: getaddrinfo --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_system/getaddrinfo_timeout.phpt ================================================ --TEST-- swoole_coroutine_system: getaddrinfo timeout --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_system/gethostbyname.phpt ================================================ --TEST-- swoole_coroutine_system: gethostbyname --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_system/gethostbyname_ipv6.phpt ================================================ --TEST-- swoole_coroutine_system: gethostbyname for IPv6 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_system/gethostbyname_timeout.phpt ================================================ --TEST-- swoole_coroutine_system: gethostbyname timeout --SKIPIF-- --FILE-- --EXPECT-- NEXT ================================================ FILE: tests/swoole_coroutine_system/readfile.phpt ================================================ --TEST-- swoole_coroutine_system: readFile --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_system/sleep.phpt ================================================ --TEST-- swoole_coroutine_system: sleep --SKIPIF-- --FILE-- --EXPECT-- OK ================================================ FILE: tests/swoole_coroutine_system/wait.phpt ================================================ --TEST-- swoole_coroutine_system: wait --SKIPIF-- --FILE-- wait(10); }); $process->start(); Coroutine\run(function () use ($process, $atomic) { for ($n = MAX_REQUESTS; $n--;) { $status = System::wait(0.001); Assert::false($status); Assert::same(swoole_last_error(), SOCKET_ETIMEDOUT); } $atomic->wakeup(); $status = System::wait(1); Assert::same($status['pid'], $process->pid); var_dump($status); }); ?> --EXPECTF-- array(3) { ["pid"]=> int(%d) ["code"]=> int(0) ["signal"]=> int(0) } ================================================ FILE: tests/swoole_coroutine_system/waitEvent.phpt ================================================ --TEST-- swoole_coroutine_system: waitEvent --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_system/waitPid.phpt ================================================ --TEST-- swoole_coroutine_system: waitPid --SKIPIF-- --FILE-- start(); $processSlow = new Process(function () use ($atomic) { $atomic->wait(10); usleep(10 * 1000); }); $processSlow->start(); Coroutine\run(function () use ($processFast, $processSlow, $atomic) { for ($n = MAX_REQUESTS; $n--;) { $status = System::waitPid($processSlow->pid, 0.001); Assert::false($status); Assert::same(swoole_last_error(), SOCKET_ETIMEDOUT); } $atomic->wakeup(); $status = System::waitPid($processSlow->pid, 1); Assert::same($status['pid'], $processSlow->pid); var_dump($status); $status = System::waitPid($processFast->pid); Assert::same($status['pid'], $processFast->pid); var_dump($status); }); ?> --EXPECTF-- array(3) { ["pid"]=> int(%d) ["code"]=> int(0) ["signal"]=> int(0) } array(3) { ["pid"]=> int(%d) ["code"]=> int(0) ["signal"]=> int(0) } ================================================ FILE: tests/swoole_coroutine_system/waitSignal.phpt ================================================ --TEST-- swoole_coroutine_system: waitSignal --SKIPIF-- --FILE-- wait(); echo "2\n"; switch_process(); Process::kill($pid, SIGUSR1); $atomic->wait(); echo "6\n"; switch_process(); Process::kill($pid, SIGUSR2); echo "8\n"; }); $killer->start(); Coroutine\run(function () use ($atomic) { Coroutine::sleep(0.001); switch_process(); $atomic->wakeup(); echo "1\n"; Assert::eq(System::waitSignal(SIGUSR1), SIGUSR1); echo "3\n"; Assert::false(System::waitSignal(SIGUSR2, 0.01)); echo "4\n"; $atomic->wakeup(); echo "5\n"; Assert::eq(System::waitSignal(SIGUSR2), SIGUSR2); echo "7\n"; System::wait(); echo "9\n"; }); ?> --EXPECT-- 1 2 3 4 5 6 8 7 9 ================================================ FILE: tests/swoole_coroutine_system/waitSignal_2.phpt ================================================ --TEST-- swoole_coroutine_system: waitSignal 2 --SKIPIF-- --FILE-- wait(); echo "2\n"; switch_process(); Process::kill($pid, SIGUSR1); $atomic->wait(); echo "6\n"; switch_process(); Process::kill($pid, SIGUSR2); echo "8\n"; }); $killer->start(); Coroutine\run(function () use ($atomic) { Coroutine::sleep(0.001); switch_process(); $atomic->wakeup(); echo "1\n"; $list = [SIGUSR1, SIGUSR2, SIGIO]; Assert::eq(System::waitSignal($list), SIGUSR1); echo "3\n"; Assert::false(System::waitSignal($list, 0.01)); echo "4\n"; $atomic->wakeup(); echo "5\n"; Assert::eq(System::waitSignal($list), SIGUSR2); echo "7\n"; System::wait(); echo "9\n"; }); ?> --EXPECT-- 1 2 3 4 5 6 8 7 9 ================================================ FILE: tests/swoole_coroutine_system/writefile.phpt ================================================ --TEST-- swoole_coroutine_system: writeFile --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_system/writefile_append.phpt ================================================ --TEST-- swoole_coroutine_system: writeFile use FILE_APPEND --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_util/dns_lookup.phpt ================================================ --TEST-- swoole_coroutine_util: dns lookup --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(array( 'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $host = swoole_async_dns_lookup_coro(TEST_DOMAIN_3); if ($host) { $response->end("OK\n"); } else { $response->end("ERROR: " . swoole_last_error() . "\n"); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_coroutine_util/exec.phpt ================================================ --TEST-- swoole_coroutine_util: exec --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_util/exec_sleep.phpt ================================================ --TEST-- swoole_coroutine_util: coroutine exec --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_util/fgets.phpt ================================================ --TEST-- swoole_coroutine_util: fgets --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_util/fread.phpt ================================================ --TEST-- swoole_coroutine_util: fread --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_util/fwrite.phpt ================================================ --TEST-- swoole_coroutine_util: fwrite --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_util/list_coroutine.phpt ================================================ --TEST-- swoole_coroutine_util: listCoroutines --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_coroutine_util/task_worker.phpt ================================================ --TEST-- swoole_coroutine_util: sleep in Task-Worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $cli->connect('127.0.0.1', $pm->getFreePort(), 5) or die("ERROR"); $cli->send("task-01") or die("ERROR"); echo trim($cli->recv()) . "\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', 'task_enable_coroutine' => true )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $serv->task([$fd, 'sleep']); }); $serv->on('task', function (Swoole\Server $serv, $task) { list($fd) = $task->data; co::sleep(0.2); $serv->send($fd, "sleep\r\n\r\n"); }); $serv->on('finish', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- sleep ================================================ FILE: tests/swoole_coroutine_wait_group/base.phpt ================================================ --TEST-- swoole_coroutine_wait_group: base --SKIPIF-- --FILE-- add(); Assert::same( file_get_contents(__FILE__), System::readFile(__FILE__) ); echo "TASK[1] DONE\n"; $wg->done(); }); $cid = go(function () use ($wg) { $wg->add(); Assert::true(Co::yield()); echo "TASK[2] DONE\n"; $wg->done(); }); go(function () use ($wg, $cid) { $wg->add(); Assert::notSame(Co::sleep(0.001), false); Co::resume($cid); echo "TASK[3] DONE\n"; $wg->done(); }); $wg->wait(); }); ?> --EXPECTF-- TASK[%d] DONE TASK[%d] DONE TASK[%d] DONE ================================================ FILE: tests/swoole_coroutine_wait_group/defer.phpt ================================================ --TEST-- swoole_coroutine_wait_group: run in defer --SKIPIF-- --FILE-- add(); Co\go(function () use ($wg, $i) { var_dump("start $i"); defer(function () use ($wg, $i) { var_dump("defer $i"); $wg->done(); var_dump("done $i"); }); var_dump("end $i"); }); } var_dump("wait"); $wg->wait(); var_dump("finish"); }); ?> --EXPECT-- string(5) "add 1" string(7) "start 1" string(5) "end 1" string(7) "defer 1" string(6) "done 1" string(5) "add 2" string(7) "start 2" string(5) "end 2" string(7) "defer 2" string(6) "done 2" string(4) "wait" string(6) "finish" ================================================ FILE: tests/swoole_coroutine_wait_group/empty.phpt ================================================ --TEST-- swoole_coroutine_wait_group: empty --SKIPIF-- --FILE-- add(1); $wg->add(-1); $wg->wait(); $wg->add(1); $wg->done(); $wg->wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_coroutine_wait_group/logic.phpt ================================================ --TEST-- swoole_coroutine_wait_group: logic --SKIPIF-- --FILE-- add(-1); }, LogicException::class); $wg->add(1); go(function () use ($wg) { Co::sleep(0.001); Assert::throws(function () use ($wg) { $wg->add(1); }, LogicException::class); $wg->done(); }); $wg->wait(); Assert::throws(function () use ($wg) { $wg->done(); }, LogicException::class); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_curl/abnormal_response/1.phpt ================================================ --TEST-- swoole_curl/abnormal_response: 1 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); Assert::isEmpty($output); Assert::oneOf(curl_errno($ch), [CURLE_RECV_ERROR, CURLE_GOT_NOTHING]); curl_close($ch); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort()); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $server->on('Connect', function ($serv, $fd, $wid) { $serv->close($fd); }); $server->on('Receive', function ($serv, $fd, $wid, $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/abnormal_response/2.phpt ================================================ --TEST-- swoole_curl/abnormal_response: 2 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); Assert::isEmpty($output); Assert::eq(curl_errno($ch), CURLE_PARTIAL_FILE); curl_close($ch); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort()); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $server->on('Receive', function ($serv, $fd, $wid, $data) { usleep(100); $serv->send($fd, "HTTP/1.1 200 OK\r\n" . "Content-Type: text/html; charset=UTF-8\r\n" . "Content-Length: 1256\r\n"); usleep(10000); $serv->close($fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/basic/1.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_exec() function with basic functionality --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo "*** Testing curl_exec() : basic functionality ***\n"; $url = $host. "/get.php?test=get"; $ch = curl_init(); ob_start(); // start output buffering curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $ok = curl_exec($ch); curl_close($ch); $curl_content = ob_get_contents(); ob_end_clean(); if ($ok) { var_dump($curl_content); } else { echo "curl_exec returned false"; } }); ?> --EXPECTF-- *** Testing curl_exec() : basic functionality *** string(25) "Hello World! Hello World!" ================================================ FILE: tests/swoole_curl/basic/10.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_error() & curl_errno() function with problematic proxy --CREDITS-- TestFest 2009 - AFUP - Perrick Penet --SKIPIF-- --FILE-- run(function ($host) { $url = "http://www.example.org"; $ch = curl_init(); curl_setopt($ch, CURLOPT_PROXY, uniqid() . ":" . uniqid()); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); Assert::eq(curl_errno($ch), CURLE_COULDNT_RESOLVE_PROXY); curl_close($ch); echo "DONE\n"; }, false); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_curl/basic/11.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with COOKIE --CREDITS-- TestFest 2009 - AFUP - Xavier Gorse --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl with cookie ***' . "\n"; $url = "{$host}/get.php?test=cookie"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_COOKIE, 'foo=bar'); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl with cookie *** string(3) "bar" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/12.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with CURLOPT_HTTP_VERSION/CURL_HTTP_VERSION_1_0 --CREDITS-- TestFest 2009 - AFUP - Xavier Gorse --SKIPIF-- --FILE-- disableNativeCurl(); $cm->run(function ($host) { // start testing echo '*** Testing curl with HTTP/1.0 ***' . "\n"; $url = "{$host}/get.php?test=httpversion"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl with HTTP/1.0 *** Warning: swoole_curl_setopt(): CURLOPT_HTTP_VERSION[%d] is not supported in %s on line %d string(8) "HTTP/1.1" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/13.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with CURLOPT_HTTP_VERSION/CURL_HTTP_VERSION_1_1 --CREDITS-- TestFest 2009 - AFUP - Xavier Gorse --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl with HTTP/1.1 ***' . "\n"; $url = "{$host}/get.php?test=httpversion"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl with HTTP/1.1 *** string(8) "HTTP/1.1" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/14.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_init() function with basic functionality --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- disableNativeCurl(); $cm->run(function ($host) { $ch = curl_init(); Assert::isInstanceOf($ch, Swoole\Curl\Handler::class); }, false); ?> ===DONE=== --EXPECT-- ===DONE=== ================================================ FILE: tests/swoole_curl/basic/15.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_init() function with $url parameter defined --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { $url = 'http://www.example.com/'; $ch = curl_init($url); Assert::same($url, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); }, false); ?> ===DONE=== --EXPECT-- ===DONE=== ================================================ FILE: tests/swoole_curl/basic/19.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_getinfo() function with CURLINFO_EFFECTIVE_URL parameter --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { $url = "http://{$host}/get.php?test="; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); $info = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); var_dump($url == $info); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- Hello World! Hello World!bool(true) ===DONE=== ================================================ FILE: tests/swoole_curl/basic/2.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with CURLOPT_RETURNTRANSFER parameter set to 1 --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); ***' . "\n"; $url = "{$host}/get.php?test=get"; $ch = curl_init(); ob_start(); // start output buffering curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); *** string(25) "Hello World! Hello World!" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/20.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_getinfo() function with CURLINFO_HTTP_CODE parameter --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { $url = "{$host}/get.php?test="; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); var_dump(curl_getinfo($ch, CURLINFO_HTTP_CODE)); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- Hello World! Hello World!int(200) ===DONE=== ================================================ FILE: tests/swoole_curl/basic/21.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_getinfo() function with CURLINFO_CONTENT_TYPE parameter --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { $url = "{$host}/get.php?test=contenttype"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); var_dump(curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- string(24) "text/plain;charset=utf-8" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/22.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_setopt() function with CURLOPT_FOLLOWLOCATION parameter --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // co::set(['log_level' => 0, 'trace_flags' => SWOOLE_TRACE_ALL]); // CURLOPT_FOLLOWLOCATION = true $urls = [ "{$host}/get.php?test=redirect_301", "{$host}/get.php?test=redirect_302", "{$host}/get.php?test=redirect_307", "{$host}/get.php?test=redirect_308", ]; foreach ($urls as $url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, 'id=123&name=swoole'); curl_exec($ch); $info = curl_getinfo($ch); Assert::eq($info['redirect_count'], 1); curl_close($ch); } // CURLOPT_FOLLOWLOCATION = false $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_exec($ch); $info = curl_getinfo($ch); Assert::eq($info['redirect_count'], 0); Assert::assert("http://{$host}/get.php?test=getpost" === $info['redirect_url']); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- array(1) { ["test"]=> string(7) "getpost" } array(0) { } array(1) { ["test"]=> string(7) "getpost" } array(0) { } array(1) { ["test"]=> string(7) "getpost" } array(2) { ["id"]=> string(3) "123" ["name"]=> string(6) "swoole" } array(1) { ["test"]=> string(7) "getpost" } array(2) { ["id"]=> string(3) "123" ["name"]=> string(6) "swoole" } ===DONE=== ================================================ FILE: tests/swoole_curl/basic/23.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_setopt() function with CURLOPT_HEADER parameter set to 1 --CREDITS-- Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=header_body"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 1); $result = curl_exec($ch); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headerContent = substr($result, 0, $headerSize); $body = substr($result, $headerSize); Assert::assert(false !== strpos($headerContent, 'abc: 123')); var_dump($body); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- string(5) "a b c" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/24.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with setting auto referer --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- disableNativeCurl(); $cm->run(function ($host) { // start testing echo '*** Testing curl setting auto referer ***' . "\n"; $url = "{$host}/get.php?test=auto_referer"; $ch = curl_init(); ob_start(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_AUTOREFERER, true); curl_setopt($ch, CURLOPT_URL, $url); $curl_content = curl_exec($ch); curl_close($ch); Assert::assert("http://{$host}/get.php?test=auto_referer" === $curl_content); }); ?> ===DONE=== --EXPECTF-- *** Testing curl setting auto referer *** ===DONE=== ================================================ FILE: tests/swoole_curl/basic/25.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_multi_getcontent() function --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl method curl_multi_getcontent ***' . "\n"; $url = "{$host}/get.php?test=curl_multi_getcontent"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); Assert::null(curl_multi_getcontent($ch)); curl_close($ch); echo PHP_EOL; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); Assert::null(curl_multi_getcontent($ch)); curl_close($ch); $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); $curl_content = curl_exec($ch); Assert::same(curl_multi_getcontent($ch), $curl_content); curl_close($ch); $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); Assert::same(curl_multi_getcontent($ch), ''); curl_close($ch); }); ?> ===DONE=== --EXPECTF-- *** Testing curl method curl_multi_getcontent *** Hello World! Hello World! ===DONE=== ================================================ FILE: tests/swoole_curl/basic/3.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with POST parameters --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl sending through GET an POST ***' . "\n"; $url = "{$host}/get.php?test=getpost&get_param=Hello%20World"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "Hello=World&Foo=Bar&Person=John%20Doe"); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl sending through GET an POST *** string(208) "array(2) { ["test"]=> string(7) "getpost" ["get_param"]=> string(11) "Hello World" } array(3) { ["Hello"]=> string(5) "World" ["Foo"]=> string(3) "Bar" ["Person"]=> string(8) "John Doe" } " ===DONE=== ================================================ FILE: tests/swoole_curl/basic/4.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with setting referer --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl setting referer ***' . "\n"; $url = "{$host}/get.php?test=referer"; $ch = curl_init(); ob_start(); // start output buffering curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_REFERER, 'http://www.refer.er'); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump($curl_content); }); ?> ===DONE=== --EXPECTF-- *** Testing curl setting referer *** string(19) "http://www.refer.er" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/5.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_opt() function with user agent --CREDITS-- Sebastian Deutsch TestFest 2009 - AFUP - Jean-Marc Fontaine --SKIPIF-- --FILE-- run(function ($host) { // start testing echo '*** Testing curl with user agent ***' . "\n"; $url = "{$host}/get.php?test=useragent"; $ch = curl_init(); ob_start(); // start output buffering curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, 'cURL phpt'); curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); }); ?> ===DONE=== --EXPECTF-- *** Testing curl with user agent *** string(9) "cURL phpt" ===DONE=== ================================================ FILE: tests/swoole_curl/basic/7.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_error() & curl_errno() function without url --CREDITS-- TestFest 2009 - AFUP - Perrick Penet --SKIPIF-- --FILE-- run(function ($host) { $ch = curl_init(); curl_exec($ch); Assert::contains(curl_error($ch), 'No URL set'); Assert::eq(curl_errno($ch), CURLE_URL_MALFORMAT); curl_close($ch); echo "Done\n"; }, false); ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_curl/basic/8.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_error() & curl_errno() function with problematic host --CREDITS-- TestFest 2009 - AFUP - Perrick Penet --SKIPIF-- --FILE-- run(function ($host) { $url = "http://www." . uniqid() . "." . uniqid(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); var_dump(curl_error($ch)); var_dump(curl_errno($ch)); curl_close($ch); }, false); ?> --EXPECTF-- %s resolve%s int(6) ================================================ FILE: tests/swoole_curl/basic/9.phpt ================================================ --TEST-- swoole_curl/basic: Test curl_error() & curl_errno() function with problematic protocol --CREDITS-- TestFest 2009 - AFUP - Perrick Penet --SKIPIF-- --FILE-- disableNativeCurl(); $cm->run(function ($host) { $url = uniqid()."://www.".uniqid().".".uniqid(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_exec($ch); var_dump(curl_error($ch)); var_dump(curl_errno($ch)); curl_close($ch); }, false); ?> --EXPECTREGEX-- string\(\d+\) ".+URL.+" int\(\d\) ================================================ FILE: tests/swoole_curl/cancel.phpt ================================================ --TEST-- swoole_curl: sleep --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); $s = microtime(true); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $cid = Coroutine::getCid(); go(function () use ($cid) { System::sleep(0.01); Assert::true(Coroutine::cancel($cid)); Assert::false(Coroutine::isCanceled()); }); $output = curl_exec($ch); Assert::isEmpty($output); Assert::eq(swoole_last_error(), SWOOLE_ERROR_CO_CANCELED); Assert::eq(curl_errno($ch), CURLE_ABORTED_BY_CALLBACK); Assert::contains(curl_error($ch), 'Operation was aborted by an application callback'); curl_close($ch); }); Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(300000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/close_before_resume.phpt ================================================ --TEST-- swoole_curl: close before resume --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); $s = microtime(true); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); go(function() use ($ch) { Co::sleep(0.1); $res = curl_close($ch); echo "close [1]\n"; Assert::true($res); }); $output = curl_exec($ch); var_dump($output); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); echo "close [2]\n"; }); Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => N, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(300000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d Stack trace: #0 %s(%d): curl_close(%s) %A thrown in %s on line %d [Coroutine-%d] Stack trace: ------------------------------------------------------------------- #0 %s(%d): curl_exec(Object(CurlHandle)) #1 [internal function]: {%s}() ================================================ FILE: tests/swoole_curl/concurrent.phpt ================================================ --TEST-- swoole_curl: Concurrent request --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); $s = microtime(true); run(function () use ($pm) { $fn = function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); $output = curl_exec($ch); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); echo "DONE\n"; }; $n = N; while($n--) { go($fn); } }); Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => N, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(rand(1000, 30000)); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE DONE DONE DONE DONE DONE DONE DONE ================================================ FILE: tests/swoole_curl/coro_read.phpt ================================================ --TEST-- swoole_curl: coroutine read --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $fileHandle = fopen('download.txt', 'r'); $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_UPLOAD, true); curl_setopt($ch, CURLOPT_INFILE, $fileHandle); curl_setopt($ch, CURLOPT_INFILESIZE, filesize('download.txt')); $result = curl_exec($ch); curl_close($ch); fclose($fileHandle); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) use ($data) { var_dump($request->getContent() == $data); $response->end('Hello World'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- bool(true) Done --CLEAN-- ================================================ FILE: tests/swoole_curl/coro_write.phpt ================================================ --TEST-- swoole_curl: coroutine write --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $data) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm, $data) { $fileHandle = fopen('download.txt', 'wb'); $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_FILE, $fileHandle); $result = curl_exec($ch); curl_close($ch); fclose($fileHandle); Assert::true(file_get_contents('download.txt') == $data); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) use ($data) { $response->end($data); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Done --CLEAN-- ================================================ FILE: tests/swoole_curl/coro_write_header.phpt ================================================ --TEST-- swoole_curl: coroutine write --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $fileHandle = fopen('download.txt', 'wb'); $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_WRITEHEADER, $fileHandle); $result = curl_exec($ch); curl_close($ch); fclose($fileHandle); echo file_get_contents('download.txt'); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { $response->end('Hello World'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- HTTP/1.1 200 OK Server: swoole-http-server Date: %s Connection: keep-alive Content-Type: text/html Content-Length: 11 Done --CLEAN-- ================================================ FILE: tests/swoole_curl/error.phpt ================================================ --TEST-- swoole_curl: error --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/event_exit.phpt ================================================ --TEST-- swoole_curl: event exit --SKIPIF-- --FILE-- sprintf('https://rpm.nodesource.com/pub_%d.x/el/%d/x86_64/', $version, $os), CURLOPT_RETURNTRANSFER => true ]); $response = curl_exec($ch); curl_close($ch); } } }); echo "Done\n"; ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/exec_twice.phpt ================================================ --TEST-- swoole_curl: exec twice --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); $s = microtime(true); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); go(function() use ($ch) { Co::sleep(0.1); echo "co 2 exec\n"; var_dump(curl_exec($ch), curl_errno($ch)); }); echo "co 1 exec\n"; $output = curl_exec($ch); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); echo "close [2]\n"; }); Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => N, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(300000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- co 1 exec co 2 exec Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d Stack trace: #0 %s(%d): curl_exec(%s) %A thrown in %s on line %d [Coroutine-%d] Stack trace: ------------------------------------------------------------------- #0 %s(%d): curl_exec(Object(CurlHandle)) #1 [internal function]: {%s}() ================================================ FILE: tests/swoole_curl/fatal_error_in_callback.phpt ================================================ --TEST-- swoole_curl: error --SKIPIF-- --FILE-- getMessage(), E_USER_WARNING); } }); curl_exec($ch); echo "Exec\n"; curl_close($ch); }); echo "Done\n"; ?> --EXPECTF-- Fatal error: test in %s on line %d Warning: curl_close(): Attempt to close cURL handle from a callback in %s on line %d --EXPECTF_85-- Fatal error: test in %s on line %d Stack trace: #0 %s(%d): trigger_error('test', %d) #1 [internal function]: {closure:{closure:%s:%d}:%d}(Object(CurlHandle), '%s...') #2 %s(%d): curl_exec(Object(CurlHandle)) #3 [internal function]: {closure:%s:%d}() #4 {main} Warning: curl_close(): Attempt to close cURL handle from a callback in %s on line %d ================================================ FILE: tests/swoole_curl/ftp.phpt ================================================ --TEST-- swoole_curl: ftp --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/guzzle/cancel.phpt ================================================ --TEST-- swoole_curl/guzzle: cancel --SKIPIF-- --FILE-- cancel(); $responses[] = $response; } foreach ($responses as $r) { Assert::true(Promise\Is::rejected($r)); } echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/guzzle/cannot_cancel_finished.phpt ================================================ --TEST-- swoole_curl/guzzle: cannot_cancel_finished --SKIPIF-- --FILE-- wait(); $response->cancel(); Assert::true(Promise\Is::fulfilled($response)); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/guzzle/cookie.phpt ================================================ --TEST-- swoole_curl/guzzle: cookie --SKIPIF-- --FILE-- 'foo', 'other_cookie' => 'barbaz1234' ], 'httpbin.org' ); $r = $client->request('GET', 'http://httpbin.org/cookies', [ 'cookies' => $jar ]); Assert::eq($r->getStatusCode(), 200); Assert::eq(json_decode($r->getBody()->getContents(), true)['cookies']['some_cookie'], 'foo'); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/guzzle/promise.phpt ================================================ --TEST-- swoole_curl/guzzle: promise --SKIPIF-- --FILE-- 'https://httpbin.org']); // Initiate each request but do not block $promises = [ 'a' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test1!']]), 'b' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test2!']]), 'c' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test3!']]), ]; // Wait on all of the requests to complete. $results = GuzzleHttp\Promise\Utils::unwrap($promises); // You can access each result using the key provided to the unwrap // function. echo json_decode($results['a']->getBody()->getContents())->data . PHP_EOL; echo $results['b']->getHeaderLine('Content-Type') . PHP_EOL; echo 'Done' . PHP_EOL; }); ?> --EXPECT-- {"data":"hello test1!"} application/json Done ================================================ FILE: tests/swoole_curl/guzzle/request_async.phpt ================================================ --TEST-- swoole_curl/guzzle: request async --SKIPIF-- --FILE-- requestAsync('GET', 'http://httpbin.org/get'); $promise->then( function (ResponseInterface $res) { echo $res->getStatusCode() .PHP_EOL; }, function (RequestException $e) { echo $e->getMessage() . PHP_EOL; echo $e->getRequest()->getMethod() . PHP_EOL; } ); $promise->wait(); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- 200 Done ================================================ FILE: tests/swoole_curl/guzzle/request_on_stats.phpt ================================================ --TEST-- swoole_curl/guzzle: request on_stats --SKIPIF-- --FILE-- request('GET', $host, [ 'on_stats' => function (TransferStats $stats) use ($host) { Assert::eq($stats->getEffectiveUri(), $host); // You must check if a response was received before using the // response object. if ($stats->hasResponse()) { Assert::eq($stats->getResponse()->getStatusCode(), 200); } else { // Error data is handler specific. You will need to know what // type of error data your handler uses before using this // value. var_dump($stats->getHandlerErrorData()); } } ]); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/guzzle/send_async.phpt ================================================ --TEST-- swoole_curl/guzzle: send async --SKIPIF-- --FILE-- request('GET', 'https://httpbin.org'); echo $response->getStatusCode(), PHP_EOL; // 200 echo $response->getHeaderLine('content-type'), PHP_EOL; // Send an asynchronous request. $request = new Request('GET', 'http://httpbin.org'); $promise = $client->sendAsync($request)->then(function ($response) { echo 'I completed! ' . $response->getStatusCode() . PHP_EOL; }); $promise->wait(); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- 200 text/html; charset=utf-8 I completed! 200 Done ================================================ FILE: tests/swoole_curl/guzzle.phpt ================================================ --TEST-- swoole_curl: guzzle --SKIPIF-- --FILE-- $client->getAsync('http://www.baidu.com/'), 'qq' => $client->getAsync('https://www.qq.com/'), 'zhihu' => $client->getAsync('http://www.zhihu.com/') ]; $responses = Promise\Utils::unwrap($promises); Assert::contains($responses['baidu']->getBody(), '百度'); Assert::contains($responses['qq']->getBody(), '腾讯'); Assert::contains($responses['zhihu']->getBody(), '知乎'); $result['task_1'] = 'OK'; }); go(function () use ($barrier, &$result) { $client = new Client(['base_uri' => 'https://httpbin.org/']); $n = N; $data = $promises = []; while ($n--) { $key = 'req_' . $n; $data[$key] = uniqid('swoole_test'); $promises[$key] = $client->getAsync('/base64/' . base64_encode($data[$key])); } $responses = Promise\Utils::unwrap($promises); $n = N; while ($n--) { $key = 'req_' . $n; Assert::eq($responses[$key]->getBody(), $data[$key]); } $result['task_2'] = 'OK'; }); Barrier::wait($barrier); Assert::eq($result['task_1'], 'OK'); Assert::eq($result['task_2'], 'OK'); echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/https.phpt ================================================ --TEST-- swoole_curl: https --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/keepalive.phpt ================================================ --TEST-- swoole_curl: keepalive --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $execFn = function () use ($ch, $code, $pm) { $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); $output = curl_exec($ch); $info = curl_getinfo($ch); Assert::eq($output, "Hello World\n" . $code); if ($output === false) { exit("CURL Error:" . curl_error($ch)); } return $info; }; echo "co 1 exec\n"; $info1 = $execFn(); Co::sleep(0.1); echo "co 2 exec\n"; $info2 = $execFn(); Assert::eq($info1['local_port'], $info2['local_port']); curl_close($ch); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 2, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { $response->end("Hello World\n" . $request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- co 1 exec co 2 exec Done ================================================ FILE: tests/swoole_curl/multi/1.phpt ================================================ --TEST-- swoole_curl/multi: 1 --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/2.phpt ================================================ --TEST-- swoole_curl/multi: 2 --SKIPIF-- --FILE-- 0.2]); echo "Done\n"; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/3.phpt ================================================ --TEST-- swoole_curl/multi: 3 --SKIPIF-- --FILE-- --EXPECT-- Done Done Done Done ================================================ FILE: tests/swoole_curl/multi/4.phpt ================================================ --TEST-- swoole_curl/multi: 4 --SKIPIF-- --FILE-- 0.2]); echo "Done\n"; }); } }); ?> --EXPECT-- Done Done Done Done ================================================ FILE: tests/swoole_curl/multi/5.phpt ================================================ --TEST-- swoole_curl/multi: 5 --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_curl/multi/6.phpt ================================================ --TEST-- swoole_curl/multi: 6 --SKIPIF-- --FILE-- --EXPECTF-- Done ================================================ FILE: tests/swoole_curl/multi/add_after_easy_exec.phpt ================================================ --TEST-- swoole_curl/multi: add handle after easy exec --SKIPIF-- --FILE-- run(function ($host) { $ch1 = curl_init(); curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=get"); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); $rs = curl_exec($ch1); Assert::eq($rs, "Hello World!\nHello World!"); $mh1 = curl_multi_init(); Assert::eq(curl_multi_add_handle($mh1, $ch1), 0); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/bug4393.phpt ================================================ --TEST-- swoole_curl/multi: guzzle --SKIPIF-- --FILE-- $guzzle->getAsync('https://www.qq.com/'), 'baidu' => $guzzle->getAsync('http://www.baidu.com/'), ]; } else { $promises = [ 'httpbin' => $guzzle->getAsync('https://www.httpbin.org/'), 'nghttp2' => $guzzle->getAsync('https://nghttp2.org/'), ]; } $responses = []; foreach (Promise\Utils::settle($promises)->wait() as $k => $v) { $responses[$k] = $v['value']; } if (IS_IN_CI) { Assert::contains($responses['baidu']->getBody(), '百度'); Assert::contains($responses['qq']->getBody(), '腾讯'); } else { Assert::contains($responses['httpbin']->getBody(), 'httpbin'); Assert::contains($responses['nghttp2']->getBody(), 'nghttp2'); } }; $n = 2; while ($n--) { $s = microtime(true); $test(); Assert::lessThan(microtime(true) - $s, 3.0); } echo 'Done' . PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/bug48203_multi.phpt ================================================ --TEST-- swoole_curl/multi: Variation of bug #48203 with curl_multi_exec (Crash when file pointers passed to curl are closed before calling curl_multi_exec) --SKIPIF-- --FILE-- 1, $curl_option => $fp, CURLOPT_URL => $target_url, ); // we also need to set CURLOPT_VERBOSE to test CURLOPT_STDERR properly if (CURLOPT_STDERR == $curl_option) { $options[CURLOPT_VERBOSE] = 1; } if (CURLOPT_INFILE == $curl_option) { $options[CURLOPT_UPLOAD] = 1; } curl_setopt_array($ch1, $options); curl_setopt_array($ch2, $options); fclose($fp); // <-- premature close of $fp caused a crash! $mh = curl_multi_init(); curl_multi_add_handle($mh, $ch1); curl_multi_add_handle($mh, $ch2); $active = 0; do { curl_multi_exec($mh, $active); } while ($active > 0); curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_close($mh); // Force curl to output results fflush(STDERR); fflush(STDOUT); echo "Ok for $description\n"; } $cm = new CurlManager(); $cm->run(function ($host) { $url = "{$host}/"; $options_to_check = array( "CURLOPT_STDERR", "CURLOPT_WRITEHEADER", "CURLOPT_FILE", "CURLOPT_INFILE" ); foreach($options_to_check as $option) { checkForClosedFilePointer($url, constant($option), $option); } }); ?> --CLEAN-- --EXPECTF-- Warning: curl_multi_add_handle(): CURLOPT_STDERR resource has gone away, resetting to stderr in %s on line %d %A Warning: curl_multi_add_handle(): CURLOPT_STDERR resource has gone away, resetting to stderr in %s on line %d %A Ok for CURLOPT_STDERR Warning: curl_multi_add_handle(): CURLOPT_WRITEHEADER resource has gone away, resetting to default in %s on line %d Warning: curl_multi_add_handle(): CURLOPT_WRITEHEADER resource has gone away, resetting to default in %s on line %d Ok for CURLOPT_WRITEHEADER Warning: curl_multi_add_handle(): CURLOPT_FILE resource has gone away, resetting to default in %s on line %d Warning: curl_multi_add_handle(): CURLOPT_FILE resource has gone away, resetting to default in %s on line %d %AOk for CURLOPT_FILE Warning: curl_multi_add_handle(): CURLOPT_INFILE resource has gone away, resetting to default in %s on line %d Warning: curl_multi_add_handle(): CURLOPT_INFILE resource has gone away, resetting to default in %s on line %d Ok for CURLOPT_INFILE ================================================ FILE: tests/swoole_curl/multi/bug67643.phpt ================================================ --TEST-- swoole_curl/multi: Bug #67643 (curl_multi_getcontent returns '' when RETURNTRANSFER isn't set) --SKIPIF-- --FILE-- 0); $results = curl_multi_getcontent($ch); curl_multi_remove_handle($mh, $ch); curl_multi_close($mh); Assert::contains($results, 'Bug #67643'); echo 'Done'.PHP_EOL; }); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/bug71523.phpt ================================================ --TEST-- swoole_curl/multi: Bug #71523 (Copied handle with new option CURLOPT_HTTPHEADER crashes while curl_multi_exec) --SKIPIF-- --FILE-- okey --EXPECT-- okey ================================================ FILE: tests/swoole_curl/multi/bug76675.phpt ================================================ --TEST-- swoole_curl/multi: Bug #76675 (Segfault with H2 server push write/writeheader handlers) --SKIPIF-- --FILE-- --EXPECTREGEX-- (Received \d+)+ ================================================ FILE: tests/swoole_curl/multi/bug77535.phpt ================================================ --TEST-- swoole_curl/multi: Bug #77535 (Invalid callback, h2 server push) --SKIPIF-- --FILE-- mh = curl_multi_init()) { throw new \RuntimeException('Unable to create a new cURL multi handle'); } $this->addServerPushCallback(); echo __METHOD__.'[2]'.PHP_EOL; $this->curl = curl_init(); curl_setopt($this->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_setopt($this->curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_setopt($this->curl, CURLOPT_HEADER, false); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, false); curl_setopt($this->curl, CURLOPT_FAILONERROR, false); curl_setopt($this->curl, CURLOPT_URL, TEST_HTTP2_SERVERPUSH_URL); curl_setopt($this->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); curl_setopt($this->curl, CURLOPT_HEADERFUNCTION, function ($ch, $data) { return \strlen($data); }); curl_setopt($this->curl, CURLOPT_WRITEFUNCTION, function ($ch, $data) { return \strlen($data); }); curl_multi_add_handle($this->mh, $this->curl); echo __METHOD__.'[3]'.PHP_EOL; $stillRunning = null; while (true) { do { $mrc = curl_multi_exec($this->mh, $stillRunning); } while (CURLM_CALL_MULTI_PERFORM === $mrc); $info = curl_multi_info_read($this->mh); while (false !== $info && $info['msg'] == CURLMSG_DONE) { if (CURLMSG_DONE !== $info['msg']) { continue; } echo "Start handle request.\n"; return; } } } private function addServerPushCallback(): void { echo __METHOD__.PHP_EOL; $callback = static function () { return CURL_PUSH_OK; }; curl_multi_setopt($this->mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); curl_multi_setopt($this->mh, CURLMOPT_PUSHFUNCTION, $callback); } } use Swoole\Runtime; use function Swoole\Coroutine\run; Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () { $buzz = new MyHttpClient(); $buzz->sendRequest(); $buzz = null; }); ?> --EXPECT-- MyHttpClient::sendRequest[1] MyHttpClient::addServerPushCallback MyHttpClient::sendRequest[2] MyHttpClient::sendRequest[3] Start handle request. ================================================ FILE: tests/swoole_curl/multi/bug77946.phpt ================================================ --TEST-- swoole_curl/multi: Bug #77946 (Errored cURL resources returned by curl_multi_info_read() must be compatible with curl_errno() and curl_error()) --SKIPIF-- --FILE-- $url) { $conn[$i] = curl_init($url); curl_multi_add_handle($mh, $conn[$i]); } do { $status = curl_multi_exec($mh, $active); $info = curl_multi_info_read($mh); if (false !== $info) { Assert::eq($info['result'], 1); Assert::eq(curl_errno($info['handle']), CURLE_UNSUPPORTED_PROTOCOL); Assert::contains(curl_error($info['handle']), 'Protocol "unknown" not supported'); } } while ($status === CURLM_CALL_MULTI_PERFORM || $active); foreach ($urls as $i => $url) { curl_close($conn[$i]); } curl_multi_close($mh); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/close.phpt ================================================ --TEST-- swoole_curl/multi: clean handle --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/curl_basic_018.phpt ================================================ --TEST-- swoole_curl/multi: Test curl_setopt() with curl_multi function with basic functionality --CREDITS-- TestFest 2009 - AFUP - Thomas Rabaix --SKIPIF-- --FILE-- run(function ($host) { // start testing echo "*** Testing curl_exec() : basic functionality ***\n"; $url = "{$host}/get.inc?test=get"; $chs = array( 0 => curl_init(), 1 => curl_init(), 2 => curl_init(), ); ob_start(); // start output buffering $options = array( CURLOPT_RETURNTRANSFER => 1, CURLOPT_URL => $url, ); curl_setopt_array($chs[0], $options); //set the options curl_setopt_array($chs[1], $options); //set the options curl_setopt_array($chs[2], $options); //set the options $mh = curl_multi_init(); // add handlers curl_multi_add_handle($mh, $chs[0]); curl_multi_add_handle($mh, $chs[1]); curl_multi_add_handle($mh, $chs[2]); $running=null; //execute the handles do { curl_multi_exec($mh, $running); } while ($running > 0); $curl_content = ''; $curl_content .= curl_multi_getcontent($chs[0]); $curl_content .= curl_multi_getcontent($chs[1]); $curl_content .= curl_multi_getcontent($chs[2]); //close the handles curl_multi_remove_handle($mh, $chs[0]); curl_multi_remove_handle($mh, $chs[1]); curl_multi_remove_handle($mh, $chs[2]); curl_multi_close($mh); var_dump( $curl_content ); }); ?> --EXPECT-- *** Testing curl_exec() : basic functionality *** string(75) "Hello World! Hello World!Hello World! Hello World!Hello World! Hello World!" ================================================ FILE: tests/swoole_curl/multi/curl_copy_handle_variation4.phpt ================================================ --TEST-- swoole_curl/multi: curl_copy_handle() allows to post CURLFile multiple times with curl_multi_exec() --SKIPIF-- --FILE-- run(function ($host) { $ch1 = curl_init(); curl_setopt($ch1, CURLOPT_SAFE_UPLOAD, 1); curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=file"); // curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); $filename = __DIR__ . '/АБВ.txt'; file_put_contents($filename, "Test."); $file = curl_file_create($filename); $params = array('file' => $file); var_dump(curl_setopt($ch1, CURLOPT_POSTFIELDS, $params)); $ch2 = curl_copy_handle($ch1); $ch3 = curl_copy_handle($ch1); $mh = curl_multi_init(); curl_multi_add_handle($mh, $ch1); curl_multi_add_handle($mh, $ch2); do { $status = curl_multi_exec($mh, $active); if ($active) { curl_multi_select($mh); } } while ($active && $status == CURLM_OK); curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_remove_handle($mh, $ch3); curl_multi_close($mh); }); echo PHP_EOL; ?> ===DONE=== --EXPECT-- bool(true) АБВ.txt|application/octet-stream|5АБВ.txt|application/octet-stream|5 ===DONE=== --CLEAN-- ================================================ FILE: tests/swoole_curl/multi/curl_int_cast.phpt ================================================ --TEST-- swoole_curl/multi: Casting CurlHandle to int returns object ID --SKIPIF-- --FILE-- --EXPECTF-- int(%d) int(%d) int(%d) int(%d) ================================================ FILE: tests/swoole_curl/multi/curl_multi_close_basic.phpt ================================================ --TEST-- swoole_curl/multi: curl_multi_close --CREDITS-- Stefan Koopmanschap #testfest Utrecht 2009 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/curl_multi_close_basic001.phpt ================================================ --TEST-- swoole_curl/multi: curl_multi_close return false when supplied resource not valid cURL multi handle --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/curl_multi_close_reference.phpt ================================================ --TEST-- swoole_curl/multi: curl_multi_close closed by cleanup functions --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_curl/multi/curl_multi_errno_strerror_001.phpt ================================================ --TEST-- swoole_curl/multi: curl_multi_errno and curl_multi_strerror basic test --SKIPIF-- --FILE-- getMessage() . "\n"; } $errno = curl_multi_errno($mh); echo $errno . PHP_EOL; echo curl_multi_strerror($errno) . PHP_EOL; }); }); $output = $pm->getChildOutput(); Assert::contains($output, "0\nNo error"); if (PHP_VERSION_ID < 80000) { Assert::contains($output, "Warning: curl_multi_setopt(): Invalid curl multi configuration option"); } else { Assert::contains($output, "0\nNo error"); Assert::contains($output, "curl_multi_setopt(): Argument #2 (\$option) is not a valid cURL multi option"); } Assert::contains($output, "6\nUnknown option"); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/curl_multi_getcontent_basic3.phpt ================================================ --TEST-- swoole_curl/multi: Curl_multi_getcontent() basic test with different sources (local file/http) --CREDITS-- Rein Velt (rein@velt.org) #TestFest Utrecht 20090509 --SKIPIF-- --FILE-- run(function ($host) { //CURL_MULTI_GETCONTENT TEST //CREATE RESOURCES $ch1 = curl_init(); $ch2 = curl_init(); //SET URL AND OTHER OPTIONS curl_setopt($ch1, CURLOPT_URL, "{$host}/get.inc?test=getpost&get_param=Hello%20World"); curl_setopt($ch2, CURLOPT_URL, "file://" . __DIR__ . DIRECTORY_SEPARATOR . "curl_testdata2.txt"); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); //CREATE MULTIPLE CURL HANDLE $mh = curl_multi_init(); //ADD THE 2 HANDLES curl_multi_add_handle($mh, $ch1); curl_multi_add_handle($mh, $ch2); //EXECUTE $running = 0; do { curl_multi_exec($mh, $running); } while ($running > 0); $results1 = curl_multi_getcontent($ch1); $results2 = curl_multi_getcontent($ch2); //CLOSE curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_close($mh); echo $results1; echo $results2; }); echo "CURL2\n"; ?> --EXPECT-- array(2) { ["test"]=> string(7) "getpost" ["get_param"]=> string(11) "Hello World" } array(0) { } CURL2 ================================================ FILE: tests/swoole_curl/multi/curl_multi_info_read.phpt ================================================ --TEST-- swoole_curl/multi: array curl_multi_info_read ( resource $mh [, int &$msgs_in_queue = NULL ] ); --CREDITS-- marcosptf - - @phpsp - sao paulo - br --SKIPIF-- --FILE-- $url) { $conn[$i] = curl_init($url); curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, 1); curl_multi_add_handle($mh, $conn[$i]); } do { $status = curl_multi_exec($mh, $active); } while ($status === CURLM_CALL_MULTI_PERFORM || $active); while ($info = curl_multi_info_read($mh)) { Assert::count($info, 3); } foreach ($urls as $i => $url) { curl_close($conn[$i]); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/curl_multi_init_basic.phpt ================================================ --TEST-- swoole_curl/multi: Test curl_multi_init() --CREDITS-- Mark van der Velden #testfest Utrecht 2009 --SKIPIF-- --FILE-- --EXPECTF-- *** Testing curl_multi_init(void); *** ================================================ FILE: tests/swoole_curl/multi/curl_multi_segfault.phpt ================================================ --TEST-- swoole_curl/multi: Segfault due to libcurl connection caching --SKIPIF-- --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_curl/multi/curl_multi_select_basic1.phpt ================================================ --TEST-- swoole_curl/multi: Test curl_multi_select() --CREDITS-- Ivo Jansch #testfest Utrecht 2009 --SKIPIF-- --FILE-- --EXPECTF-- %r(0|-1)%r ================================================ FILE: tests/swoole_curl/multi/curl_multi_setopt_basic001.phpt ================================================ --TEST-- swoole_curl/multi: curl_multi_setopt basic test --SKIPIF-- --FILE-- getMessage() . "\n"; } curl_multi_close($mh); }); }); $output = $pm->getChildOutput(); Assert::contains($output, 'bool(true)'); if (PHP_VERSION_ID < 80000) { Assert::contains($output, 'Warning: curl_multi_setopt(): Invalid curl multi configuration option'); } else { Assert::contains($output, 'curl_multi_setopt(): Argument #2 ($option) is not a valid cURL multi option'); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/dtor.phpt ================================================ --TEST-- swoole_curl/multi: multi dtor --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/multiple_binding.phpt ================================================ --TEST-- swoole_curl/multi: multiple binding --SKIPIF-- --FILE-- run(function ($host) { $ch1 = curl_init(); curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=get"); $mh1 = curl_multi_init(); Assert::eq(curl_multi_add_handle($mh1, $ch1), 0); $mh2 = curl_multi_init(); Assert::eq(curl_multi_add_handle($mh2, $ch1), CURLM_ADDED_ALREADY); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/multi/no_hook.phpt ================================================ --TEST-- swoole_curl/multi: no hook --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/multi/select_cancel.phpt ================================================ --TEST-- swoole_curl/multi: select twice --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $mh = curl_multi_init(); $add_handle = function ($url) use($mh) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_multi_add_handle($mh, $ch); return $ch; }; $ch1 = $add_handle("http://127.0.0.1:{$pm->getFreePort()}/"); $active = null; do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); $now = microtime(true); $cid = Coroutine::getCid(); go(function () use($cid) { System::sleep(0.005); Coroutine::cancel($cid); }); while ($active && $mrc == CURLM_OK) { $n = curl_multi_select($mh, TIMEOUT); phpt_var_dump('return value:'.$n); phpt_var_dump('swoole error:'.swoole_last_error()); if ($n != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } if (Coroutine::isCanceled()) { Assert::eq(swoole_last_error(), SWOOLE_ERROR_CO_CANCELED); echo "CANCELED\n"; break; } } Assert::eq(curl_multi_info_read($mh), false); curl_multi_remove_handle($mh, $ch1); curl_multi_close($mh); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { sleep(20000); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- CANCELED Done ================================================ FILE: tests/swoole_curl/multi/select_timeout.phpt ================================================ --TEST-- swoole_curl/multi: select timeout --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $mh = curl_multi_init(); $add_handle = function ($url) use($mh) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_multi_add_handle($mh, $ch); return $ch; }; $ch1 = $add_handle("http://127.0.0.1:{$pm->getFreePort()}/"); $active = null; do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); $now = microtime(true); while ($active && $mrc == CURLM_OK) { $tm = microtime(true); $n = curl_multi_select($mh, TIMEOUT); Assert::lessThan(microtime(true) - $tm, TIMEOUT + 0.05); $error = swoole_last_error(); phpt_var_dump('select return value: '.$n); phpt_var_dump('swoole error: '.$error); if ($n != -1) { do { $mrc = curl_multi_exec($mh, $active); phpt_var_dump('exec return value: '.$mrc); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } if (microtime(true) - $now >= TIMEOUT * 4) { echo "TIMEOUT\n"; break; } } Assert::eq(curl_multi_info_read($mh), false); curl_multi_remove_handle($mh, $ch1); curl_multi_close($mh); }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => 1, 'log_file' => '/dev/null', 'max_wait_time' => 1,]); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { sleep(20000); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- TIMEOUT Done ================================================ FILE: tests/swoole_curl/multi/select_twice.phpt ================================================ --TEST-- swoole_curl/multi: select twice --SKIPIF-- --FILE-- true]); echo "Done\n"; }); } }); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d Stack trace: #0 %s(%d): curl_multi_select(%s) %A thrown in %s on line %d [Coroutine-%d] Stack trace: ------------------------------------------------------------------- #0 %s(%d): curl_multi_select(Object(CurlMultiHandle)) #1 %s(%d): swoole_test_curl_multi_ex(Object(CurlMultiHandle), Array) #2 %s(%d): swoole_test_curl_multi(Array) #3 [internal function]: {%s}() ================================================ FILE: tests/swoole_curl/non_exclusive.phpt ================================================ --TEST-- swoole_curl: non-exclusive --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $header_count = 0; curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) use (&$header_count) { Assert::eq(curl_getinfo($ch, CURLINFO_HTTP_CODE), 200); $header_count++; return strlen($strHeader); }); $output = curl_exec($ch); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } Assert::greaterThan($header_count, 1); curl_close($ch); echo "Close\n"; }); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort()); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(30000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Close Done ================================================ FILE: tests/swoole_curl/setopt/1.phpt ================================================ --TEST-- swoole_curl/setopt: curl_setopt_array() function - tests setting multiple cURL options with curl_setopt_array() --CREDITS-- Mattijs Hoitink mattijshoitink@gmail.com #Testfest Utrecht 2009 --SKIPIF-- --FILE-- run(function ($host) { // Use the set Environment variable $url = "{$host}/get.php?test=get"; // Start the test echo '== Starting test curl_setopt_array($ch, $options); ==' . "\n"; // curl handler $ch = curl_init(); // options for the curl handler $options = array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => 1 ); curl_setopt_array($ch, $options); $returnContent = curl_exec($ch); curl_close($ch); var_dump($returnContent); }); ?> --EXPECT-- == Starting test curl_setopt_array($ch, $options); == string(25) "Hello World! Hello World!" ================================================ FILE: tests/swoole_curl/setopt/3.phpt ================================================ --TEST-- swoole_curl/setopt: curl_setopt() call with CURLOPT_HTTPHEADER --CREDITS-- Paul Sohier #phptestfest utrecht --SKIPIF-- --FILE-- disableNativeCurl(); $cm->run(function ($host) { // start testing echo "*** curl_setopt() call with CURLOPT_HTTPHEADER\n"; $url = "{$host}/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_HTTPHEADER, 1); $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); $ch = curl_init(); ob_start(); // start output buffering curl_setopt($ch, CURLOPT_HTTPHEADER, array()); curl_setopt($ch, CURLOPT_URL, $host); $curl_content = curl_exec($ch); ob_end_clean(); curl_close($ch); var_dump( $curl_content ); }); ?> --EXPECTF-- *** curl_setopt() call with CURLOPT_HTTPHEADER Warning: swoole_curl_setopt(): You must pass either an object or an array with the CURLOPT_HTTPHEADER argument in %s on line %d bool(false) bool(true) ================================================ FILE: tests/swoole_curl/setopt/4.phpt ================================================ --TEST-- swoole_curl/setopt: curl_setopt() call with CURLOPT_RETURNTRANSFER --CREDITS-- Paul Sohier #phptestfest utrecht --SKIPIF-- --FILE-- run(function ($host) { // start testing echo "*** curl_setopt() call with CURLOPT_RETURNTRANSFER set to 1\n"; $url = "{$host}/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); $curl_content = curl_exec($ch); curl_close($ch); var_dump( $curl_content ); echo "*** curl_setopt() call with CURLOPT_RETURNTRANSFER set to 0\n"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0); curl_setopt($ch, CURLOPT_URL, $url); ob_start(); $curl_content = curl_exec($ch); ob_end_clean(); curl_close($ch); var_dump( $curl_content ); }); ?> --EXPECTF-- *** curl_setopt() call with CURLOPT_RETURNTRANSFER set to 1 string(%d) "%a" *** curl_setopt() call with CURLOPT_RETURNTRANSFER set to 0 bool(true) ================================================ FILE: tests/swoole_curl/setopt/5_skip.phpt ================================================ --TEST-- swoole_curl/setopt: cURL option CURLOPT_READFUNCTION --CREDITS-- WHITE new media architects - Jeroen Vermeulen #testfest Utrecht 2009 --SKIPIF-- --FILE-- ===DONE=== --CLEAN-- --EXPECT-- string(27) "custom:contents of tempfile" ===DONE=== ================================================ FILE: tests/swoole_curl/setopt/filetime_1.phpt ================================================ --TEST-- swoole_curl/setopt: CURLOPT_FILETIME --SKIPIF-- --FILE-- run(function ($host) { $ch = curl_init(); $options = array( CURLOPT_URL => 'https://static.zhihu.com/heifetz/chunks/5946.4600cc0c1b3dcecac17c.js', CURLOPT_RETURNTRANSFER => 1, CURLOPT_FILETIME => true, CURLOPT_NOBODY => true, ); curl_setopt_array($ch, $options); curl_exec($ch); $info = curl_getinfo($ch); Assert::assert(!empty($info['filetime'])); Assert::greaterThanEq($info['filetime'], 1000000); curl_close($ch); echo "END\n"; }, false); ?> --EXPECT-- END ================================================ FILE: tests/swoole_curl/setopt/filetime_2.phpt ================================================ --TEST-- swoole_curl/setopt: CURLOPT_FILETIME [return -1] --SKIPIF-- --FILE-- run(function ($host) { $url = "{$host}/get.php?test=get"; $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => 1, CURLOPT_FILETIME => true, ); curl_setopt_array($ch, $options); curl_exec($ch); $info = curl_getinfo($ch); Assert::assert(!empty($info['filetime'])); Assert::eq($info['filetime'], -1); curl_close($ch); echo "END\n"; }); ?> --EXPECT-- END ================================================ FILE: tests/swoole_curl/setopt/header_out.phpt ================================================ --TEST-- swoole_curl/setopt: CURLINFO_HEADER_OUT --SKIPIF-- --FILE-- run(function ($host) { $url = "{$host}/post.php?test=get"; $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLINFO_HEADER_OUT => true, CURLOPT_RETURNTRANSFER => 1, CURLOPT_POST => 1, CURLOPT_POSTFIELDS => 'id=123&name=swoole', ); curl_setopt_array($ch, $options); $returnContent = curl_exec($ch); Assert::assert($returnContent); $info = curl_getinfo($ch); Assert::assert($info['request_header']); curl_close($ch); echo "END\n"; }); ?> --EXPECT-- END ================================================ FILE: tests/swoole_curl/setopt/infile.phpt ================================================ --TEST-- swoole_curl/setopt: CURLOPT_INFILE --SKIPIF-- --FILE-- run(function ($host) { $url = 'http://' . HTTPBIN_SERVER_HOST . ':' . HTTPBIN_SERVER_PORT . '/put'; $fp = fopen(__FILE__, "r"); $ch = curl_init(); curl_setopt($ch, CURLOPT_USERPWD, 'user:password'); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_PUT, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_INFILESIZE, filesize(__FILE__)); $http_result = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); var_dump($http_code); $http_body = json_decode($http_result, true); var_dump($http_body['headers']['Authorization']); Assert::same($http_body['data'], file_get_contents(__FILE__)); curl_close($ch); fclose($fp); }); ?> --EXPECT-- int(200) string(26) "Basic dXNlcjpwYXNzd29yZA==" ================================================ FILE: tests/swoole_curl/setopt/nobody.phpt ================================================ --TEST-- swoole_curl/setopt: CURLOPT_NOBODY --SKIPIF-- --FILE-- run(function ($host) { $url = "{$host}/get.php?test=get"; $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => 1, CURLOPT_NOBODY => true, ); curl_setopt_array($ch, $options); $returnContent = curl_exec($ch); Assert::isEmpty($returnContent); curl_close($ch); echo "END\n"; }); ?> --EXPECT-- END ================================================ FILE: tests/swoole_curl/share/1.phpt ================================================ --TEST-- swoole_curl/share: Basic curl_share test --SKIPIF-- --FILE-- run(function ($host) { $sh = curl_share_init(); $ch1 = curl_init(); curl_setopt($ch1, CURLOPT_URL, 'file://' . dirname(__DIR__) . '/upload/curl_testdata1.txt'); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch1, CURLOPT_SHARE, $sh); $ch2 = curl_init(); curl_setopt($ch2, CURLOPT_URL, 'file://' . dirname(__DIR__) . '/upload/curl_testdata2.txt'); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch2, CURLOPT_SHARE, $sh); // Make sure nothing bad handles if the share handle is unset early. unset($sh); var_dump(curl_exec($ch1)); var_dump(curl_exec($ch2)); }); ?> --EXPECT-- string(6) "CURL1 " string(6) "CURL2 " ================================================ FILE: tests/swoole_curl/share/5.phpt ================================================ --TEST-- swoole_curl/share: Basic curl_share test --SKIPIF-- --FILE-- run(function ($host) { $sh = curl_share_init(); curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); $cookie_value = 'swoole'; $cookie_name = 'test_cookie'; $url = "{$host}/get.php?test=cookie_set&cookie_name={$cookie_name}&cookie_value={$cookie_value}&cookie_expire=" . (time() + 3600); $ch1 = curl_init($url); curl_setopt($ch1, CURLOPT_SHARE, $sh); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch1, CURLOPT_COOKIEFILE, ""); $rs1 = curl_exec($ch1); Assert::notEmpty($rs1); Assert::eq(json_decode($rs1), true); $url = "{$host}/get.php?test=cookie_get"; $ch2 = curl_init($url); curl_setopt($ch2, CURLOPT_SHARE, $sh); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch2, CURLOPT_COOKIEFILE, ""); $rs2 = curl_exec($ch2); Assert::notEmpty($rs2); Assert::eq(json_decode($rs2)->$cookie_name, $cookie_value); curl_share_close($sh); curl_close($ch1); curl_close($ch2); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/sleep.phpt ================================================ --TEST-- swoole_curl: sleep --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); $s = microtime(true); run(function () use ($pm) { $n = N; while($n--) { go(function() use ($pm) { $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); $output = curl_exec($ch); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); }); } }); Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['worker_num' => N, 'log_file' => '/dev/null']); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(300000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/ssl/version.phpt ================================================ --TEST-- swoole_curl/ssl: Test SSL_VERSION option --SKIPIF-- --FILE-- run(function ($host) { $ch = curl_init(); curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, "https://www.baidu.com/"); $result = curl_exec($ch); Assert::assert($result); Assert::contains($result, '百度'); curl_close($ch); }, false); ?> --EXPECTF-- ================================================ FILE: tests/swoole_curl/symfony-noco.phpt ================================================ --TEST-- swoole_curl: symfony http client --SKIPIF-- --FILE-- 5]) ); $uid = uniqid(); $req = Psr17FactoryDiscovery::findRequestFactory() ->createRequest('POST', 'http://www.httpbin.org/post') ->withHeader('Content-Type', 'application/json') ->withBody(Psr17FactoryDiscovery::findStreamFactory()->createStream(json_encode(['key' => $uid]))); $res = (new PluginClient($httpClient))->sendAsyncRequest($req)->wait(); $json = $res->getBody()->getContents(); Assert::notEmpty($json); $data_1 = json_decode($json); $data_2 = json_decode($data_1->data); Assert::eq($data_2->key, $uid); echo 'Done' . PHP_EOL; ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/symfony.phpt ================================================ --TEST-- swoole_curl: symfony http client --SKIPIF-- --FILE-- 5]) ); $uid = uniqid(); $req = Psr17FactoryDiscovery::findRequestFactory() ->createRequest('POST', 'http://www.httpbin.org/post') ->withHeader('Content-Type', 'application/json') ->withBody(Psr17FactoryDiscovery::findStreamFactory()->createStream(json_encode(['key' => $uid]))); $res = (new PluginClient($httpClient))->sendAsyncRequest($req)->wait(); $json = $res->getBody()->getContents(); Assert::notEmpty($json); $data_1 = json_decode($json); $data_2 = json_decode($data_1->data); Assert::eq($data_2->key, $uid); echo 'Done' . PHP_EOL; }); Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_curl/template ================================================ require __DIR__ . '/../../include/bootstrap.php'; $cm = new \SwooleTest\CurlManager(); $cm->run(function ($host) { }); ================================================ FILE: tests/swoole_curl/timer_coredump.phpt ================================================ --TEST-- swoole_curl: timer coredump --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_curl/undefined_behavior/0.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 0 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(0); Assert::contains($output, "Warning: curl_close(): supplied resource is not a valid cURL handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/1.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 1 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(255); Assert::contains($output, "curl_close(): supplied resource is not a valid Swoole-Coroutine-cURL-Handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/2.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 2 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(255); Assert::contains($output, "curl_setopt(): supplied resource is not a valid Swoole-Coroutine-cURL-Handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/3.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 3 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(0); Assert::contains($output, "curl_close(): supplied resource is not a valid cURL handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/4.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 4 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(0); Assert::contains($output, "curl_close(): supplied resource is not a valid cURL handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/5.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 5 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(255); Assert::contains($output, "curl_multi_close(): supplied resource is not a valid Swoole-Coroutine-cURL-Multi-Handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/6.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 6 --SKIPIF-- --FILE-- getChildOutput(); $pm->expectExitCode(0); ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/7.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 7 --SKIPIF-- --FILE-- getChildOutput(); if (PHP_VERSION_ID < 80000) { $pm->expectExitCode(0); Assert::contains($output, "Warning: curl_multi_close(): supplied resource is not a valid cURL Multi Handle resource"); } else { $pm->expectExitCode(0); } ?> --EXPECT-- ================================================ FILE: tests/swoole_curl/undefined_behavior/8.phpt ================================================ --TEST-- swoole_curl/undefined_behavior: 8 --SKIPIF-- --FILE-- --EXPECTF-- Warning: curl_exec(): The given handle is not initialized in coroutine in %s on line %d ================================================ FILE: tests/swoole_curl/upload/1.phpt ================================================ --TEST-- swoole_curl/upload: CURL file uploading --INI-- --SKIPIF-- --FILE-- true, // 'enable_kqueue' => true, ]); $cm = new \SwooleTest\CurlManager(); $cm->run(function ($host) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=file"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); testcurl($ch, __DIR__ . '/curl_testdata1.txt'); testcurl($ch, __DIR__ . '/curl_testdata1.txt', 'text/plain'); testcurl($ch, __DIR__ . '/curl_testdata1.txt', '', 'foo.txt'); testcurl($ch, __DIR__ . '/curl_testdata1.txt', 'text/plain', 'foo.txt'); $file = new CurlFile(__DIR__ . '/curl_testdata1.txt'); $file->setMimeType('text/plain'); var_dump($file->getMimeType()); var_dump($file->getFilename()); curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file)); var_dump(curl_exec($ch)); $file = curl_file_create(__DIR__ . '/curl_testdata1.txt'); $file->setPostFilename('foo.txt'); var_dump($file->getPostFilename()); curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file)); var_dump(curl_exec($ch)); try { curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 0); } catch (throwable $e) { trigger_error($e->getMessage(), E_USER_WARNING); } $params = array('file' => '@' . __DIR__ . '/curl_testdata1.txt'); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); var_dump(curl_exec($ch)); curl_setopt($ch, CURLOPT_SAFE_UPLOAD, true); $params = array('file' => '@' . __DIR__ . '/curl_testdata1.txt'); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); var_dump(curl_exec($ch)); curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=post"); $params = array('file' => '@' . __DIR__ . '/curl_testdata1.txt'); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); var_dump(curl_exec($ch)); curl_close($ch); }); function testcurl($ch, $name, $mime = '', $postname = '') { if(!empty($postname)) { $file = new CurlFile($name, $mime, $postname); } else if(!empty($mime)) { $file = new CurlFile($name, $mime); } else { $file = new CurlFile($name); } curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file)); $ret = curl_exec($ch) or die("ERROR: Code=".curl_errno($ch). "Msg=".curl_error($ch)."\n"); var_dump($ret); } ?> --EXPECTF-- string(%d) "curl_testdata1.txt|application/octet-stream|6" string(%d) "curl_testdata1.txt|text/plain|6" string(%d) "foo.txt|application/octet-stream|6" string(%d) "foo.txt|text/plain|6" string(%d) "text/plain" string(%d) "%s/curl_testdata1.txt" string(%d) "curl_testdata1.txt|text/plain|6" string(%d) "foo.txt" string(%d) "foo.txt|application/octet-stream|6" Warning: %s(): Disabling safe uploads is no longer supported in %s on line %d string(0) "" string(0) "" string(%d) "array(1) { ["file"]=> string(%d) "@%s/curl_testdata1.txt" } " ================================================ FILE: tests/swoole_curl/upload/2.phpt ================================================ --TEST-- swoole_curl/upload: CURL file uploading --INI-- --SKIPIF-- --FILE-- $file)); $result = curl_exec($ch); Assert::notEmpty($result); $json = json_decode($result); Assert::notEmpty($json); Assert::notEmpty($json->files->swoole_file); $prefix = 'data:application/octet-stream;base64,'; Assert::startsWith($json->files->swoole_file, $prefix); $data = substr($json->files->swoole_file, strlen($prefix)); Assert::eq(md5(base64_decode($data)), md5_file(TEST_IMAGE)); }); ?> --EXPECTF-- ================================================ FILE: tests/swoole_curl/upload/3.phpt ================================================ --TEST-- swoole_curl/upload: CURL file uploading[INFILE] --INI-- --SKIPIF-- --FILE-- run(function ($host) { Runtime::enableCoroutine(SWOOLE_HOOK_ALL); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=input"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_PUT, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $fp = fopen(TEST_IMAGE, 'r'); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_HTTPHEADER, array("Expect:")); curl_setopt($ch, CURLOPT_INFILESIZE, filesize(TEST_IMAGE)); $http_result = curl_exec($ch); Assert::eq(md5($http_result), md5_file(TEST_IMAGE)); }); ?> --EXPECTF-- ================================================ FILE: tests/swoole_curl/upload/curl_testdata1.txt ================================================ CURL1 ================================================ FILE: tests/swoole_curl/upload/curl_testdata2.txt ================================================ CURL2 ================================================ FILE: tests/swoole_curl/yield_in_callback.phpt ================================================ --TEST-- swoole_curl: suspend in callback --SKIPIF-- --FILE-- --EXPECT-- exec exec end Close ================================================ FILE: tests/swoole_event/add_after_server_start.phpt ================================================ --TEST-- swoole_event: add event after server start --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $pm->kill(); $str = swoole_string(file_get_contents(FILE)); Assert::true($str->contains('HTTP/1.1 302 Moved Temporarily') or $str->contains('HTTP/1.1 301 Moved Permanently')); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on("start", function (Server $serv) use ($pm) { $fp = stream_socket_client("tcp://" . TEST_DOMAIN_3 . ":80", $errno, $errstr, 30); fwrite($fp, "GET / HTTP/1.1\r\nHost: " . TEST_DOMAIN_3 . "\r\n\r\n"); Swoole\Event::add($fp, function ($fp) use ($pm) { $resp = fread($fp, 8192); Swoole\Event::del($fp); fclose($fp); file_put_contents(FILE, $resp); $pm->wakeup(); }); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(FILE); ?> --EXPECT-- ================================================ FILE: tests/swoole_event/cycle.phpt ================================================ --TEST-- swoole_event: cycle --SKIPIF-- --FILE-- --EXPECT-- cycle [0] timer [1] defer [2] cycle [1] timer [2] cycle [2] ================================================ FILE: tests/swoole_event/defer.phpt ================================================ --TEST-- swoole_event: Swoole\Event::defer --SKIPIF-- --FILE-- --EXPECT-- defer [1] timer [1] defer [2] timer [2] ================================================ FILE: tests/swoole_event/defer_with_sleep.phpt ================================================ --TEST-- swoole_event: Swoole\Event::defer and sleep --SKIPIF-- --FILE-- --EXPECT-- defer [1] timer [1] defer [2] timer [2] ================================================ FILE: tests/swoole_event/defer_without_io.phpt ================================================ --TEST-- swoole_event: Swoole\Event::defer without io --SKIPIF-- --FILE-- --EXPECT-- defer [1] ================================================ FILE: tests/swoole_event/del.phpt ================================================ --TEST-- swoole_event: Swoole\Event::del --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_event/del_after_close.phpt ================================================ --TEST-- swoole_event: del after close --SKIPIF-- --FILE-- SWOOLE_LOG_ERROR]); $cli = new Swoole\Client(SWOOLE_SOCK_TCP); $cli->connect("www.qq.com", 80); $fd = $cli->sock; Event::add($fd, function ($fd) use ($cli) { $resp = $cli->recv(8192); Swoole\Event::del($fd); $cli->close(); }); Event::write($fd, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); $cli->close(); if (Event::isset($fd)) { if (!Event::del($fd)) { echo "Unable to release fd {$fd} from EventLoop\n"; } else { echo "FD {$fd} released from EventLoop\n"; } } $eventNum = Swoole\Coroutine::stats()['event_num']; var_dump($eventNum); Event::wait(); ?> --EXPECTF-- FD %d released from EventLoop int(0) ================================================ FILE: tests/swoole_event/deprecated_event_wait.phpt ================================================ --TEST-- swoole_event: deprecated_event_wait --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_event/dispatch.phpt ================================================ --TEST-- swoole_event: dispatch --SKIPIF-- --FILE-- --EXPECT-- loop loop Tick loop Tick loop Tick loop Tick ================================================ FILE: tests/swoole_event/function_alias.phpt ================================================ --TEST-- swoole_event: function alias --SKIPIF-- --FILE-- --EXPECTF-- bool(true) ================================================ FILE: tests/swoole_event/isset.phpt ================================================ --TEST-- swoole_event: Swoole\Event::isset --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_event/kqueue.phpt ================================================ --TEST-- swoole_event: write() --SKIPIF-- --FILE-- true, ]); $fp = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30); Swoole\Event::add($fp, function($fp) { $resp = fread($fp, 8192); Swoole\Event::del($fp); fclose($fp); echo "SUCCESS\n"; Swoole\Timer::after(100, function () { posix_kill(posix_getpid(), SIGIO); Swoole\Timer::after(100, function () { echo "Done\n"; }); }); }); Swoole\Event::write($fp, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); Swoole\Process::signal(SIGIO, function () { echo "SIGIO received\n"; Swoole\Process::signal(SIGIO, null); }); echo "Finish\n"; Swoole\Event::wait(); ?> --EXPECT-- Finish SUCCESS SIGIO received Done ================================================ FILE: tests/swoole_event/rshutdown.phpt ================================================ --TEST-- swoole_event: read stdin --SKIPIF-- --FILE-- --EXPECTF-- Deprecated: swoole_event_rshutdown(): Event::wait() in shutdown function is deprecated in Unknown on line 0 string(0) "" ================================================ FILE: tests/swoole_event/set.phpt ================================================ --TEST-- swoole_event: Swoole\Event::set --SKIPIF-- --FILE-- --EXPECT-- Finish write_callback: SUCCESS ================================================ FILE: tests/swoole_event/simple.phpt ================================================ --TEST-- swoole_event: Swoole\Event::exit --SKIPIF-- --FILE-- --EXPECT-- tick ================================================ FILE: tests/swoole_event/sockets.phpt ================================================ --TEST-- swoole_event: add event after server start --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Unable to create socket\n"); socket_set_nonblock($socket) or die("Unable to set nonblock on socket\n"); function socket_onRead($socket) { static $i = 0; $line = socket_read($socket, 8192); if (!$line) { exit("ERROR\n"); } Assert::eq($line, "Swoole: " . GREETING_MESSAGE); if ($i > 10) { echo "DONE\n"; Event::del($socket); socket_close($socket); } else { usleep(10000); $i++; Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); } } function socket_onWrite($socket) { socket_write($socket, GREETING_MESSAGE); Event::set($socket, null, null, SWOOLE_EVENT_READ); } function socket_onConnect($socket) { $err = socket_get_option($socket, SOL_SOCKET, SO_ERROR); if ($err == 0) { echo "CONNECTED\n"; Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ); socket_write($socket, GREETING_MESSAGE); } else { echo "connect server failed\n"; Event::del($socket); socket_close($socket); } } Event::add($socket, 'socket_onRead', 'socket_onConnect', SWOOLE_EVENT_WRITE); socket_connect($socket, '127.0.0.1', $pm->getFreePort()); Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'log_file' => '/dev/null', )); $serv->on("start", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- CONNECTED DONE ================================================ FILE: tests/swoole_event/sync_client_1.phpt ================================================ --TEST-- swoole_event: sync client --SKIPIF-- --FILE-- false]); $fp = new Client(SWOOLE_SOCK_TCP); // sync connect $fp->connect('www.qq.com', 80); Event::add($fp, function($fp) { $resp = $fp->recv(8192); Assert::contains($resp, 'Location: https://www.qq.com/'); Event::del($fp); $fp->close(); echo "Done\n"; }); Event::write($fp, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); Event::wait(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_event/sync_client_2.phpt ================================================ --TEST-- swoole_event: sync client --SKIPIF-- --FILE-- false]); $fp = new Client(SWOOLE_SOCK_TCP); // async connect $result = $fp->connect('www.qq.com', 80, 1, 1); Assert::true($result); Assert::eq($fp->errCode, SOCKET_EINPROGRESS); Event::add($fp, null, function (Client $fp) { $fp->send("GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); Event::set($fp, function ($fp) { $resp = $fp->recv(8192); Assert::contains($resp, 'Location: https://www.qq.com/'); Event::del($fp); $fp->close(); echo "Done\n"; }, null, SWOOLE_EVENT_READ); }, SWOOLE_EVENT_WRITE); Event::wait(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_event/wait.phpt ================================================ --TEST-- swoole_event: wait (auto) --SKIPIF-- --FILE-- --EXPECT-- register 1 register 2 register 3 register 4 register 5 register 6 register 7 register 8 ================================================ FILE: tests/swoole_event/write.phpt ================================================ --TEST-- swoole_event: write() --SKIPIF-- --FILE-- --EXPECT-- Finish SUCCESS ================================================ FILE: tests/swoole_feature/cross_close/client.phpt ================================================ --TEST-- swoole_feature/cross_close: client --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); echo "RECV\n"; go(function () use ($pm, $cli) { Co::sleep(0.001); echo "CLOSE\n"; $cli->close(); $pm->kill(); echo "DONE\n"; }); Assert::false(@$cli->recv(-1)); Assert::same($cli->errCode, SOCKET_ECANCELED); echo "CLOSED\n"; Assert::assert(!$cli->connected); }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); co::sleep(5); $conn->close(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- RECV CLOSE CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/client_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: client closed by server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); echo "RECV\n"; Assert::same($cli->recv(-1), ''); echo "CLOSED\n"; while ($ret = @$cli->send(get_safe_random())) { continue; } if ($cli->errCode) { Assert::same($cli->errCode, SOCKET_EPIPE); } while ($ret = @$cli->recv(-1)) { continue; } if ($ret === false) { Assert::same($cli->errCode, IS_MAC_OS ? SOCKET_EPIPE : SOCKET_ECONNRESET); } }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Socket)) { switch_process(); echo "CLOSE\n"; $conn->close(); switch_process(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- RECV CLOSE CLOSED ================================================ FILE: tests/swoole_feature/cross_close/full_duplex.phpt ================================================ --TEST-- swoole_feature/cross_close: full duplex --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); set_socket_coro_buffer_size($cli->exportSocket(), 8192); go(function () use ($pm, $cli) { Co::sleep(0.001); echo "CLOSE\n"; $cli->close(); $pm->kill(); echo "DONE\n"; }); go(function () use ($cli) { echo "SEND\n"; $size = 16 * 1024 * 1024; Assert::lessThan($cli->send(str_repeat('S', $size)), $size); Assert::assert(!$cli->connected); Assert::eq($cli->errCode, SOCKET_ECANCELED); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; Assert::false($cli->recv(-1)); Assert::assert(!$cli->connected); Assert::eq($cli->errCode, SOCKET_ECANCELED); echo "RECV CLOSED\n"; }); }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); co::sleep(5); $conn->close(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SEND RECV CLOSE SEND CLOSED RECV CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/full_duplex_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: full duplex and close by server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); set_socket_coro_buffer_size($cli->exportSocket(), 65536); go(function () use ($cli) { echo "SEND\n"; $size = 16 * 1024 * 1024; $str = str_repeat('S', $size); Assert::assert($cli->send($str) < $size); usleep(1000); Assert::assert($cli->send($str) < $size); Assert::same($cli->errCode, SOCKET_EPIPE); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; Assert::eq($cli->recv(-1), ''); // Assert::same($cli->errCode, SOCKET_ECONNRESET); echo "RECV CLOSED\n"; }); $pm->wakeup(); }); Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Socket)) { $pm->wait(); echo "CLOSE\n"; $conn->close(); switch_process(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- SEND RECV CLOSE %s CLOSED %s CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/http.phpt ================================================ --TEST-- swoole_feature/cross_close: http client --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $http = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); echo "GET\n"; go(function () use ($pm, $http) { Co::sleep(0.001); echo "CLOSE\n"; $http->close(); $pm->kill(); echo "DONE\n"; }); Assert::assert(!$http->get('/')); echo "CLOSED\n"; Assert::same($http->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); Assert::same($http->errCode, SOCKET_ECANCELED); Assert::assert(empty($http->body)); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function ($request, Swoole\Http\Response $response) use ($server) { switch_process(); co::sleep(3); $server->close($response->fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- GET CLOSE CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/http_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: http client closed by server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $http = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); echo "GET\n"; Assert::assert(!$http->get('/')); echo "CLOSED\n"; Assert::same($http->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); Assert::same($http->errCode, SOCKET_ECONNRESET); Assert::assert(empty($http->body)); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function ($request, Swoole\Http\Response $response) use ($server) { switch_process(); echo "CLOSE\n"; $server->close($response->fd); switch_process(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- GET CLOSE CLOSED ================================================ FILE: tests/swoole_feature/cross_close/php_stream_full_duplex.phpt ================================================ --TEST-- swoole_feature/cross_close: full duplex (php stream) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $cli = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}", $errno, $errstr, 1); Assert::true(!$errno); go(function () use ($pm, $cli) { Co::sleep(0.001); echo "CLOSE\n"; Assert::eq(get_resource_type($cli), 'stream'); Assert::true(fclose($cli)); // double close Assert::eq(get_resource_type($cli), 'Unknown'); // Assert::true(!@fclose($cli)); $pm->kill(); echo "DONE\n"; }); go(function () use ($cli) { echo "SEND\n"; $size = 64 * 1024 * 1024; Assert::eq(get_resource_type($cli), 'stream'); Assert::true(@fwrite($cli, str_repeat('S', $size)) < $size); Assert::eq(get_resource_type($cli), 'Unknown'); // Assert::true(!@fclose($cli)); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; Assert::eq(get_resource_type($cli), 'stream'); Assert::true(empty(fread($cli, 8192))); Assert::eq(get_resource_type($cli), 'Unknown'); // Assert::true(!@fclose($cli)); echo "RECV CLOSED\n"; }); }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($server->bind('127.0.0.1', $pm->getFreePort())); Assert::true($server->listen()); go(function () use ($pm, $server) { if (Assert::true(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); co::sleep(5); $conn->close(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SEND RECV CLOSE SEND CLOSED RECV CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/php_stream_full_duplex_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: full duplex and close by server (php stream) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}", $errno, $errstr, 1); Assert::true(!$errno); go(function () use ($cli) { echo "SEND\n"; $size = 64 * 1024 * 1024; Assert::true(@fwrite($cli, str_repeat('S', $size)) < $size); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; Assert::true(!fread($cli, 8192)); echo "RECV CLOSED\n"; }); $pm->wakeup(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($server->bind('127.0.0.1', $pm->getFreePort())); Assert::true($server->listen()); go(function () use ($pm, $server) { if (Assert::true(($conn = $server->accept()) && $conn instanceof Co\Socket)) { $pm->wait(); echo "CLOSE\n"; $conn->close(); switch_process(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- SEND RECV CLOSE %s CLOSED %s CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/redis.phpt ================================================ --TEST-- swoole_feature/cross_close: redis --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); $redis = new \redis; go(function () use ($pm, $redis) { $redis->connect('127.0.0.1', $pm->getFreePort()); go(function () use ($pm, $redis) { echo "GET\n"; go(function () use ($pm, $redis) { co::sleep(0.001); echo "CLOSE\n"; Assert::assert($redis->close()); echo "DONE\n"; $pm->kill(); }); try { $ret = $redis->get($pm->getRandomData()); } catch (\RedisException $e) { $ret = false; echo "CLOSED\n"; } Assert::assert(!$ret); }); }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { $pm->wakeup(); if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); $data = $conn->recv(); $random = $pm->getRandomData(); $random_len = strlen($random); Assert::same($data, "*2\r\n$3\r\nGET\r\n\${$random_len}\r\n{$random}\r\n"); switch_process(); co::sleep(5); $conn->close(); } $server->close(); }); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- GET CLOSE CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/redis_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: redis closed by server --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); go(function () use ($pm) { $redis = new \redis; Assert::assert($redis->connect('127.0.0.1', $pm->getFreePort())); echo "GET\n"; try { $redis->get($pm->getRandomData()); } catch (\RedisException $e) { echo "CLOSED\n"; } $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); $data = $conn->recv(); $random = $pm->getRandomData(); $random_len = strlen($random); Assert::same($data, "*2\r\n$3\r\nGET\r\n\${$random_len}\r\n{$random}\r\n"); echo "CLOSE\n"; $conn->close(); switch_process(); } $server->close(); }); $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- GET CLOSE CLOSED DONE ================================================ FILE: tests/swoole_feature/cross_close/stream.phpt ================================================ --TEST-- swoole_feature/cross_close: stream --SKIPIF-- --FILE-- getChildOutput(); Assert::contains($output, "READ\nCLOSE\nCLOSED\n"); if (PHP_VERSION_ID < 80500) { Assert::contains($output, "Fatal error: Uncaught TypeError: fclose(): supplied resource is not a valid stream resource"); } else { Assert::contains($output, 'Fatal error: Uncaught TypeError: fclose(): Argument #1 ($stream) must be an open stream resource'); } ?> --EXPECT-- ================================================ FILE: tests/swoole_feature/cross_close/stream_by_server.phpt ================================================ --TEST-- swoole_feature/cross_close: stream closed by server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $fp = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}", $errno, $errstr, 1); if (!$fp) { exit("$errstr ($errno)\n"); } else { echo "WRITE\n"; Assert::same(fwrite($fp, ($data = tcp_pack("Hello Swoole Server!"))), strlen($data)); echo "READ\n"; Assert::same(fread($fp, 1024), ''); echo "CLOSED\n"; fclose($fp); echo "DONE\n"; } }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => MAX_CONCURRENCY_MID]]); $server = stream_socket_server( "tcp://127.0.0.1:{$pm->getFreePort()}", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx ); if (!$server) { exit("$errstr ($errno)\n"); } else { go(function () use ($server) { if ($conn = stream_socket_accept($server, 1)) { switch_process(); Assert::same(fread($conn, tcp_length(fread($conn, tcp_type_length()))), "Hello Swoole Server!"); echo "CLOSE\n"; fclose($conn); switch_process(); } fclose($server); }); } $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- WRITE READ CLOSE CLOSED DONE ================================================ FILE: tests/swoole_feature/full_duplex/client.phpt ================================================ --TEST-- swoole_feature/full_duplex: client --SKIPIF-- --FILE-- initRandomDataEx(MAX_CONCURRENCY_LOW, MAX_REQUESTS_LOW, CHUNK_SIZE); $pm->parentFunc = function ($pid) use ($pm) { global $closer; $closer = go(function () { $closer = co::getCid(); $timer = Swoole\Timer::after(10 * 1000, function () use ($closer) { echo "TIMEOUT\n"; co::resume($closer); }); co::yield(); if (Swoole\Timer::exists($timer)) { Swoole\Timer::clear($timer); } global $clients; foreach ($clients as $client) { $client->close(); } }); for ($c = 0; $c < MAX_CONCURRENCY_LOW; $c++) { go(function () use ($pm, $c) { global $clients; $clients[] = $client = new Co\Client(SWOOLE_SOCK_TCP); $ret = $client->connect('127.0.0.1', $pm->getFreePort(), -1); if (!Assert::assert($ret)) { throw new RuntimeException('connect failed'); } else { set_socket_coro_buffer_size($client->exportSocket(), BUFFER_SIZE); $client->set([ 'open_eof_check' => true, 'package_eof' => "\n" ]); } // read go(function () use ($pm, $client, $c) { for ($n = 0; $n < MAX_REQUESTS_LOW; $n++) { // id if (!$client->send(tcp_head($c))) { break; } // length if (!$client->send(tcp_head(CHUNK_SIZE * CHUNK_NUM, 'N'))) { break; } // data $data = $pm->getRandomDataEx($c); for ($p = CHUNK_NUM; $p--;) { $send_n = 0; do { $n_bytes = $client->send(substr($data, $send_n)); if (!$n_bytes) { break; } $send_n += $n_bytes; } while ($send_n !== CHUNK_SIZE); } } }); // write go(function () use ($pm, $client) { while (($id = $client->recv(-1))) { global $count, $closer; $id = rtrim($id); @$count[$id]++; if (array_sum($count) === MAX_CONCURRENCY_LOW * MAX_REQUESTS_LOW * CHUNK_NUM) { phpt_var_dump($count); co::resume($closer); echo "DONE\n"; } } }); }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen(MAX_CONCURRENCY)); while ($conn = $server->accept(-1)) { if (!Assert::assert($conn instanceof Co\Socket)) { throw new RuntimeException('accept failed'); } else { set_socket_coro_buffer_size($conn, BUFFER_SIZE); } go(function () use ($pm, $conn) { while (true) { // id $head = $conn->recv(tcp_type_length(), -1); if (!$head || ($id = tcp_length($head)) < 0) { break; } // length $length = tcp_length($conn->recv(tcp_type_length('N'), -1), 'N'); // data $verify = $pm->getRandomDataEx($id); do { $data = ''; $need_n = CHUNK_SIZE; do { $tmp = $conn->recv($need_n, -1); if (!$tmp) { break; } $data .= $tmp; $need_n = CHUNK_SIZE - strlen($data); } while ($need_n !== 0); if (!Assert::assert($data === $verify)) { break; } $length -= strlen($data); $conn->send("{$id}\n"); } while ($length > 0); } }); } }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_feature/full_duplex/socket.phpt ================================================ --TEST-- swoole_feature/full_duplex: socket --SKIPIF-- --FILE-- initRandomDataEx(MAX_CONCURRENCY_LOW, MAX_REQUESTS_LOW, CHUNK_SIZE); $pm->parentFunc = function ($pid) use ($pm) { global $closer; $closer = go(function () { $closer = co::getCid(); $timer = Swoole\Timer::after(10 * 1000, function () use ($closer) { echo "TIMEOUT\n"; co::resume($closer); }); co::yield(); if (Swoole\Timer::exists($timer)) { Swoole\Timer::clear($timer); } global $sockets; foreach ($sockets as $socket) { $socket->close(); } }); for ($c = 0; $c < MAX_CONCURRENCY_LOW; $c++) { go(function () use ($pm, $c) { global $sockets; $sockets[] = $socket = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $ret = $socket->connect('127.0.0.1', $pm->getFreePort(), -1); if (!Assert::assert($ret)) { throw new RuntimeException('connect failed'); } else { set_socket_coro_buffer_size($socket, BUFFER_SIZE); } // read go(function () use ($pm, $socket, $c) { for ($n = 0; $n < MAX_REQUESTS_LOW; $n++) { // id if (!$socket->send(tcp_head($c))) { break; } // length if (!$socket->send(tcp_head(CHUNK_SIZE * CHUNK_NUM, 'N'))) { break; } // data $data = $pm->getRandomDataEx($c); for ($p = CHUNK_NUM; $p--;) { $send_n = 0; do { $n_bytes = $socket->send(substr($data, $send_n)); if (!$n_bytes) { break; } $send_n += $n_bytes; } while ($send_n !== CHUNK_SIZE); } } }); // write go(function () use ($pm, $socket) { while ($data = $socket->recv(tcp_type_length(), -1)) { global $count, $closer; @$count[tcp_length($data)]++; if (array_sum($count) === MAX_CONCURRENCY_LOW * MAX_REQUESTS_LOW * CHUNK_NUM) { phpt_var_dump($count); co::resume($closer); } } }); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen(MAX_CONCURRENCY)); while ($conn = $server->accept(-1)) { if (!Assert::assert($conn instanceof Co\Socket)) { throw new RuntimeException('accept failed'); } else { set_socket_coro_buffer_size($conn, BUFFER_SIZE); } go(function () use ($pm, $conn) { while (true) { // id $head = $conn->recv(tcp_type_length(), -1); if (!$head || ($id = tcp_length($head)) < 0) { break; } // length $length = tcp_length($conn->recv(tcp_type_length('N'), -1), 'N'); // data $verify = $pm->getRandomDataEx($id); do { $data = ''; $need_n = CHUNK_SIZE; do { $data .= $conn->recv($need_n, -1); $need_n = CHUNK_SIZE - strlen($data); } while ($need_n !== 0); if (!Assert::assert($data === $verify)) { break; } $length -= strlen($data); $conn->send(tcp_head($id)); } while ($length > 0); } }); } }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_feature/full_duplex/socket_ssl.phpt ================================================ --TEST-- swoole_feature/full_duplex: socket --SKIPIF-- --FILE-- initRandomDataEx(MAX_CONCURRENCY_LOW, MAX_REQUESTS_LOW, CHUNK_SIZE); $pm->parentFunc = function ($pid) use ($pm) { global $closer; $closer = go(function () { $closer = co::getCid(); $timer = Swoole\Timer::after(10 * 1000, function () use ($closer) { echo "TIMEOUT\n"; co::resume($closer); }); co::yield(); if (Swoole\Timer::exists($timer)) { Swoole\Timer::clear($timer); } global $sockets; foreach ($sockets as $socket) { $socket->close(); } }); for ($c = 0; $c < MAX_CONCURRENCY_LOW; $c++) { go(function () use ($pm, $c) { global $sockets; $sockets[] = $socket = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol(['open_ssl' => true]); $ret = $socket->connect('127.0.0.1', $pm->getFreePort(), -1); if (!Assert::assert($ret)) { throw new RuntimeException('connect failed'); } else { set_socket_coro_buffer_size($socket, BUFFER_SIZE); } // read go(function () use ($pm, $socket, $c) { for ($n = 0; $n < MAX_REQUESTS_LOW; $n++) { // id if (!$socket->send(tcp_head($c))) { break; } // length if (!$socket->send(tcp_head(CHUNK_SIZE * CHUNK_NUM, 'N'))) { break; } // data $data = $pm->getRandomDataEx($c); for ($p = CHUNK_NUM; $p--;) { $send_n = 0; do { $n_bytes = $socket->send(substr($data, $send_n)); if (!$n_bytes) { break; } $send_n += $n_bytes; } while ($send_n !== CHUNK_SIZE); } } }); // write go(function () use ($pm, $socket) { while ($data = $socket->recv(tcp_type_length(), -1)) { global $count, $closer; @$count[tcp_length($data)]++; if (array_sum($count) === MAX_CONCURRENCY_LOW * MAX_REQUESTS_LOW * CHUNK_NUM) { phpt_var_dump($count); co::resume($closer); } } }); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $server->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR.'/client.crt', 'ssl_key_file' => SSL_FILE_DIR.'/client.key' ]); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen(MAX_CONCURRENCY)); /** @var $conn Socket */ while ($conn = $server->accept(-1)) { if (!Assert::assert($conn instanceof Co\Socket)) { throw new RuntimeException('accept failed'); } else { set_socket_coro_buffer_size($conn, BUFFER_SIZE); } go(function () use ($pm, $conn) { if (!Assert::true($conn->sslHandshake())) { return; } while (true) { // id $head = $conn->recv(tcp_type_length(), -1); if (!$head || ($id = tcp_length($head)) < 0) { break; } // length $length = tcp_length($conn->recv(tcp_type_length('N'), -1), 'N'); // data $verify = $pm->getRandomDataEx($id); do { $data = ''; $need_n = CHUNK_SIZE; do { $data .= $conn->recv($need_n, -1); $need_n = CHUNK_SIZE - strlen($data); } while ($need_n !== 0); if (!Assert::assert($data === $verify)) { break; } $length -= strlen($data); $conn->send(tcp_head($id)); } while ($length > 0); } }); } }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_feature/full_duplex/websocket.phpt ================================================ --TEST-- swoole_feature/full_duplex: websocket --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm, $c) { $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $ret = $cli->upgrade('/'); Assert::true($ret); if ($ret) { $randoms = []; for ($n = MAX_REQUESTS; $n--;) { $randoms[] = get_safe_random(); } go(function () use ($cli, $randoms) { for ($n = MAX_REQUESTS; $n--;) { $ret = $cli->push(json_encode([$n, $randoms[$n]])); Assert::true($ret); } }); go(function () use ($cli, $randoms) { for ($n = MAX_REQUESTS; $n--;) { $frame = $cli->recv(); list($_n, $data) = json_decode($frame->data); Assert::same($randoms[$_n], $data); } }); } }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(['log_file' => '/dev/null']); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $server->push($frame->fd, $frame->data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_ftp/001.phpt ================================================ --TEST-- FTP login --FILE-- --EXPECT-- bool(true) array(4) { [0]=> string(55) "214-There is help available for the following commands:" [1]=> string(5) " USER" [2]=> string(5) " HELP" [3]=> string(15) "214 end of list" } array(1) { [0]=> string(39) "214 Syntax: HELP [ ] " } bool(true) ================================================ FILE: tests/swoole_ftp/002.phpt ================================================ --TEST-- FTP login (SSL) --SKIPIF-- --FILE-- --EXPECT-- bool(true) array(4) { [0]=> string(55) "214-There is help available for the following commands:" [1]=> string(5) " USER" [2]=> string(5) " HELP" [3]=> string(15) "214 end of list" } array(1) { [0]=> string(39) "214 Syntax: HELP [ ] " } bool(true) ================================================ FILE: tests/swoole_ftp/003.phpt ================================================ --TEST-- FTP cwd --FILE-- --EXPECT-- bool(true) string(1) "/" bool(true) string(6) "/mydir" bool(true) string(11) "/xpto/mydir" bool(true) string(5) "/xpto" bool(true) string(1) "/" bool(true) ================================================ FILE: tests/swoole_ftp/004.phpt ================================================ --TEST-- FTP with bogus parameters --FILE-- getMessage() . "\n"; } $ftp = ftp_connect('127.0.0.1', $fn()); if (!$ftp) die("Couldn't connect to the server"); var_dump(ftp_login($ftp, 'user', 'pass')); var_dump(ftp_login($ftp, 'user', 'bogus')); var_dump(ftp_quit($ftp)); }); ?> --EXPECTF-- ftp_connect(): Argument #3 ($timeout) must be greater than 0 bool(true) Warning: ftp_login(): Not logged in. in %s on line %d bool(false) bool(false) ================================================ FILE: tests/swoole_ftp/005.phpt ================================================ --TEST-- FTP with bogus server responses --FILE-- getMessage() . "\n"; } try { ftp_fput($ftp, 'x', fopen(__FILE__, 'r'), 0); } catch (ValueError $exception) { echo $exception->getMessage() . "\n"; } try { ftp_get($ftp, 'x', 'y', 0); } catch (ValueError $exception) { echo $exception->getMessage() . "\n"; } var_dump(ftp_mdtm($ftp, 'x')); var_dump(ftp_mkdir($ftp, 'x')); var_dump(ftp_nb_continue($ftp)); try { ftp_nb_fget($ftp, STDOUT, 'x', 0); } catch (ValueError $exception) { echo $exception->getMessage() . "\n"; } try { ftp_nb_fput($ftp, 'x', fopen(__FILE__, 'r'), 0); } catch (ValueError $exception) { echo $exception->getMessage() . "\n"; } var_dump(ftp_systype($ftp)); var_dump(ftp_pwd($ftp)); var_dump(ftp_size($ftp, '')); var_dump(ftp_rmdir($ftp, '')); }); ?> --EXPECTF-- bool(true) bool(false) Warning: ftp_cdup(): Command not implemented (1). in %s005.php on line %d bool(false) Warning: ftp_chdir(): Command not implemented (2). in %s005.php on line %d bool(false) Warning: ftp_chmod(): Command not implemented (3). in %s005.php on line %d bool(false) Warning: ftp_delete(): Command not implemented (4). in %s005.php on line %d bool(false) Warning: ftp_exec(): Command not implemented (5). in %s005.php on line %d bool(false) ftp_fget(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY ftp_fput(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY ftp_get(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY int(-1) Warning: ftp_mkdir(): Command not implemented (7). in %s005.php on line %d bool(false) Warning: ftp_nb_continue(): No nbronous transfer to continue in %s005.php on line %d int(0) ftp_nb_fget(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY ftp_nb_fput(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY Warning: ftp_systype(): Command not implemented (8). in %s005.php on line %d bool(false) Warning: ftp_pwd(): Command not implemented (9). in %s005.php on line %d bool(false) int(-1) Warning: ftp_rmdir(): Command not implemented (11). in %s005.php on line %d bool(false) ================================================ FILE: tests/swoole_ftp/007.phpt ================================================ --TEST-- FTP with bogus resource --CREDITS-- Michael Paul da Rosa PHP TestFest Dublin 2017 --FILE-- getMessage(), "\n"; } try { var_dump(ftp_pwd($ftp)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_cdup($ftp)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_chdir($ftp, '~')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_exec($ftp, 'x')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_raw($ftp, 'x')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_mkdir($ftp, '/')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_rmdir($ftp, '/')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_chmod($ftp, 7777, '/')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_alloc($ftp, 7777)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nlist($ftp, '/')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_rawlist($ftp, '~')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_mlsd($ftp, '~')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_systype($ftp)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_fget($ftp, $ftp, 'remote', 7777)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nb_fget($ftp, $ftp, 'remote', 7777)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_pasv($ftp, false)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_get($ftp, 'local', 'remote', 7777)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nb_get($ftp, 'local', 'remote', 7777)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nb_continue($ftp)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_fput($ftp, 'remote', $ftp, 9999)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nb_fput($ftp, 'remote', $ftp, 9999)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_put($ftp, 'remote', 'local', 9999)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_append($ftp, 'remote', 'local', 9999)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_nb_put($ftp, 'remote', 'local', 9999)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_size($ftp, '~')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_mdtm($ftp, '~')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_rename($ftp, 'old', 'new')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_delete($ftp, 'gone')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_site($ftp, 'localhost')); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_close($ftp)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_set_option($ftp, 1, 2)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } try { var_dump(ftp_get_option($ftp, 1)); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } fclose($ftp); }); ?> --EXPECT-- ftp_login(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_pwd(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_cdup(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_chdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_exec(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_raw(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_mkdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_rmdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_chmod(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_alloc(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nlist(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_rawlist(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_mlsd(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_systype(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_fget(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nb_fget(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_pasv(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_get(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nb_get(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nb_continue(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_fput(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nb_fput(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_put(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_append(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_nb_put(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_size(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_mdtm(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_rename(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_delete(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_site(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_close(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_set_option(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ftp_get_option(): Argument #1 ($ftp) must be of type FTP\Connection, resource given ================================================ FILE: tests/swoole_ftp/bug27809.phpt ================================================ --TEST-- Bug #27809 (ftp_systype returns null) --FILE-- --EXPECT-- bool(true) string(6) "OS/400" ================================================ FILE: tests/swoole_ftp/bug37799.phpt ================================================ --TEST-- Bug #37799 (ftp_ssl_connect() falls back to non-ssl connection) --SKIPIF-- --FILE-- --EXPECTF-- Warning: ftp_login(): %rdummy|bogus msg%r in %sbug37799.php on line %d bool(false) ================================================ FILE: tests/swoole_ftp/bug39458-2.phpt ================================================ --TEST-- Bug #39458 (ftp_nlist() returns false on empty directories (other server behaviour)) --FILE-- --EXPECT-- bool(true) array(3) { [0]=> string(5) "file1" [1]=> string(5) "file1" [2]=> string(9) "file b0rk" } array(0) { } bool(false) ================================================ FILE: tests/swoole_ftp/bug39458.phpt ================================================ --TEST-- Bug #39458 (ftp_nlist() returns false on empty directories) --FILE-- --EXPECT-- bool(true) array(3) { [0]=> string(5) "file1" [1]=> string(5) "file1" [2]=> string(9) "file b0rk" } array(0) { } bool(false) ================================================ FILE: tests/swoole_ftp/bug39583-2.phpt ================================================ --TEST-- Bug #39583 (FTP always transfers in binary mode) --FILE-- --EXPECTF-- bool(true) Uploaded %sbug39583-2.php as bug39583-2.php ================================================ FILE: tests/swoole_ftp/bug39583.phpt ================================================ --TEST-- Bug #39583 (FTP always transfers in binary mode) --FILE-- --EXPECTF-- bool(true) Uploaded %sbug39583.php as bug39583.php ================================================ FILE: tests/swoole_ftp/bug7216-2.phpt ================================================ --TEST-- Bug #7216 (ftp_mkdir returns nothing (2)) --FILE-- --EXPECT-- bool(true) string(3) "CVS" ================================================ FILE: tests/swoole_ftp/bug79100.phpt ================================================ --TEST-- Bug #79100 (Wrong FTP error messages) --SKIPIF-- --FILE-- --EXPECTF-- bool(true) bool(true) Warning: ftp_systype(): %rConnection|Operation%r timed out in %s on line %d ================================================ FILE: tests/swoole_ftp/bug80901.phpt ================================================ --TEST-- Bug #80901 (Info leak in ftp extension) --SKIPIF-- --FILE-- --EXPECTF-- bool(true) Warning: ftp_systype(): **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** in %s on line %d ================================================ FILE: tests/swoole_ftp/cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIDhTCCAm2gAwIBAgIJAN75FFz+owOAMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xNjA3MjUxMjM3 MTJaFw0yNjA3MjMxMjM3MTJaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALtG AIrNUDItISfpAqztL2TFEWEHLGTzCEh5Ag2sdMD7UYbqIPHLOE4EINv+dqEMM0Nz LYnw7ChtVegXT907xCaQcmeDFSdhqze4L8zawDfnn4syB8XAwGYJfpstYwe3nO6+ 0WvLSb1A5TYNeyoXjwlAUKElxkeWAo51uhR41GDhDQ9GgpqX1ccAhmSoUhgIRSzf 6f4KE3WTdzl1p12ZtkYHB8Jo2jB/JXnwGOz6isLnpRvkex4B7sUX+7u1MqK/e1X7 Hi1G/VkaAfC2SOfjTePtGBDBXrQ1arYXDPRA04sgFzSh55l7lC/4HasQ/jAb3h95 dcEIqyc69iioaN1c1NcCAwEAAaNQME4wHQYDVR0OBBYEFNv3kefb1H+6/6CpjiBi +I2s9E90MB8GA1UdIwQYMBaAFNv3kefb1H+6/6CpjiBi+I2s9E90MAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIzSEWpHSaBs7KduBRXX5+qFxBN6OCPl 7ID0rxAOYfw7ruzbmwgOpBgMIHGn+KqA6CmQI0jh9bZbv5TV2aFpFsUihugPc2lW 5EshCozxlEPmIJNsO8jDqPE4w3m4KiVTscRWjBa5cco+lwLDqboerm2l7vvrtr6B pgLaZct1c73MouvoJSCGK5EOGW7jsgaxjxJ3UZug+24Ko1wulO2cgBLhda9Ilrnx CIKI9h8Z2WVWuVQfyCyO1g7XkJgkBec77OhxD+m4onzPY6waqnnhmFOBcS+gKgBV jHeK9DCvZ9zet3EyEp6fyQOOtsC+gU0piYgfsQL7aCp5oLe+fjTiuUY= -----END CERTIFICATE----- -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7RgCKzVAyLSEn 6QKs7S9kxRFhByxk8whIeQINrHTA+1GG6iDxyzhOBCDb/nahDDNDcy2J8OwobVXo F0/dO8QmkHJngxUnYas3uC/M2sA355+LMgfFwMBmCX6bLWMHt5zuvtFry0m9QOU2 DXsqF48JQFChJcZHlgKOdboUeNRg4Q0PRoKal9XHAIZkqFIYCEUs3+n+ChN1k3c5 daddmbZGBwfCaNowfyV58Bjs+orC56Ub5HseAe7FF/u7tTKiv3tV+x4tRv1ZGgHw tkjn403j7RgQwV60NWq2Fwz0QNOLIBc0oeeZe5Qv+B2rEP4wG94feXXBCKsnOvYo qGjdXNTXAgMBAAECggEAD7yDISa9fWnjZlojGmrX16zjl/alWVo+sPBSJtn9+ZVk tWSJHihIc+3O4Q2R5FiFGj7cbcHr5j3BwT3sPRfflKoAowgVx/hiDc2RXrJnAouZ EXZDxu86e5iCpgF7V9OrATjRmjA74wZH/HHHjrLqFwnrfI8TCULmthfYag35Mqax qrIEzvSuYdaGxblNe+ZfnVEDW2F9DLBGcma0ffUlJp8AvV7bpo8Rj/JovPxit/VS UdwSRxwSAugctFpmcGlFkoQfxUx1WdEy8hjopLrayMjCCJvRUL4+C4zT9r9PBHOj fCSbJ+ajQIoRrgaL9bURk8BFMHY8+yMUsWEYVSmFAQKBgQDtOvPkhvZsNocUB5nJ ti3SXyDQ6OZQuKKHPSeDV/EvmZKeNlrQ1ZnwXLP3vkcedDOkt1nEVq/hUewpjt08 2MvmMwJBQEnmbTzMf43DtlXsStdP1lhYaFbU4iMM5zRfyBHDu1GPZEPXvKKpJUk0 M+jYIyTAP3mcZhqDKn0mPVP7VwKBgQDKFy9DtWFKxCvhFQvHx7YeZiVWJbIst/O4 ZyuPVAErni0hzSeCkmm7+F9hgEdPSLRcSaeTWP4L0u1cixECKboIhoNs38aft7o3 MdnI2RDSEKtKX2uVuhvpGDNuGpBAc8Qu8iCiv5INSC36ZhD1h++O/TiiUdgRJ3yX yeG7ej+CgQKBgCR5F95e0aw5hfMSaBaXJ9xcO9Niu2ZVvMdGI7kR4EcNOXmRqczJ ym0mE5VXb9/Cxd3hQq/pFAl0avbIvEMKoe62kPYvSC1hRiO6yLT6Z6N4rjncHqEZ CaCZVAI72dWQEQsi1ZtSMwwMOIYA8YxRHs98N75HBA+DszfPZIZoj2zpAoGBAJHp B3ElhmeLF/tdTLIj2bQ9H/wBH5H2Bvw/UU4c4vNxMzjSfRJjUAAtpgAptFLkNYTk kR9sA5DZ7BMDPXaIVg9Nv5peP3SWHNc5IPtI7kIdUu9R0cW7J+e2V3vJphlC/ITA wRuAoZ0BXmEKTHhae3aMEdXwrcZE8kpNsrO/4hcBAoGBAMISiPJPuxAX1UtqcxTa mDJfnQ2gxRu6AK9VmXqo0X4IBxDSnTjcL0huUlS849wgsE5oTXgdYb2hn+TXM5JJ NsEXLhV09X1mrk4M4LV1npd0mYxvFsO4+p+IX5YLiahInmQtq0gx3DWE8wouVFER 4yzfp27z8MZT8Qvr/ZI9lzWd -----END PRIVATE KEY----- ================================================ FILE: tests/swoole_ftp/dead-resource.phpt ================================================ --TEST-- Attempt to use a closed FTP\Connection --FILE-- getMessage(), "\n"; } }); ?> --EXPECT-- bool(true) bool(true) Exception: FTP\Connection is already closed ================================================ FILE: tests/swoole_ftp/filesize_large.phpt ================================================ --TEST-- Verify php can handle filesizes >32bit --SKIPIF-- --FILE-- --EXPECT-- int(5368709120) ================================================ FILE: tests/swoole_ftp/ftp_alloc_basic1.phpt ================================================ --TEST-- Testing ftp_alloc returns true --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ftp/ftp_alloc_basic2.phpt ================================================ --TEST-- Testing ftp_alloc returns true --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- bool(true) string(20) "1024 bytes allocated" ================================================ FILE: tests/swoole_ftp/ftp_append.phpt ================================================ --TEST-- ftp_append() create new file and append something --FILE-- --CLEAN-- --EXPECT-- bool(true) bool(true) bool(true) string(6) "foobar" ================================================ FILE: tests/swoole_ftp/ftp_chmod_basic.phpt ================================================ --TEST-- Testing ftp_chmod returns file mode --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- int(420) ================================================ FILE: tests/swoole_ftp/ftp_connect_001.phpt ================================================ --TEST-- ftp_connect - return FALSE if connection fails and Waning is generated --FILE-- --EXPECTF-- Warning: ftp_connect(): getaddrinfo for 'dummy-host-name' failed, error: %s in %s on line %d bool(false) ================================================ FILE: tests/swoole_ftp/ftp_constructor.phpt ================================================ --TEST-- Attempt to instantiate an FTP\Connection directly --FILE-- getMessage(), "\n"; } }); ?> --EXPECT-- Exception: Cannot directly construct FTP\Connection, use ftp_connect() or ftp_ssl_connect() instead ================================================ FILE: tests/swoole_ftp/ftp_delete.phpt ================================================ --TEST-- Testing ftp_delete basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) Contributed by Ward Cappelle User Group: PHP-WVL & PHPGent #PHPTestFest --FILE-- --EXPECTF-- Test case #1: removal of existing file from FTP, should return true: bool(true) Test case #2: removal of non-existent file from FTP, should return false: Warning: ftp_delete(): No such file or directory in %s on line %d bool(false) ================================================ FILE: tests/swoole_ftp/ftp_exec_basic.phpt ================================================ --TEST-- Testing ftp_exec returns true --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ftp/ftp_fget_basic.phpt ================================================ --TEST-- FTP ftp_fget file for both binary and ASCII transfer modes --CREDITS-- Nathaniel McHugh --FILE-- --EXPECTF-- bool(true) bool(true) For sale: baby shoes, never worn. bool(true) "BINARYFoo\u0000Bar\r\n" Warning: ftp_fget(): a warning: No such file or directory in %sftp_fget_basic.php on line %d ================================================ FILE: tests/swoole_ftp/ftp_fget_basic1.phpt ================================================ --TEST-- Testing ftp_fget ignore autoresume if autoseek is switched off --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- bool(true) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_fget_basic2.phpt ================================================ --TEST-- Testing ftp_fget autoresume --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- bool(true) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_fget_basic3.phpt ================================================ --TEST-- Testing ftp_fget resume parameter --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- bool(true) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_fput.phpt ================================================ --TEST-- Testing ftp_fput basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ftp/ftp_fput_ascii_over_4_kib.phpt ================================================ --TEST-- Testing ftp_fput basic functionality --FILE-- --CLEAN-- --EXPECT-- bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ftp/ftp_get_basic.phpt ================================================ --TEST-- FTP ftp_get file for both binary and ASCII transfer modes --CREDITS-- Nathaniel McHugh --FILE-- --EXPECTF-- bool(true) bool(true) For sale: baby shoes, never worn. bool(true) "BINARYFoo\u0000Bar\r\n" Warning: ftp_get(): a warning: No such file or directory in %sftp_get_basic.php on line %d ================================================ FILE: tests/swoole_ftp/ftp_get_option.phpt ================================================ --TEST-- Testing ftp_get_option basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- getMessage() . "\n"; } }); ?> --EXPECTF-- int(%d) bool(true) bool(true) ftp_get_option(): Argument #2 ($option) must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS ================================================ FILE: tests/swoole_ftp/ftp_mdtm_basic.phpt ================================================ --TEST-- Test the File Modification Time as described in http://tools.ietf.org/html/rfc3659#section-3.1 --CREDITS-- Nathaniel McHugh --FILE-- --EXPECT-- bool(true) June 15 1998 10:00:45 000000 June 15 1998 10:00:45 000000 July 05 1998 13:23:16 000000 int(-1) October 05 1999 21:31:02 000000 int(-1) ================================================ FILE: tests/swoole_ftp/ftp_mlsd.phpt ================================================ --TEST-- ftp_mlsd() return parsed lines --FILE-- --EXPECTF-- bool(true) Warning: ftp_mlsd(): Missing pathname in MLSD response in %s on line %d Warning: ftp_mlsd(): Malformed fact in MLSD response in %s on line %d Warning: ftp_mlsd(): Malformed fact in MLSD response in %s on line %d array(4) { [0]=> array(8) { ["name"]=> string(1) "." ["modify"]=> string(14) "20170127230002" ["perm"]=> string(7) "flcdmpe" ["type"]=> string(4) "cdir" ["unique"]=> string(11) "811U4340002" ["UNIX.group"]=> string(2) "33" ["UNIX.mode"]=> string(4) "0755" ["UNIX.owner"]=> string(2) "33" } [1]=> array(8) { ["name"]=> string(2) ".." ["modify"]=> string(14) "20170127230002" ["perm"]=> string(7) "flcdmpe" ["type"]=> string(4) "pdir" ["unique"]=> string(11) "811U4340002" ["UNIX.group"]=> string(2) "33" ["UNIX.mode"]=> string(4) "0755" ["UNIX.owner"]=> string(2) "33" } [2]=> array(9) { ["name"]=> string(6) "foobar" ["modify"]=> string(14) "20170126121225" ["perm"]=> string(5) "adfrw" ["size"]=> string(4) "4729" ["type"]=> string(4) "file" ["unique"]=> string(11) "811U4340CB9" ["UNIX.group"]=> string(2) "33" ["UNIX.mode"]=> string(4) "0644" ["UNIX.owner"]=> string(2) "33" } [3]=> array(3) { ["name"]=> string(9) "path;name" ["fact"]=> string(6) "val=ue" ["empty"]=> string(0) "" } } ================================================ FILE: tests/swoole_ftp/ftp_mlsd_empty_directory.phpt ================================================ --TEST-- ftp_mlsd() must not return false on empty directories --FILE-- --EXPECT-- bool(true) array(0) { } bool(false) ================================================ FILE: tests/swoole_ftp/ftp_mlsd_missing_directory.phpt ================================================ --TEST-- Testing ftp_mlsd returns false on server error --CREDITS-- Andreas Treichel --FILE-- --EXPECT-- bool(false) ================================================ FILE: tests/swoole_ftp/ftp_nb_continue.phpt ================================================ --TEST-- Testing whether ftp_nb_continue() fetches more data --FILE-- --CLEAN-- --EXPECT-- This is line 0 of the test data. This is line 1 of the test data. This is line 2 of the test data. This is line 3 of the test data. This is line 4 of the test data. This is line 5 of the test data. This is line 6 of the test data. This is line 7 of the test data. This is line 8 of the test data. This is line 9 of the test data. This is line 10 of the test data. This is line 11 of the test data. This is line 12 of the test data. This is line 13 of the test data. This is line 14 of the test data. This is line 15 of the test data. This is line 16 of the test data. This is line 17 of the test data. This is line 18 of the test data. This is line 19 of the test data. This is line 20 of the test data. This is line 21 of the test data. This is line 22 of the test data. This is line 23 of the test data. This is line 24 of the test data. This is line 25 of the test data. This is line 26 of the test data. This is line 27 of the test data. This is line 28 of the test data. This is line 29 of the test data. This is line 30 of the test data. This is line 31 of the test data. This is line 32 of the test data. This is line 33 of the test data. This is line 34 of the test data. This is line 35 of the test data. This is line 36 of the test data. This is line 37 of the test data. This is line 38 of the test data. This is line 39 of the test data. This is line 40 of the test data. This is line 41 of the test data. This is line 42 of the test data. This is line 43 of the test data. This is line 44 of the test data. This is line 45 of the test data. This is line 46 of the test data. This is line 47 of the test data. This is line 48 of the test data. This is line 49 of the test data. This is line 50 of the test data. This is line 51 of the test data. This is line 52 of the test data. This is line 53 of the test data. This is line 54 of the test data. This is line 55 of the test data. This is line 56 of the test data. This is line 57 of the test data. This is line 58 of the test data. This is line 59 of the test data. This is line 60 of the test data. This is line 61 of the test data. This is line 62 of the test data. This is line 63 of the test data. This is line 64 of the test data. This is line 65 of the test data. This is line 66 of the test data. This is line 67 of the test data. This is line 68 of the test data. This is line 69 of the test data. This is line 70 of the test data. This is line 71 of the test data. This is line 72 of the test data. This is line 73 of the test data. This is line 74 of the test data. This is line 75 of the test data. This is line 76 of the test data. This is line 77 of the test data. This is line 78 of the test data. This is line 79 of the test data. This is line 80 of the test data. This is line 81 of the test data. This is line 82 of the test data. This is line 83 of the test data. This is line 84 of the test data. This is line 85 of the test data. This is line 86 of the test data. This is line 87 of the test data. This is line 88 of the test data. This is line 89 of the test data. This is line 90 of the test data. This is line 91 of the test data. This is line 92 of the test data. This is line 93 of the test data. This is line 94 of the test data. This is line 95 of the test data. This is line 96 of the test data. This is line 97 of the test data. This is line 98 of the test data. This is line 99 of the test data. This is line 100 of the test data. This is line 101 of the test data. This is line 102 of the test data. This is line 103 of the test data. This is line 104 of the test data. This is line 105 of the test data. This is line 106 of the test data. This is line 107 of the test data. This is line 108 of the test data. This is line 109 of the test data. This is line 110 of the test data. This is line 111 of the test data. This is line 112 of the test data. This is line 113 of the test data. This is line 114 of the test data. This is line 115 of the test data. This is line 116 of the test data. This is line 117 of the test data. This is line 118 of the test data. This is line 119 of the test data. This is line 120 of the test data. This is line 121 of the test data. This is line 122 of the test data. This is line 123 of the test data. This is line 124 of the test data. This is line 125 of the test data. This is line 126 of the test data. This is line 127 of the test data. This is line 128 of the test data. This is line 129 of the test data. This is line 130 of the test data. This is line 131 of the test data. This is line 132 of the test data. This is line 133 of the test data. This is line 134 of the test data. This is line 135 of the test data. This is line 136 of the test data. This is line 137 of the test data. This is line 138 of the test data. This is line 139 of the test data. This is line 140 of the test data. This is line 141 of the test data. This is line 142 of the test data. This is line 143 of the test data. This is line 144 of the test data. This is line 145 of the test data. This is line 146 of the test data. This is line 147 of the test data. This is line 148 of the test data. This is line 149 of the test data. ================================================ FILE: tests/swoole_ftp/ftp_nb_fget_basic1.phpt ================================================ --TEST-- Testing ftp_nb_fget ignore autoresume if autoseek is switched off --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- int(2) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_nb_fget_basic2.phpt ================================================ --TEST-- Testing ftp_nb_fget autoresume --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- int(2) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_nb_fget_basic3.phpt ================================================ --TEST-- Testing ftp_nb_fget resume parameter --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --CLEAN-- --EXPECT-- int(2) string(12) "ASCIIFooBar " ================================================ FILE: tests/swoole_ftp/ftp_nb_fput.phpt ================================================ --TEST-- Testing ftp_nb_fput basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- int(1) ================================================ FILE: tests/swoole_ftp/ftp_nb_get_large.phpt ================================================ --TEST-- Testing ftp_nb_fget can handle large files incl. resume --SKIPIF-- --FILE-- --CLEAN-- --EXPECT-- string(1) "X" int(5368709120) ================================================ FILE: tests/swoole_ftp/ftp_nb_put.phpt ================================================ --TEST-- Testing ftp_nb_put basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- int(1) ================================================ FILE: tests/swoole_ftp/ftp_pasv.phpt ================================================ --TEST-- Testing ftp_pasv basic funcionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ftp/ftp_rawlist_basic1.phpt ================================================ --TEST-- Testing ftp_rawlist basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- bool(true) array(3) { [0]=> string(5) "file1" [1]=> string(5) "file1" [2]=> string(9) "file b0rk" } ================================================ FILE: tests/swoole_ftp/ftp_rawlist_basic2.phpt ================================================ --TEST-- Testing ftp_rawlist returns false on server error --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- bool(false) ================================================ FILE: tests/swoole_ftp/ftp_rename_basic1.phpt ================================================ --TEST-- FTP basic ftp_rename calls --FILE-- --EXPECTF-- bool(true) Warning: ftp_rename(): No such file or directory in %sftp_rename_basic1.php on line %d bool(false) ================================================ FILE: tests/swoole_ftp/ftp_rmdir_basic.phpt ================================================ --TEST-- Testing ftp_rmdir returns true --CREDITS-- Rodrigo Moyle #testfest PHPSP on 2009-06-20 --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ftp/ftp_set_option.phpt ================================================ --TEST-- Testing ftp_set_option basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECT-- bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ftp/ftp_set_option_errors.phpt ================================================ --TEST-- Testing ftp_set_option errors while setting up --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- FTP_TIMEOUT_SEC, 'value' => 0 ], [ 'option' => FTP_TIMEOUT_SEC, 'value' => '0' ], [ 'option' => FTP_USEPASVADDRESS, 'value' => ['1'] ], [ 'option' => FTP_AUTOSEEK, 'value' => 'true' ], [ 'option' => FOO_BAR, 'value' => 1 ], ]; foreach ($options as $option) try { var_dump(ftp_set_option($ftp, $option['option'], $option['value'])); } catch (\Throwable $ex) { echo "Exception: ", $ex->getMessage(), "\n"; } }); ?> --EXPECT-- Exception: ftp_set_option(): Argument #3 ($value) must be greater than 0 for the FTP_TIMEOUT_SEC option Exception: ftp_set_option(): Argument #3 ($value) must be of type int for the FTP_TIMEOUT_SEC option, string given Exception: ftp_set_option(): Argument #3 ($value) must be of type bool for the FTP_USEPASVADDRESS option, array given Exception: ftp_set_option(): Argument #3 ($value) must be of type bool for the FTP_AUTOSEEK option, string given Exception: ftp_set_option(): Argument #2 ($option) must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS ================================================ FILE: tests/swoole_ftp/ftp_site_basic.phpt ================================================ --TEST-- ftp_site function basic functionality --CREDITS-- Gabriel Caruso (carusogabriel34@gmail.com) --FILE-- --EXPECTF-- bool(true) Warning: ftp_site(): Syntax error, command unrecognized. in %s on line %d bool(false) ================================================ FILE: tests/swoole_ftp/ftp_ssl_connect_error.phpt ================================================ --TEST-- Test ftp_ssl_connect() function : error conditions --SKIPIF-- --FILE-- getMessage() . "\n"; } echo "===DONE===\n"; }); ?> --EXPECTF-- *** Testing ftp_ssl_connect() function : error conditions *** -- Testing ftp_ssl_connect() function on failure -- Warning: ftp_ssl_connect(): getaddrinfo for '%s' failed, error: %s in %s on line %d bool(false) -- Testing ftp_ssl_connect() function timeout exception for value 0 -- ftp_ssl_connect(): Argument #3 ($timeout) must be greater than 0 ===DONE=== ================================================ FILE: tests/swoole_ftp/gh10521.phpt ================================================ --TEST-- GH-10521 (ftp_get/ftp_nb_get resumepos offset is maximum 10GB) --SKIPIF-- --FILE-- --CLEAN-- --EXPECTF-- bool(true) %s: ftp_fget(): Can't open data connection (12345678910). in %s on line %d %s: ftp_fget(): Can't open data connection (9223372036854775807). in %s on line %d ================================================ FILE: tests/swoole_ftp/gh10562.phpt ================================================ --TEST-- GH-10562 (Memory leak with consecutive ftp_nb_fget) --FILE-- --CLEAN-- --EXPECT-- bool(true) bool(true) BINARYFooBar For sale: baby shoes, never worn. ================================================ FILE: tests/swoole_ftp/server.inc ================================================ ['local_cert' => dirname(__FILE__) . '/cert.pem']]); $socket = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context); if (!$socket) { echo "{$errstr} ({$errno})\n"; exit("could not start/bind the ftp server\n"); } $socket_name = stream_socket_get_name($socket, false); $port = (int) substr($socket_name, strrpos($socket_name, ':') + 1); $pid = pcntl_fork(); // server if ($pid == 0) { function dump_and_exit($buf) { var_dump($buf); exit; } function anonymous() { return $GLOBALS['user'] === 'anonymous'; } /* quick&dirty realpath() like function */ function change_dir($dir) { global $cwd; if ($dir[0] == '/') { $cwd = $dir; return; } $cwd = "{$cwd}/{$dir}"; do { $old = $cwd; $cwd = preg_replace('@/?[^/]+/\.\.@', '', $cwd); } while ($old != $cwd); $cwd = strtr($cwd, ['//' => '/']); if (!$cwd) { $cwd = '/'; } } $s = stream_socket_accept($socket); if (!$s) { exit("Error accepting a new connection\n"); } fputs($s, "220----- PHP FTP server 0.3 -----\r\n220 Service ready\r\n"); $buf = fread($s, 2048); function user_auth($buf) { global $user, $s, $ssl, $bug37799; if (!empty($ssl)) { if ($buf !== "AUTH TLS\r\n") { fputs($s, "500 Syntax error, command unrecognized.\r\n"); dump_and_exit($buf); } if (empty($bug37799)) { fputs($s, "234 auth type accepted\r\n"); } else { fputs($s, "666 dummy\r\n"); sleep(1); fputs($s, "666 bogus msg\r\n"); exit; } if (!stream_socket_enable_crypto($s, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER)) { exit("SSLv23 handshake failed.\n"); } if (!preg_match('/^PBSZ \d+\r\n$/', $buf = fread($s, 2048))) { fputs($s, "501 bogus data\r\n"); dump_and_exit($buf); } fputs($s, "200 OK\r\n"); $buf = fread($s, 2048); if ($buf !== "PROT P\r\n") { fputs($s, "504 Wrong protection.\r\n"); dump_and_exit($buf); } fputs($s, "200 OK\r\n"); $buf = fread($s, 2048); } if ($buf == "AUTH TLS\r\n") { fputs($s, "500 not supported.\r\n"); return; } if (!preg_match('/^USER (\w+)\r\n$/', $buf, $m)) { fputs($s, "500 Syntax error, command unrecognized.\r\n"); dump_and_exit($buf); } $user = $m[1]; if ($user !== 'user' && $user !== 'anonymous') { fputs($s, "530 Not logged in.\r\n"); exit; } if (anonymous()) { fputs($s, "230 Anonymous user logged in\r\n"); } else { fputs($s, "331 User name ok, need password\r\n"); if (!preg_match('/^PASS (\w+)\r\n$/', $buf = fread($s, 100), $m)) { fputs($s, "500 Syntax error, command unrecognized.\r\n"); dump_and_exit($buf); } $pass = $m[1]; if ($pass === 'pass') { fputs($s, "230 User logged in\r\n"); } else { fputs($s, "530 Not logged in.\r\n"); exit; } } } user_auth($buf); $cwd = '/'; $num_bogus_cmds = 0; while ($buf = fread($s, 4098)) { if (!empty($bogus)) { fputs($s, '502 Command not implemented (' . $num_bogus_cmds++ . ").\r\n"); } elseif ($buf === "HELP\r\n") { fputs($s, "214-There is help available for the following commands:\r\n"); fputs($s, " USER\r\n"); fputs($s, " HELP\r\n"); fputs($s, "214 end of list\r\n"); } elseif ($buf === "HELP HELP\r\n") { fputs($s, "214 Syntax: HELP [ ] \r\n"); } elseif ($buf === "PWD\r\n") { fputs($s, "257 \"{$cwd}\" is current directory.\r\n"); } elseif ($buf === "CDUP\r\n") { change_dir('..'); fputs($s, "250 CDUP command successful.\r\n"); } elseif ($buf === "SYST\r\n") { if (isset($bug27809)) { fputs($s, "215 OS/400 is the remote operating system. The TCP/IP version is \"V5R2M0\"\r\n"); } elseif (isset($bug79100)) { // do nothing so test hits timeout } elseif (isset($bug80901)) { fputs($s, "\r\n" . str_repeat('*', 4096) . "\r\n"); } else { fputs($s, "215 UNIX Type: L8.\r\n"); } } elseif ($buf === "TYPE A\r\n") { $ascii = true; fputs($s, "200 OK\r\n"); } elseif ($buf === "AUTH SSL\r\n") { $ascii = true; fputs($s, "500 not supported\r\n"); } elseif ($buf === "TYPE I\r\n") { $ascii = false; fputs($s, "200 OK\r\n"); } elseif ($buf === "QUIT\r\n") { fputs($s, "221 Bye\r\n"); break; } elseif (preg_match("~^PORT (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)\r\n$~", $buf, $m)) { $host = "{$m[1]}.{$m[2]}.{$m[3]}.{$m[4]}"; $port = ((int) $m[5] << 8) + (int) $m[6]; fputs($s, "200 OK.\r\n"); } elseif (preg_match("~^STOR ([\\w/.-]+)\r\n$~", $buf, $m)) { fputs($s, "150 File status okay; about to open data connection\r\n"); if (empty($pasv)) { if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } $data = stream_get_contents($fs); $orig = file_get_contents(dirname(__FILE__) . '/' . $m[1]); if (isset($ascii) && !$ascii && $orig === $data) { fputs($s, "226 Closing data Connection.\r\n"); } elseif ((!empty($ascii) || isset($bug39583)) && $data === strtr($orig, ["\r\n" => "\n", "\r" => "\n", "\n" => "\r\n"])) { fputs($s, "226 Closing data Connection.\r\n"); } else { var_dump($data); var_dump($orig); fputs($s, "552 Requested file action aborted.\r\n"); } fclose($fs); } else { $data = file_get_contents('nm2.php'); $orig = file_get_contents(dirname(__FILE__) . '/' . $m[1]); if ($orig === $data) { fputs($s, "226 Closing data Connection.\r\n"); } else { var_dump($data); var_dump($orig); fputs($s, "552 Requested file action aborted.\r\n"); } } } elseif (preg_match("~^APPE ([\\w/.-]+)\r\n$~", $buf, $m)) { fputs($s, "150 File status okay; about to open data connection\r\n"); if (empty($pasv)) { if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } $data = stream_get_contents($fs); file_put_contents(__DIR__ . '/' . $m[1], $data, FILE_APPEND); fputs($s, "226 Closing data Connection.\r\n"); fclose($fs); } else { $data = stream_get_contents($fs); file_put_contents(__DIR__ . '/' . $m[1], $data, FILE_APPEND); fputs($s, "226 Closing data Connection.\r\n"); fclose($fs); } } elseif (preg_match("~^CWD ([A-Za-z./]+)\r\n$~", $buf, $m)) { if (isset($bug77680)) { fputs($s, "550 Directory change to {$m[1]} failed: file does not exist\r\n"); var_dump($buf); } else { change_dir($m[1]); fputs($s, "250 CWD command successful.\r\n"); } } elseif (preg_match("~^NLST(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) { if (isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) { fputs($s, "250 {$m[1]}: No such file or directory\r\n"); continue; } // there are some servers that don't open the ftp-data socket if there's nothing to send if (isset($bug39458, $m[1]) && $m[1] === 'emptydir') { fputs($s, "226 Transfer complete.\r\n"); continue; } if (empty($pasv)) { fputs($s, "150 File status okay; about to open data connection\r\n"); if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } } else { fputs($s, "125 Data connection already open; transfer starting.\r\n"); $fs = $pasvs; } if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { exit("SSLv23 handshake failed.\n"); } if (empty($m[1]) || $m[1] !== 'emptydir') { fputs($fs, "file1\r\nfile1\r\nfile\nb0rk\r\n"); } fputs($s, "226 Closing data Connection.\r\n"); fclose($fs); } elseif (preg_match("~^MKD ([A-Za-z./]+)\r\n$~", $buf, $m)) { if (isset($bug7216)) { fputs($s, "257 OK.\r\n"); } else { if (isset($bug77680)) { var_dump($buf); } fputs($s, "257 \"/path/to/ftproot{$cwd}{$m[1]}\" created.\r\n"); } } elseif (preg_match('/^USER /', $buf)) { user_auth($buf); } elseif (preg_match('/^MDTM ([\w\h]+)/', $buf, $matches)) { switch ($matches[1]) { case 'A': fputs($s, "213 19980615100045.014\r\n"); break; case 'B': fputs($s, "213 19980615100045.014\r\n"); break; case 'C': fputs($s, "213 19980705132316\r\n"); break; case '19990929043300 File6': fputs($s, "213 19991005213102\r\n"); break; default: fputs($s, "550 No file named \"{$matches[1]}\"\r\n"); break; } } elseif (preg_match('/^RETR ([\/]*[\w\h]+)/', $buf, $matches)) { if (!empty($pasv)) { } elseif (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } switch ($matches[1]) { case 'pasv': fputs($s, "150 File status okay; about to open data connection.\r\n"); // the data connection is handled in another forked process // called from outside this while loop fputs($s, "226 Closing data Connection.\r\n"); break; case 'a story': fputs($s, "150 File status okay; about to open data connection.\r\n"); fputs($fs, "For sale: baby shoes, never worn.\r\n"); fputs($s, "226 Closing data Connection.\r\n"); break; case 'binary data': fputs($s, "150 File status okay; about to open data connection.\r\n"); $transfer_type = $ascii ? 'ASCII' : 'BINARY'; fputs($fs, $transfer_type . "Foo\0Bar\r\n"); fputs($s, "226 Closing data Connection.\r\n"); break; case 'fget': fputs($s, "150 File status okay; about to open data connection.\r\n"); $transfer_type = $ascii ? 'ASCII' : 'BINARY'; fputs($fs, $transfer_type . "FooBar\r\n"); fputs($s, "226 Closing data Connection.\r\n"); break; case 'fgetresume': fputs($s, "150 File status okay; about to open data connection.\r\n"); $transfer_type = $ascii ? 'ASCII' : 'BINARY'; fputs($fs, "Bar\r\n"); fputs($s, "226 Closing data Connection.\r\n"); break; case 'fget_large': fputs($s, "150 File status okay; about to open data connection.\r\n"); $transfer_type = $ascii ? 'ASCII' : 'BINARY'; if ($GLOBALS['rest_pos'] == '5368709119') { fputs($fs, 'X'); } else { fputs($fs, 'Y'); } fputs($s, "226 Closing data Connection.\r\n"); break; case 'mediumfile': fputs($s, "150 File status okay; about to open data connection.\r\n"); for ($i = 0; $i < 150; $i++) { fputs($fs, "This is line {$i} of the test data.\n"); } fputs($s, "226 Closing data Connection.\r\n"); break; case '/bug73457': fputs($s, "150 File status okay; about to open data connection.\r\n"); break; case 'gh10521': // Just a side channel for getting the received file size. fputs($s, "425 Can't open data connection (" . $GLOBALS['rest_pos'] . ").\r\n"); break; default: fputs($s, "550 {$matches[1]}: No such file or directory \r\n"); break; } if (isset($fs)) { fclose($fs); } } elseif (preg_match('/^PASV/', $buf, $matches)) { $pasv = true; $host = '127.0.0.1'; $i = 0; if (empty($bug73457)) { if (!empty($ssl)) { $soc = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context); } else { $soc = stream_socket_server('tcp://127.0.0.1:0'); } if (!$soc) { echo "{$errstr} ({$errno})\n"; exit("could not bind passive port\n"); } $soc_name = stream_socket_get_name($soc, false); $pasv_port = (int) substr($soc_name, strrpos($soc_name, ':') + 1); } else { $pasv_port = 1234; } $p2 = $pasv_port % ((int) 1 << 8); $p1 = ($pasv_port - $p2) / ((int) 1 << 8); fputs($s, "227 Entering Passive Mode. (127,0,0,1,{$p1},{$p2})\r\n"); if (empty($bug73457)) { $pasvs = stream_socket_accept($soc, 10); } } elseif (preg_match('/^EPSV/', $buf, $matches)) { fputs($s, "550 Extended passsive mode not supported.\r\n"); } elseif (preg_match('/^SITE EXEC/', $buf, $matches)) { fputs($s, "200 OK\r\n"); } elseif (preg_match('/^RMD/', $buf, $matches)) { fputs($s, "250 OK\r\n"); } elseif (preg_match('/^SITE CHMOD/', $buf, $matches)) { fputs($s, "200 OK\r\n"); } elseif (preg_match('/^DELE ([\w\h]+)/', $buf, $matches)) { if (isset($matches[1]) && in_array($matches[1], ['file1', "file\nb0rk"])) { fputs($s, "250 Delete successful\r\n"); } else { fputs($s, "550 No such file or directory\r\n"); } } elseif (preg_match('/^ALLO (\d+)/', $buf, $matches)) { fputs($s, '200 ' . $matches[1] . " bytes allocated\r\n"); } elseif (preg_match('/^LIST www\//', $buf, $matches)) { if (empty($pasv)) { fputs($s, "150 File status okay; about to open data connection\r\n"); if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } } else { fputs($s, "125 Data connection already open; transfer starting.\r\n"); $fs = $pasvs; } if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { exit("SSLv23 handshake failed.\n"); } fputs($fs, "file1\r\nfile1\r\nfile\nb0rk\r\n"); fputs($s, "226 Closing data Connection.\r\n"); fclose($fs); fputs($s, "226 Transfer complete\r\n"); } elseif (preg_match('/^LIST no_exists\//', $buf, $matches)) { fputs($s, "425 Error establishing connection\r\n"); } elseif (preg_match('/^REST (\d+)/', $buf, $matches)) { $GLOBALS['rest_pos'] = $matches[1]; fputs($s, "350 OK\r\n"); } elseif (preg_match('/^SIZE largefile/', $buf)) { fputs($s, "213 5368709120\r\n"); } elseif (preg_match('/^RNFR existing_file/', $buf, $matches)) { fputs($s, "350 File or directory exists, ready for destination name\r\n"); } elseif (preg_match('/^RNFR nonexisting_file/', $buf, $matches)) { fputs($s, "550 No such file or directory\r\n"); } elseif (preg_match('/^RNTO nonexisting_file/', $buf, $matches)) { fputs($s, "250 Rename successful\r\n"); } elseif (preg_match('/^MLSD no_exists\//', $buf, $matches)) { fputs($s, "425 Error establishing connection\r\n"); } elseif (preg_match("~^MLSD(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) { if (isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) { fputs($s, "250 {$m[1]}: No such file or directory\r\n"); continue; } // there are some servers that don't open the ftp-data socket if there's nothing to send if (isset($bug39458, $m[1]) && $m[1] === 'emptydir') { fputs($s, "226 Transfer complete.\r\n"); continue; } if (empty($pasv)) { fputs($s, "150 File status okay; about to open data connection\r\n"); if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { fputs($s, "425 Can't open data connection\r\n"); continue; } } else { fputs($s, "125 Data connection already open; transfer starting.\r\n"); $fs = $pasvs; } if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { exit("SSLv23 handshake failed.\n"); } if (empty($m[1]) || $m[1] !== 'emptydir') { fputs($fs, "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .\r\n"); fputs($fs, "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ..\r\n"); fputs($fs, "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar\r\n"); fputs($fs, "fact=val=ue;empty=; path;name\r\n"); fputs($fs, "no_space\r\n"); fputs($fs, "no_semi pathname\r\n"); fputs($fs, "no_eq; pathname\r\n"); } fputs($s, "226 Closing data Connection.\r\n"); fclose($fs); } elseif (preg_match('/^SIZE \/bug73457/', $buf)) { fputs($s, "213 10\r\n"); } elseif (preg_match('/^SITE/', $buf)) { fputs($s, "500 Syntax error, command unrecognized.\r\n"); } else { dump_and_exit($buf); } } exit; } fclose($socket); return function () use ($pid, $port) { Co\defer(function () use ($pid, $port) { $out = Co\System::waitpid($pid); assert($out['code'] == 0); }); return $port; }; ================================================ FILE: tests/swoole_function/substr_json_decode.phpt ================================================ --TEST-- swoole_function: substr_json_decode --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_function/substr_unserialize.phpt ================================================ --TEST-- swoole_function: ext_unserialize --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_function/swoole_clear_dns_cache.phpt ================================================ --TEST-- swoole_function: test swoole_clear_dns_cache --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_function/swoole_cpu_num.phpt ================================================ --TEST-- swoole_function: swoole_cpu_num --SKIPIF-- --FILE-- --EXPECTF-- cpu_num: %d ================================================ FILE: tests/swoole_function/swoole_error_log.phpt ================================================ --TEST-- swoole_function: swoole_error_log --SKIPIF-- --FILE-- LOG_FILE]); swoole_error_log(SWOOLE_LOG_NOTICE, "hello 1"); swoole_error_log_ex(SWOOLE_LOG_NOTICE, ERROR_1, "hello 2"); swoole_ignore_error(ERROR_2); swoole_error_log_ex(SWOOLE_LOG_NOTICE, ERROR_2, "hello 3"); $content = file_get_contents(LOG_FILE); Assert::contains($content, 'hello 1'); Assert::contains($content, 'hello 2'); Assert::contains($content, '(ERRNO ' . ERROR_1 . ')'); Assert::notContains($content, 'hello 3'); unlink(LOG_FILE); ?> --EXPECT-- ================================================ FILE: tests/swoole_function/swoole_get_local_ip.phpt ================================================ --TEST-- swoole_function: get local ip --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_function/swoole_get_local_mac.phpt ================================================ --TEST-- swoole_function: get mac address --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_function/swoole_set_process_name.phpt ================================================ --TEST-- swoole_function: set process name --SKIPIF-- --FILE-- --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_function/swoole_strerror.phpt ================================================ --TEST-- swoole_function: swoole_strerror --SKIPIF-- --FILE-- --EXPECT-- Connection reset by peer File not exist ================================================ FILE: tests/swoole_function/swoole_version.phpt ================================================ --TEST-- swoole_function: swoole_version --SKIPIF-- --FILE-- --EXPECTF-- swoole_version: %s ================================================ FILE: tests/swoole_global/channel_construct_check.phpt ================================================ --TEST-- swoole_global: socket construct check --SKIPIF-- --FILE-- push('123'); }); }); Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> --EXPECT-- ================================================ FILE: tests/swoole_global/closed_stdout.phpt ================================================ --TEST-- swoole_global: handle closed STDOUT/STDERR without exception --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_global/create_deny.phpt ================================================ --TEST-- swoole_global: deny create object --SKIPIF-- --FILE-- getMessage() . PHP_EOL; } try { new Swoole\Event; } catch (Error $e) { echo $e->getMessage() . PHP_EOL; } try { new Swoole\Runtime; } catch (Error $e) { echo $e->getMessage() . PHP_EOL; } try { new Swoole\Timer; } catch (Error $e) { echo $e->getMessage() . PHP_EOL; } }); ?> --EXPECT-- The object of Swoole\Coroutine can not be created for security reasons The object of Swoole\Event can not be created for security reasons The object of Swoole\Runtime can not be created for security reasons The object of Swoole\Timer can not be created for security reasons ================================================ FILE: tests/swoole_global/function_alias.phpt ================================================ --TEST-- swoole_global: function alias --SKIPIF-- --FILE-- --EXPECTF-- bool(true) ================================================ FILE: tests/swoole_global/serialize_deny.phpt ================================================ --TEST-- swoole_global: deny serialize and unserialize --SKIPIF-- --FILE-- getMessage(), 'Serialization'), 0); } try { $hcc = new \Swoole\Client(SWOOLE_TCP); serialize($hcc); Assert::true(false, 'never here'); } catch (\Exception $exception) { Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); } try { $hcc = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); serialize($hcc); Assert::true(false, 'never here'); } catch (\Exception $exception) { Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); } try { $hcc = new \Swoole\Coroutine\Http\Client('127.0.0.1'); serialize($hcc); Assert::true(false, 'never here'); } catch (\Exception $exception) { Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); } try { $hcc = new \Swoole\Table(1); serialize($hcc); Assert::true(false, 'never here'); } catch (\Exception $exception) { Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_global/socket_construct_check.phpt ================================================ --TEST-- swoole_global: socket construct check --SKIPIF-- --FILE-- connect('127.0.0.1', 12345); }); }); Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> --EXPECTF-- ================================================ FILE: tests/swoole_global/too_many_objects.phpt ================================================ --TEST-- swoole_global: too many objects --SKIPIF-- --FILE-- listen('127.0.0.1', get_one_free_port(), SWOOLE_TCP); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_global/unset_deny.phpt ================================================ --TEST-- swoole_global: deny unset properties and clone --SKIPIF-- --FILE-- test = 1; Assert::same($chan->test, 1); unset($chan->test); Assert::true(!isset($chan->test)); // clone error try { $chan = clone $chan; } catch (Error $e) { echo "{$e->getMessage()}\n"; } // unset error try { unset($chan->errCode); } catch (Error $e) { echo "{$e->getMessage()}\n"; Assert::true(isset($chan->errCode)); } ?> --EXPECT-- Trying to clone an uncloneable object of class Swoole\Coroutine\Channel Property errCode of class Swoole\Coroutine\Channel cannot be unset ================================================ FILE: tests/swoole_global/unset_property_01.phpt ================================================ --TEST-- swoole_global: unset internal class property --SKIPIF-- --FILE-- capacity); ?> --EXPECTF-- Fatal error: Uncaught Error: Property capacity of class Swoole\Coroutine\Channel cannot be unset in %s/tests/%s/unset%s.php:%d Stack trace: #0 {main} thrown in %s ================================================ FILE: tests/swoole_global/unset_property_02.phpt ================================================ --TEST-- swoole_global: unset user class's parent internal property --SKIPIF-- --FILE-- capacity); ?> --EXPECTF-- Fatal error: Uncaught Error: Property capacity of class %s cannot be unset in %s:%d Stack trace: #0 {main} thrown in %s ================================================ FILE: tests/swoole_global/unset_property_03.phpt ================================================ --TEST-- swoole_global: unset user class's own property --SKIPIF-- --FILE-- t); ?> --EXPECTF-- ================================================ FILE: tests/swoole_http2_client_coro/bug_5127.phpt ================================================ --TEST-- swoole_http2_client_coro: Github #5127 When use swoole in php 8.2,Swoole\Http2\Request may throw ErrorException:Creation of dynamic property $usePipelineRead --SKIPIF-- --FILE-- usePipelineRead = true; echo 'DONE'; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/connect_twice.phpt ================================================ --TEST-- swoole_http2_client_coro: connect twice --SKIPIF-- --FILE-- connect(); $req = new Request(); $uuid = uniqid(); $req->method = 'GET'; $req->path = '/base64/' . base64_encode($uuid); $client->send($req); $chan->push(true); $resp = $client->recv(); Assert::notNull($resp); Assert::eq($resp->statusCode, 200); Assert::eq($resp->data, $uuid); $chan->pop(); }); go(function () use ($client, $chan) { Assert::eq($client->connect(), false); $uuid = uniqid(); $req = new Request(); $req->method = 'GET'; $req->path = '/base64/' . base64_encode($uuid); $client->send($req); $chan->push(true); Assert::eq($client->recv(), false); $chan->pop(); }); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/cookies.phpt ================================================ --TEST-- swoole_http2_client_coro: cookies --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->connect(); $request = new Swoole\Http2\Request; for ($n = MAX_REQUESTS; $n--;) { $request->cookies = []; for ($k = 32; $k--;) { $request->cookies[get_safe_random()] = get_safe_random(); } Assert::assert($cli->send($request)); $response = $cli->recv(1); Assert::same('OK', $response->data); Assert::same($request->cookies, $response->cookies); } echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { foreach ($request->cookie as $name => $value) { $response->cookie($name, $value); } $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/error.phpt ================================================ --TEST-- swoole_http2_client_coro: cookies --SKIPIF-- --FILE-- set(['timeout' => 0.1]); Assert::false($cli->connect()); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); Assert::false($cli->send(new Swoole\Http2\Request)); Assert::same($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); Assert::false($cli->recv(1)); Assert::same($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/goaway.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 go away --SKIPIF-- --FILE-- set([ 'timeout' => 1, 'ssl_host_name' => $domain ]); if (!$cli->connect()) { return; // we can't connect to this website without proxy in China so we skip it. } $cli->goaway(SWOOLE_HTTP2_ERROR_NO_ERROR, '[GOAWAY] nothing~bye~bye~'); Assert::assert(!$cli->recv(-1)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/headers.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 headers auto to lower --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; // auto to-lower $req->headers = [ 'Host' => $domain, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip', ]; for ($n = 5; $n--;) { Assert::assert($cli->send($req)); $response = $cli->recv(); echo "{$response->statusCode}\n"; } }); ?> --EXPECT-- 200 200 200 200 200 ================================================ FILE: tests/swoole_http2_client_coro/host.phpt ================================================ --TEST-- swoole_http2_client_coro: host --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $domain = '127.0.0.1'; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set(['timeout' => 5]); $cli->connect(); $request = new Swoole\Http2\Request; for ($n = MAX_REQUESTS; $n--;) { $request->path = '/'; $request->headers = [ 'user-agent' => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'connection' => 'keep-alive' ]; for ($i = 32; $i--;) { $request->headers[md5(mt_rand(1, 65535))] = sha1(get_safe_random(32)); } Assert::assert($cli->send($request)); $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::same(json_encode($request->headers), $response->data); unset( $response->headers['host'], $response->headers['server'], $response->headers['date'], $response->headers['content-type'], $response->headers['content-length'] ); Assert::same($request->headers, $response->headers); } }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ // 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'http_compression' => false, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::same($request->header['host'], "{$http->host}:{$http->port}"); unset($request->header['host']); foreach ($request->header as $name => $value) { $response->header($name, $value); } $response->end(json_encode($request->header)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_http2_client_coro/http_proxy.phpt ================================================ --TEST-- swoole_http2_client_coro: get with http_proxy --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain, 'http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT, ]); Assert::true($c->connect(), var_dump_return($c)); $r = new Request(); $r->method = 'GET'; $r->path = '/'; $r->headers = [ 'host' => $domain, ]; $c->send($r); $response = $c->recv(); Assert::notEmpty($response); Assert::eq($response->statusCode, 301); Assert::eq($response->headers['location'], 'https://www.cloudflare.com/'); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/huge_headers.phpt ================================================ --TEST-- swoole_http2_client_coro: huge headers --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $domain = '127.0.0.1'; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set(['timeout' => 5]); $cli->connect(); $request = new Swoole\Http2\Request; for ($n = MAX_REQUESTS; $n--;) { $request->path = '/'; $request->headers = [ 'host' => $domain, 'user-agent' => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'connection' => 'keep-alive' ]; for ($i = 32; $i--;) { $request->headers[md5(mt_rand(1, 65535))] = sha1(get_safe_random(32)); } Assert::assert($cli->send($request)); $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::same(json_encode($request->headers), $response->data); unset( $response->headers['server'], $response->headers['date'], $response->headers['content-type'], $response->headers['content-length'] ); Assert::same($request->headers, $response->headers); } for ($i = 32; $i--;) { $request->headers[md5(mt_rand(1, 65535))] = sha1(get_safe_random(32)); } Assert::assert(!$cli->send($request)); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ // 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'http_compression' => false, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { foreach ($request->header as $name => $value) { $response->header($name, $value); } $response->end(json_encode($request->header)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Coroutine\Http2\Client::send(): header cannot bigger than remote max_header_list_size %d in %s on line %d ================================================ FILE: tests/swoole_http2_client_coro/issues_2374.phpt ================================================ --TEST-- swoole_http2_client_coro: Github#2374 --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; // auto to-lower $req->headers = [ 'Host' => $domain, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip', 'number' => 2333 ]; for ($n = 5; $n--;) { Assert::assert($cli->send($req)); $response = $cli->recv(); echo "{$response->statusCode}\n"; } }); ?> --EXPECT-- 200 200 200 200 200 ================================================ FILE: tests/swoole_http2_client_coro/max-frame-size.phpt ================================================ --TEST-- swoole_http2_client_coro: max frame size --SKIPIF-- &1"), 'nghttp2') === false) { skip('no nghttpd'); } ?> --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { co::sleep(0.1); $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = "/test.jpg"; Assert::greaterThanEq($cli->send($req), 1); $resp = $cli->recv(); Assert::contains($resp->headers['server'], 'nghttpd'); Assert::eq($resp->data, file_get_contents(TEST_IMAGE)); shell_exec("ps -A | grep nghttpd | awk '{print $1}' | xargs kill -9 > /dev/null 2>&1"); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $root = ROOT_DIR . '/examples'; $pm->wakeup(); shell_exec("nghttpd -v -d {$root}/ -a 0.0.0.0 {$pm->getFreePort()} --no-tls&"); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/multi.phpt ================================================ --TEST-- swoole_http2_client_coro: multi --SKIPIF-- --FILE-- set([ 'timeout' => -1, 'ssl_host_name' => $domain ]); Assert::true($cli->connect()); Assert::true($cli->connected); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip', ]; /**@var $response swoole\http2\response */ $i = 4; while ($i--) { Assert::assert($cli->send($req)); } $stream_map = []; $responses_headers_count_map = []; $i = 0; while ($cli->connected) { // it's for the test, you should make timeout bigger $response = $cli->recv(0.1); if ($response) { echo "$response->statusCode\n"; $responses_headers_count_map[] = count($response->headers); Assert::contains($response->data, 'nghttp2.org'); $stream_map[] = $response->streamId; if (++$i === 4) { break; } } } Assert::assert(empty(array_diff([1, 3, 5, 7], $stream_map))); Assert::lessThanEq(count(array_unique($responses_headers_count_map)), 2); Assert::assert($responses_headers_count_map[0] > 10); }); Swoole\Event::wait(); ?> --EXPECT-- 200 200 200 200 ================================================ FILE: tests/swoole_http2_client_coro/no-gzip.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 without gzip and recv big data (window-update) --SKIPIF-- --FILE-- set([ 'timeout' => -1, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/signup?next=/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => '' ]; $i = 5; while ($i--) { Assert::assert($cli->send($req)); } $i = 5; $map = []; while ($i--) { /**@var $response swoole_http2_response */ $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::assert(strpos($response->data, 'zhihu') !== false); $map[] = $response->streamId; } Assert::assert(!array_diff($map, [1, 3, 5, 7, 9])); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/number.phpt ================================================ --TEST-- swoole_http2_client_coro: number --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->connect()); $streams = []; $request = new Request; for ($n = MAX_REQUESTS; $n--;) { $request->data = $n; $streams[$client->send($request)] = $n; } for ($n = MAX_REQUESTS; $n--;) { /** @var $response Response */ $response = $client->recv(); Assert::same($streams[$response->streamId], (int)$response->data); } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('request', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) { Coroutine::sleep(mt_rand(1, MAX_REQUESTS) / 1000); $response->end($request->rawContent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/ping.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 ping --SKIPIF-- --FILE-- SWOOLE_LOG_WARNING]); go(function () { $cli = new Swoole\Coroutine\Http2\Client('nghttp2.org', 443, true); $cli->set(['timeout' => 5,]); if (!$cli->connect()) { return; // we can't connect to this website without proxy in China so we skip it. } $ret = $cli->ping(); Assert::assert($ret); Co::sleep(0.5); Assert::greaterThan($cli->send(new Swoole\Http2\Request), 0); /** @var $response Swoole\Http2\Response */ $response = $cli->recv(); if (!$response && $cli->errCode === SOCKET_ETIMEDOUT) { return; } if ($response && ((string) $response->statusCode)[0] === '5') { return; } Assert::contains($response->data, 'nghttp2'); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/pipeline.phpt ================================================ --TEST-- swoole_http2_client_coro: pipeline --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS, 8); $pm->parentFunc = function ($pid) use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $client = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->connect()); /** @var $channels Swoole\Coroutine\Channel[] */ $channels = []; for ($n = MAX_REQUESTS; $n--;) { $request = new Swoole\Http2\Request; $request->pipeline = true; $streamId = $client->send($request); if (Assert::greaterThan($streamId, 0)) { $data = $pm->getRandomData(); for ($i = 0; $i < strlen($data); $i++) { $client->write($streamId, $data[$i], $i === (strlen($data) - 1)); } $channels[$streamId] = $channel = new Swoole\Coroutine\Channel; Swoole\Coroutine::create(function () use ($streamId, $channel, $data) { /** @var $response Swoole\Http2\Response */ $response = $channel->pop(); $response->headers += ($channel->pop())->headers; Assert::same($response->streamId, $streamId); unset($response->headers['date']); Assert::same($response->headers, [ 'content-type' => 'application/srpc', 'trailer' => 'srpc-status, srpc-message', 'server' => 'swoole-http-server', 'content-length' => '8', 'srpc-status' => '0', 'srpc-message' => '', ]); Assert::same($response->data, $data); }); } } while (true) { /** @var $response Swoole\Http2\Response */ $response = $client->read(); $channels[$response->streamId]->push($response); if (!$response->pipeline) { unset($channels[$response->streamId]); } if (empty($channels)) { break; } } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->header('content-type', 'application/srpc'); $response->header('trailer', 'srpc-status, srpc-message'); $trailer = [ "srpc-status" => '0', "srpc-message" => '' ]; foreach ($trailer as $trailer_name => $trailer_value) { $response->trailer($trailer_name, $trailer_value); } $response->end($pm->getRandomData()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/post.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 without gzip and recv big data (window-update) --SKIPIF-- --FILE-- set([ 'timeout' => -1, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->method = 'POST'; $req->path = '/api/v4/answers/300000000/voters'; $req->headers = [ 'host' => $domain, "user-agent" => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip' ]; $req->data = '{"type":"up"}'; $cli->send($req); $response = $cli->recv(); Assert::true(in_array(json_decode($response->data)->error->code, [602, 10002, 100], true)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/send-cookies.phpt ================================================ --TEST-- swoole_http2_client_coro: nghttp2 big data with ssl --SKIPIF-- &1`, 'nghttp2') === false) { skip('no nghttpd'); } ?> --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { co::sleep(0.1); $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->connect(); $filename = pathinfo(__FILE__, PATHINFO_BASENAME); $req = new Swoole\Http2\Request; $req->path = "/{$filename}"; $req->cookies = [ 'foo' => 'bar', 'bar' => 'char' ]; for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->send($req)); $response = $cli->recv(1); Assert::same($response->data, co::readFile(__FILE__)); } `ps -A | grep nghttpd | awk '{print $1}' | xargs kill -9 > /dev/null 2>&1`; echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $root = __DIR__; `nghttpd -v -d {$root}/ -a 0.0.0.0 {$pm->getFreePort()} --no-tls&`; $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/send_only_bug.phpt ================================================ --TEST-- swoole_http2_client_coro: send only without recv and use sleep --SKIPIF-- --FILE-- set([ 'timeout' => 5, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = [ 'host' => $domain, "user-agent" => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip' ]; Assert::assert($cli->send($req)); // not recv here (core dump before ver < 4.0.3) co::sleep(1); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/set-cookies.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 response cookies --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip', ]; Assert::assert($cli->send($req)); /**@var $response swoole_http2_response */ $response = $cli->recv(); Assert::assert(is_array($response->headers)); Assert::assert(count($response->set_cookie_headers) >= 3); // check cookies foreach ($response->set_cookie_headers as $set_cookie_header) { $cookie = explode(';', $set_cookie_header, 2)[0]; list($key, $value) = explode('=', $cookie, 2); Assert::same($response->cookies[$key], $value); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_client_coro/sock_type_unix.phpt ================================================ --TEST-- swoole_http2_client_coro: sock type unix --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Client('unix:/' . UNIXSOCK_PATH, 0, false); Assert::eq($client->connect(), true); $req = new Request(); $req->method = 'POST'; $req->path = '/'; $client->send($req); $result = $client->recv(); Assert::eq($result->data, "OK"); $client = new Client('unix://' . UNIXSOCK_PATH, 0, false); Assert::eq($client->connect(), true); $req = new Request(); $req->method = 'POST'; $req->path = '/'; $client->send($req); $result = $client->recv(); Assert::eq($result->data, "OK"); $client = new Client('unix:///' . UNIXSOCK_PATH, 0, false); Assert::eq($client->connect(), true); $req = new Request(); $req->method = 'POST'; $req->path = '/'; $client->send($req); $result = $client->recv(); Assert::eq($result->data, "OK"); }); Swoole\Event::wait(); @unlink(UNIXSOCK_PATH); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server(UNIXSOCK_PATH, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_STREAM); $server->set([ 'worker_num' => 1, 'open_http2_protocol' => true ]); $server->on("workerStart", function ($server) use ($pm) { $pm->wakeup(); }); $server->on('request', function (\Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_client_coro/wrong_headers.phpt ================================================ --TEST-- swoole_http2_client_coro: http2 with wrong headers --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = 1; Assert::assert($cli->send($req)); Assert::assert(is_array($req->headers)); // check array /**@var $response swoole_http2_response */ $response = $cli->recv(); echo $response->statusCode; Assert::assert(stripos($response->data, 'tencent') !== false); }); ?> --EXPECT-- 200 ================================================ FILE: tests/swoole_http2_server/413.phpt ================================================ --TEST-- swoole_http2_server: 413 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $file = tempnam('/tmp', 'swoole_http2_'); file_put_contents($file, str_repeat('A', 2 * 1024 * 1024)); Assert::assert(empty($res = `nghttp -d {$file} https://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null`)); $pm->kill(); unlink($file); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'package_max_length' => 1024 * 1024, 'open_http2_protocol' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->getContent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/big_data.phpt ================================================ --TEST-- swoole_http2_server: big data --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $domain = '127.0.0.1'; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => 10, 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); Assert::assert($cli->connect()); $req = new Swoole\Http2\Request; $req->method = 'POST'; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip' ]; for ($n = MAX_REQUESTS; $n--;) { $req->data = get_safe_random(65535 + mt_rand(0, 65535)); Assert::assert($cli->send($req)); $res = $cli->recv(); Assert::same($res->statusCode, 200); Assert::same(md5($req->data), md5($res->data)); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->rawcontent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/compression.phpt ================================================ --TEST-- swoole_http2_server: conpression with http2 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $domain = '127.0.0.1'; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => -1, 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip' ]; for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->send($req)); $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::same(md5_file(__DIR__ . '/../../README.md'), md5($response->data)); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'http_gzip_level' => 9, 'http_compression' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/compression_types.phpt ================================================ --TEST-- swoole_http2_server: compression types --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { http2_compression_types_test($pm); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'http_compression_types' => [ 'text/html', 'application/json' ], 'open_http2_protocol' => true, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { if ($request->server['request_uri'] == '/html') { $response->end(str_repeat('A', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/json') { $response->setHeader('Content-Type', 'application/json'); $response->end(str_repeat('B', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/raw') { $response->setHeader('Content-Type', 'text/raw'); $response->end(str_repeat('C', $request->get['bytes'])); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_server/getMethod.phpt ================================================ --TEST-- swoole_http2_server: getMethod --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); Assert::true($cli->connect()); $req = new Swoole\Http2\Request(); $req->method = 'POST'; $req->path = '/api'; $req->headers = [ 'user-agent' => 'Chrome/49.0.2587.3', 'accept' => 'text/html,application/xhtml+xml,application/xml', 'accept-encoding' => 'gzip' ]; $req->data = '{"type":"up"}'; $cli->send($req); $response = $cli->recv(); $json = json_decode($response->data); Assert::same($json->request_method, 'POST'); Assert::same($json->getMethod, 'POST'); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('::', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP6); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $request_method = $request->server['request_method']; $getMethod = $request->getMethod(); $response->end(json_encode(compact('request_method', 'getMethod'), JSON_PRETTY_PRINT) . "\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/goaway.phpt ================================================ --TEST-- swoole_http2_server: goaway --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 10]); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $cli->recv(); Assert::same($cli->serverLastStreamId, $streamId); Assert::same($cli->errCode, SWOOLE_HTTP2_ERROR_NO_ERROR); Assert::same($cli->errMsg, 'NO_ERROR'); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->goaway(SWOOLE_HTTP2_ERROR_NO_ERROR, 'NO_ERROR'); $response->end($request->rawcontent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/http2_headers.phpt ================================================ --TEST-- swoole_http2_server: array headers --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $output = shell_exec("curl --http2-prior-knowledge --silent -I http://127.0.0.1:{$pm->getFreePort()}"); Assert::contains($output, 'HTTP/2 200'); Assert::contains($output, 'test-value: a'); Assert::contains($output, 'test-value: d5678'); Assert::contains($output, 'test-value: e'); Assert::contains($output, 'test-value: 5678'); Assert::contains($output, 'test-value: 3.1415926'); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->header('test-value', [ "a\r\n", 'd5678', "e \n ", null, 5678, 3.1415926, ]); $response->end('

Hello Swoole.

'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/issue_4365.phpt ================================================ --TEST-- swoole_http2_server: github issue#4365 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { if (Assert::assert(!empty($res = shell_exec("curl -s --http2-prior-knowledge http://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null")))) { Assert::length($res, N); echo "DONE\n"; } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('0.0.0.0', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'open_http2_protocol' => true, 'enable_reuse_port' => true, 'enable_coroutine' => false, 'log_level' => 1, 'log_file' => TEST_LOG_FILE, ]); $http->on('request', function ($request, $response) { $response->end(str_repeat('x', N - 4) . "\r\n\r\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_server/max_concurrency.phpt ================================================ --TEST-- swoole_http2_server: max_concurrency --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $n = SERVER_MAX_CONCURRENCY; // 200, low concurrency $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 10]); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $response = $cli->recv(); Assert::eq($response->statusCode, 200); Assert::eq($response->data, GREETING_MESSAGE); }); } System::sleep(0.005); // 503, high concurrency $n = SERVER_MAX_CONCURRENCY; while ($n--) { $cid_list[] = go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 10]); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $response = $cli->recv(); Assert::eq($response->statusCode, 503); }); } // wait Coroutine::join($cid_list); // low concurrency $n = SERVER_MAX_CONCURRENCY; $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 10]); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $response = $cli->recv(); Assert::eq($response->statusCode, 200); Assert::eq($response->data, GREETING_MESSAGE); }); } Coroutine::join($cid_list); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', 'max_concurrency' => SERVER_MAX_CONCURRENCY, 'open_http2_protocol' => true, ]); $http->on('start', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { System::sleep(0.1); $response->end(GREETING_MESSAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_server/nghttp2_big_data.phpt ================================================ --TEST-- swoole_http2_server: nghttp2 big data with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $file = TEST_IMAGE; if (Assert::assert(!empty($res = `nghttp -d {$file} https://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null`))) { Assert::same(md5($res), md5_file($file)); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->getContent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/no_compression.phpt ================================================ --TEST-- swoole_http2_server: no compression with http2 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $domain = '127.0.0.1'; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => -1, 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', ]; for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->send($req)); $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::same(md5_file(__DIR__ . '/../../README.md'), md5($response->data)); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'http_compression' => false, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/ping.phpt ================================================ --TEST-- swoole_http2_server: ping --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 10]); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $s = microtime(true); $response = $cli->recv(); time_approximate(0.5, microtime(true) - $s); Assert::same($response->streamId, $streamId); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Swoole\Timer::tick(100, function (int $id) use ($request, $response) { Assert::true($response->ping()); if (@++$GLOBALS['i'] === 5) { $response->end($request->rawcontent()); Swoole\Timer::clear($id); } }); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/sendfile.phpt ================================================ --TEST-- swoole_http2_server: sendfile with http2 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort(), false); $cli->set(['timeout' => -1]); Assert::true($cli->connect()); for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->send(new Swoole\Http2\Request)); } for ($n = MAX_REQUESTS; $n--;) { $response = $cli->recv(); if (Assert::notEmpty($response)) { if (Assert::same(md5_file(__DIR__ . '/../../README.md'), md5($response->data))) { if ($response->statusCode !== 200) { echo 'missing header' . PHP_EOL; } } } else { break; } } }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $mode) { $http = new Server('127.0.0.1', $pm->getFreePort(), $mode); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'open_http2_protocol' => true ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { if (!Assert::true($response->sendfile(__DIR__ . '/../../README.md'))) { echo swoole_strerror(swoole_last_error()) . PHP_EOL; } }); $http->start(); }; $pm->childFirst(); $pm->run(); } ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/sendfile_content_type.phpt ================================================ --TEST-- swoole_http2_server: sendfile [content-type] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort(), false); $cli->set(['timeout' => -1]); Assert::true($cli->connect()); Assert::assert($cli->send(new Swoole\Http2\Request)); $response = $cli->recv(); Assert::notEmpty($response); Assert::same(md5_file(TEST_IMAGE), md5($response->data)); Assert::same($response->statusCode, 200); Assert::same($response->headers['content-type'], 'image/jpeg'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'open_http2_protocol' => true ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { if (!Assert::true($response->sendfile(TEST_IMAGE))) { echo swoole_strerror(swoole_last_error()) . PHP_EOL; } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/sendfile_set_content_type.phpt ================================================ --TEST-- swoole_http2_server: sendfile [set content-type] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort(), false); $cli->set(['timeout' => -1]); Assert::true($cli->connect()); Assert::assert($cli->send(new Swoole\Http2\Request)); $response = $cli->recv(); Assert::notEmpty($response); Assert::same($response->statusCode, 200); Assert::same($response->headers['content-type'], 'application/javascript'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'open_http2_protocol' => true ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->setHeader('Content-Type', 'application/javascript'); if (!Assert::true($response->sendfile(__FILE__))) { echo swoole_strerror(swoole_last_error()) . PHP_EOL; } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/server_addr.phpt ================================================ --TEST-- swoole_http2_server: add server addr for http2 server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $ips) { go(function () use ($pm, $ips) { $domain = $ips[1]; $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => 10, 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); Assert::assert($cli->connect()); $req = new Swoole\Http2\Request; $req->method = 'POST'; $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip' ]; for ($n = MAX_REQUESTS; $n--;) { $req->data = get_safe_random(65535 + mt_rand(0, 65535)); Assert::assert($cli->send($req)); $res = $cli->recv(); Assert::same($res->statusCode, 200); Assert::same(md5($req->data), md5($res->data)); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm, $ips) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($ips) { $server = $request->server; Assert::eq($server['server_addr'], $ips[1]); Assert::eq($server['remote_addr'], $ips[1]); Assert::true($server['server_port'] != $server['remote_port']); $response->end($request->rawcontent()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/static_handler.phpt ================================================ --TEST-- swoole_http2_server: static handler --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort(), false); $cli->set([ 'timeout' => -1, ]); $cli->connect(); $req = new Swoole\Http2\Request; $req->path = '/README.md'; //request for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->send($req)); } //response for ($n = MAX_REQUESTS; $n--;) { $response = $cli->recv(); Assert::same($response->statusCode, 200); Assert::same(md5_file(DOC_ROOT . '/README.md'), md5($response->data)); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => DOC_ROOT, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_server/streaming.phpt ================================================ --TEST-- swoole_http2_server: streaming --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $proc = ChildProcess::exec("nghttp http://127.0.0.1:{$pm->getFreePort()}/"); $out = ''; while($line = $proc->read()) { $out .= $line; if (str_contains($line, 'hello world, end')) { break; } } echo $out; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, ]); $http->on('WorkerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('Request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $n = 5; while ($n--) { $response->write("hello world, #$n\n"); Co\System::sleep(0.1); } $response->end("hello world, end\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- hello world, #4 hello world, #3 hello world, #2 hello world, #1 hello world, #0 hello world, end ================================================ FILE: tests/swoole_http2_server/streaming_2.phpt ================================================ --TEST-- swoole_http2_server: streaming 2 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $proc = ChildProcess::exec("nghttp http://127.0.0.1:{$pm->getFreePort()}/"); $out = ''; while($line = $proc->read()) { $out .= $line; if (str_contains($line, 'hello world, #0')) { break; } } echo $out; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true, ]); $http->on('WorkerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('Request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $n = 5; while ($n--) { $response->write("hello world, #$n\n"); Co\System::sleep(0.1); } $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- hello world, #4 hello world, #3 hello world, #2 hello world, #1 hello world, #0 ================================================ FILE: tests/swoole_http2_server/trailer.phpt ================================================ --TEST-- swoole_http2_server: trailer --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); Assert::true($cli->connect()); /** @var $channels Swoole\Coroutine\Channel[] */ $channels = []; for ($n = MAX_REQUESTS; $n--;) { $streamId = $cli->send(new Swoole\Http2\Request); if (Assert::greaterThan($streamId, 0)) { $channels[$streamId] = $channel = new Swoole\Coroutine\Channel; go(function () use ($streamId, $channel) { /** @var $response Swoole\Http2\Response */ $response = $channel->pop(); Assert::same($response->streamId, $streamId); $headers = $response->headers; unset($headers['date']); Assert::same($headers, [ 'content-type' => 'application/srpc', 'trailer' => 'srpc-status, srpc-message', 'server' => 'swoole-http-server', 'content-length' => '32', 'srpc-status' => '0', 'srpc-message' => '', ]); }); } } while (true) { /** @var $response Swoole\Http2\Response */ $response = $cli->recv(); $channels[$response->streamId]->push($response); unset($channels[$response->streamId]); if (empty($channels)) { break; } } }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->header('content-type', 'application/srpc'); $response->header('trailer', 'srpc-status, srpc-message'); $trailer = [ "srpc-status" => '0', "srpc-message" => '' ]; foreach ($trailer as $trailer_name => $trailer_value) { $response->trailer($trailer_name, $trailer_value); } $response->end($pm->getRandomData()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http2_server/worker_max_concurrency.phpt ================================================ --TEST-- swoole_http2_server: worker_max_concurrency --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $n = SERVER_WORKER_MAX_CONCURRENCY * 3; $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); Assert::true($cli->connect()); Assert::greaterThan($streamId = $cli->send(new Swoole\Http2\Request), 0); $response = $cli->recv(); Assert::eq($response->statusCode, 200); Assert::eq($response->data, GREETING_MESSAGE); }); } // wait Coroutine::join($cid_list); $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); Assert::true($cli->connect()); $req = new Swoole\Http2\Request; $req->path = '/stats'; Assert::greaterThan($streamId = $cli->send($req), 0); $response = $cli->recv(); Assert::eq($response->statusCode, 200); $json = json_decode($response->data); Assert::true(isset($json->coroutine_peek_num)); Assert::eq($json->coroutine_peek_num, SERVER_WORKER_MAX_CONCURRENCY); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { // $mode = SWOOLE_BASE; $mode = SERVER_MODE_RANDOM; $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), $mode); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'open_http2_protocol' => true, 'worker_max_concurrency' => SERVER_WORKER_MAX_CONCURRENCY, ]); $http->on('start', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $http) { if ($request->server['request_uri'] == '/stats') { $response->end(json_encode($http->stats())); return; } System::sleep(0.1); $response->end(GREETING_MESSAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http2_server_coro/cookies.phpt ================================================ --TEST-- swoole_http2_server_coro: cookies --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); $cli->connect(); $requests = []; for ($n = MAX_REQUESTS; $n--;) { $request = new Swoole\Http2\Request; $request->headers = ['id' => $n]; $request->cookies = []; for ($k = 32; $k--;) { $request->cookies[get_safe_random()] = get_safe_random(); } $requests[$n] = $request; Assert::assert($cli->send($request)); } for ($n = MAX_REQUESTS; $n--;) { $response = $cli->recv(1); if (Assert::isInstanceOf($response, Swoole\Http2\Response::class)) { $request = $requests[$response->headers['id']]; Assert::same('OK', $response->data); Assert::same($request->cookies, $response->cookies); } } echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { Coroutine::create(function () use ($pm) { $http = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $http->handle('/', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('id', $request->header['id']); foreach ($request->cookie as $name => $value) { $response->cookie($name, $value); } $response->end('OK'); }); $http->start(); }); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/204.phpt ================================================ --TEST-- swoole_http_client_coro: http 204 no content --SKIPIF-- --FILE-- set(['timeout' => 5]); $cli->setHeaders([ 'Host' => $domain, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip' ]); Assert::assert($cli->post($path, [])); Assert::same($cli->statusCode, 204); Assert::assert(empty($cli->body)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/addData.phpt ================================================ --TEST-- swoole_http_client_coro: addData --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->addData(co::readFile(TEST_IMAGE), 'test.jpg', 'image/jpeg', 'test.jpg'); $cli->post('/upload_file', array('name' => 'rango')); Assert::same($cli->statusCode, 200); $ret = json_decode($cli->body, true); Assert::assert($ret and is_array($ret)); Assert::same(md5_file(TEST_IMAGE), $ret['md5']); $cli->close(); }); Swoole\Event::wait(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { include __DIR__ . '/../include/api/http_server.php'; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/alias.phpt ================================================ --TEST-- swoole_http_client_coro: alias --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $random = get_safe_random(16); Assert::assert($cli->get('/')); Assert::contains($cli->getBody(), 'baidu.com'); Assert::same($cli->getStatusCode(), 200); Assert::assert(count($cli->getHeaders()) > 5); Assert::assert(count($cli->getCookies()) > 2); echo "DONE\n"; }); Swoole\Event::wait(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/another_coroutine.phpt ================================================ --TEST-- swoole_http_client_coro: illegal another coroutine --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { $process = new Swoole\Process(function (Swoole\Process $worker) use ($pm) { function get(Swoole\Coroutine\Http\Client $client) { $client->get('/'); } $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); go(function () use ($cli) { (function () use ($cli) { (function () use ($cli) { co::sleep(0.001); get($cli); })(); })(); }); go(function () use ($cli) { $cli->get('/'); }); Swoole\Event::wait(); }, false); $process->start(); Swoole\Process::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function (Swoole\Http\Server $server) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server) { co::sleep(0.1); $server->shutdown(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] ERROR (PHP Fatal Error: %d): Swoole\Coroutine\Http\Client::get: Socket#%d has already been bound to another coroutine#%d, reading of the same socket in multiple coroutines at the same time is not allowed Stack trace: #0 Swoole\Coroutine\Http\Client->get() called at [%s:%d] #1 get() called at [%s:%d] #2 {closure}() called at [%s:%d] #3 {closure}() called at [%s:%d] ================================================ FILE: tests/swoole_http_client_coro/auto_reconnect.phpt ================================================ --TEST-- swoole_http_client_coro: auto reconnect --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->get('/')); Assert::same($cli->body, $pm->getRandomData(), var_dump_return($cli)); co::sleep(0.005); } }); Swoole\Event::wait(); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server) { $response->end($pm->getRandomData()); co::sleep(0.001); $server->close($request->fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/bind_address.phpt ================================================ --TEST-- swoole_http_client_coro: bind address and port --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $wg = new WaitGroup(); $wg->add(5); //test1: valid address go(function () use ($pm, $wg) { $client = new Client('127.0.0.1', $pm->getFreePort()); $bindAddress = current(swoole_get_local_ip()); $bindPort = get_one_free_port(); $client->set([ 'bind_address' => $bindAddress, 'bind_port' => $bindPort, ]); $client->post('/validaddress', ['bind_address' => $bindAddress, 'bind_port' => $bindPort]); $wg->done(); }); // test2: invalid address go(function () use ($pm, $wg) { $client = new Client('127.0.0.1', $pm->getFreePort()); $bindAddress = 11111; $bindPort = get_one_free_port(); $client->set([ 'bind_address' => $bindAddress, 'bind_port' => $bindPort, ]); $client->post('/invalidaddress', ['bind_address' => $bindAddress, 'bind_port' => $bindPort]); $wg->done(); }); // test3: invalid port go(function () use ($pm, $wg) { $client = new Client('127.0.0.1', $pm->getFreePort()); $bindAddress = current(swoole_get_local_ip()); $bindPort = -1; $client->set([ 'bind_address' => $bindAddress, 'bind_port' => -1, ]); $client->post('/invalidport', ['bind_address' => $bindAddress, 'bind_port' => $bindPort]); $wg->done(); }); // test4: not bind port go(function () use ($pm, $wg) { $client = new Client('127.0.0.1', $pm->getFreePort()); $bindAddress = current(swoole_get_local_ip()); $bindPort = null; $client->set([ 'bind_address' => $bindAddress, ]); $client->post('/notbindport', ['bind_address' => $bindAddress, 'bind_port' => $bindPort]); $wg->done(); }); //test5: request baidu.com go(function () use ($pm, $wg) { $client = new Client('www.baidu.com', 80); $bindAddress = current(swoole_get_local_ip()); $bindPort = get_one_free_port(); $client->set([ 'bind_address' => $bindAddress, 'bind_port' => $bindPort, ]); Assert::true($client->get('/')); $client = new Client('www.baidu.com', 80); $bindAddress = '127.0.0.1'; $bindPort = get_one_free_port(); $client->set([ 'bind_address' => $bindAddress, 'bind_port' => $bindPort, ]); Assert::false($client->get('/')); $wg->done(); }); $wg->wait(); $client = new Client('127.0.0.1', $pm->getFreePort()); $client->get('/stop?hello=1'); echo $client->body . PHP_EOL; echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/validaddress', function (Request $request, Response $response) { Assert::eq($request->server['remote_addr'], $request->post['bind_address']); Assert::eq($request->server['remote_port'], $request->post['bind_port']); }); $server->handle('/invalidaddress', function (Request $request, Response $response) { Assert::eq($request->post['bind_address'], '11111'); Assert::eq($request->server['remote_addr'], '127.0.0.1'); Assert::notEq($request->server['remote_port'], $request->post['bind_port']); }); $server->handle('/invalidport', function (Request $request, Response $response) { Assert::eq($request->post['bind_port'], '-1'); Assert::eq($request->server['remote_addr'], $request->post['bind_address']); Assert::greaterThan($request->server['remote_port'], 0); }); $server->handle('/notbindport', function (Request $request, Response $response) { Assert::keyNotExists($request->post, 'bind_port'); Assert::eq($request->server['remote_addr'], $request->post['bind_address']); Assert::greaterThan($request->server['remote_port'], 0); }); $server->handle('/stop', function ($request, $response) use ($server) { $response->end("

Stop

"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT--

Stop

DONE ================================================ FILE: tests/swoole_http_client_coro/bug_2661.phpt ================================================ --TEST-- swoole_http_client_coro: #2611 bound error with dns resolve and cross close --SKIPIF-- --FILE-- get($path); echo "push is " . $path . " " . Co::getCid() . "\n"; $client->close(); $ch->push($path); } function bar($client) { $length = 10; $ch = new Swoole\Coroutine\Channel($length); for ($i = 0; $i < $length; $i++) { go('foo', $ch, $client); } for ($i = 0; $i < $length; $i++) { go(function ($ch) { $data = $ch->pop(1); echo "pop is " . $data . "\n"; }, $ch); } } bar($client); }); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: Socket#%d has already been bound to another coroutine#%d, reading or writing of the same socket in coroutine#%d at the same time is not allowed in %s:%d Stack trace: #0 %s(%d): Swoole\Coroutine\Http\Client->get('%s') #1 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_http_client_coro/bug_3118.phpt ================================================ --TEST-- swoole_http_client_coro: Github #3118 --SKIPIF-- --FILE-- set(['timeout' => 10]); $codes = [200, 201, 304, 301, 302, 303,]; foreach ($codes as $code) { $client->get("/status/{$code}"); Assert::same($client->getStatusCode(), $code); } }); echo "DONE\n" ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/compression_with_big_data.phpt ================================================ --TEST-- swoole_http_client_coro: compression with big data --SKIPIF-- --FILE-- initRandomData(1, [1, 4, 8, 32][PRESSURE_LEVEL] * 1024 * 1024); $pm->parentFunc = function () use ($pm) { Co\Run(function () use ($pm) { $random = $pm->getRandomData(); foreach ([[], ['download' => ['/', TEST_LOG_FILE]]] as $download) { foreach (['deflate', 'gzip', 'br'] as $compression) { $response = httpRequest( "http://127.0.0.1:{$pm->getFreePort()}", ['headers' => ['Accept-Encoding' => $compression]] + $download ); Assert::same( empty($download) ? $response['body'] : file_get_contents(TEST_LOG_FILE), $random ); if (empty($download)) { phpt_var_dump($response['headers']['content-encoding'] ?? 'no-compression'); var_dump($response['headers']['content-encoding'] ?? $compression); } } } }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { static $random; if (!$random) { $random = $pm->getRandomData(); } $response->end($random); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- string(7) "deflate" string(4) "gzip" string(2) "br" ================================================ FILE: tests/swoole_http_client_coro/connect_timeout.phpt ================================================ --TEST-- swoole_http_client_coro: connect timeout --SKIPIF-- --FILE-- setHeaders([ 'Host' => "login.wx.qq.com", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $random_timeout = mt_rand(100, 1000) / 1000; phpt_var_dump($random_timeout); $cli->set(['connect_timeout' => $random_timeout]); $s = microtime(true); $cli->get('/'); $s = microtime(true) - $s; time_approximate($random_timeout, $s); echo $cli->body; $cli->close(); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/connection_close.phpt ================================================ --TEST-- swoole_http_client_coro: connection close --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); var_dump($client->get('/close')); var_dump($client->getHeaders()); var_dump($client->get('/keep_alive')); var_dump($client->getHeaders()); $client->close(); var_dump($client->errMsg); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort()); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function($request, $response) { if ($request->server['request_uri'] == '/close') { $response->header('connection', 'close'); } $response->end(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- bool(true) array(5) { ["connection"]=> string(5) "close" ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) "%s" ["content-type"]=> string(9) "text/html" ["content-length"]=> string(1) "0" } bool(true) array(5) { ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) "%s" ["connection"]=> string(10) "keep-alive" ["content-type"]=> string(9) "text/html" ["content-length"]=> string(1) "0" } string(0) "" DONE ================================================ FILE: tests/swoole_http_client_coro/construct_failed.phpt ================================================ --TEST-- swoole_http_client_coro: construct failed --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught Swoole\Coroutine\Http\Client\Exception: host is empty in %s/tests/swoole_http_client_coro/construct_failed.php:3 Stack trace: #0 %s/tests/swoole_http_client_coro/construct_failed.php(3): Swoole\Coroutine\Http\Client->__construct('') #1 {main} thrown in %s/tests/swoole_http_client_coro/construct_failed.php on line 3 ================================================ FILE: tests/swoole_http_client_coro/cookies_set_bug.phpt ================================================ --TEST-- swoole_http_client_coro: cookies set bug --SKIPIF-- --FILE-- set(['timeout' => 5]); $cli->setHeaders([ 'Host' => $domain, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip' ]); // first request $cookies = getCookies(); $cli->setCookies($cookies); Assert::assert($cli->get($path)); // second request $cookies = getCookies(); $cli->setCookies($cookies); Assert::assert($cli->get('/cookies')); }); ?> --EXPECTF-- array(0) { } array(0) { } ================================================ FILE: tests/swoole_http_client_coro/defer.phpt ================================================ --TEST-- swoole_http_client_coro: error handler --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); // normal for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->get('/')); Assert::same($cli->body, $pm->getRandomData()); } // failed when recv response $retry_time = microtime(true); for ($n = MAX_REQUESTS; $n--;) { Assert::assert(!$cli->get('/')); } $retry_time = microtime(true) - $retry_time; $pm->kill(); $pm->wait(); // failed when connect $failed_time = microtime(true); for ($n = MAX_REQUESTS; $n--;) { Assert::assert(!$cli->get('/')); Assert::same($cli->errCode, SOCKET_ECONNREFUSED); Assert::same($cli->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED, $cli->statusCode); } $failed_time = microtime(true) - $failed_time; phpt_var_dump($retry_time, $failed_time); Assert::assert($retry_time > $failed_time * 2); }); Swoole\Event::wait(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server) { static $i = 0; $i++; if ($i <= MAX_REQUESTS) { co::sleep(0.1 / MAX_REQUESTS); $response->end($pm->getRandomData()); } else { $server->close($request->fd); } }); $server->start(); $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/defer_02.phpt ================================================ --TEST-- swoole_http_client_coro: defer concurrency --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $randomData) { go(function () use ($pm, $randomData) { $clients = []; // normal for ($n = MAX_CONCURRENCY; $n--;) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); $cli->setDefer(); $clients[$n] = $cli; $retval = $cli->get('/?n=' . $n); Assert::assert($retval); if (!$retval) { var_dump($cli->errCode); } } for ($n = MAX_CONCURRENCY; $n--;) { $cli = $clients[$n]; $cli->recv(); Assert::same($cli->statusCode, 200); Assert::same($cli->body, $randomData[$n]); } }); Swoole\Event::wait(); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm, $randomData) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server, $randomData) { $response->end($randomData[$request->get['n']]); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/disable_keep_alive.phpt ================================================ --TEST-- swoole_http_client_coro: disable keep alive --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'keep_alive' => false ]); $cli->setHeaders(['Host' => $host]); $cli->get('/'); Assert::same($cli->statusCode, 200); Assert::true($cli->get('/ch/tech/')); Assert::same($cli->statusCode, 200); // failed clear $cli->set([ 'timeout' => 0.001 ]); Assert::false($cli->get('/ch/tech/')); Assert::assert(empty($cli->headers)); Assert::assert(empty($cli->body)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/download.phpt ================================================ --TEST-- swoole_http_client_coro: download file and download offset --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { $raw_file_size = filesize(TEST_IMAGE); $raw_file_content = file_get_contents(TEST_IMAGE); for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm, &$count, $c, $raw_file_size, $raw_file_content) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $filename = '/tmp/test-' . $c . '.jpg'; $offset = mt_rand(0, $raw_file_size); $cli->setHeaders(['Range' => "bytes=$offset-"]); Assert::assert($cli->download('/', $filename, 0)); // assert length if (!Assert::assert($raw_file_size === ($offset + filesize($filename)))) { goto _end; } // read content $raw_file = fopen(TEST_IMAGE, 'r+'); fseek($raw_file, $offset); if (!Assert::assert(co::fread($raw_file) === co::readFile($filename))) { goto _end; } $count++; _end: @unlink($filename); $cli->setHeaders([]); $cli->get('/'); Assert::same($cli->body, $raw_file_content); }); } Swoole\Event::wait(); Assert::same($count, MAX_CONCURRENCY_LOW); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(['log_file' => '/dev/null']); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $offset = (int) @explode('-', explode('=', $request->header['range'])[1])[0]; $response->sendfile(TEST_IMAGE, $offset); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/download_302.phpt ================================================ --TEST-- swoole_http_client_coro: http download 302 --SKIPIF-- --FILE-- download('/absolute-redirect/1', FILE); Assert::contains(file_get_contents(FILE), 'Redirecting'); if (((string)$cli->statusCode)[0] === '3') { $cli->download($cli->headers['location'], FILE); } if (Assert::contains(json_decode(file_get_contents(FILE), true)['url'], 'get')) { echo "OK\n"; } }); @unlink(FILE); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/download_failed.phpt ================================================ --TEST-- swoole_http_client_coro: http download io failure --SKIPIF-- --FILE-- bind('127.0.0.1', 0)); Assert::assert($server->listen()); $oort = $server->getsockname()['port']; go(function () use ($server) { $client = $server->accept(); while ($client->recv(1)) { CO::sleep(0.01); } $server->close(); }); $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $oort); $cli->set(['timeout' => 0.1]); Assert::false($cli->download('/get', FILE)); Assert::false(file_exists(FILE)); echo "OK\n"; }); Co\run(function () { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', 0)); Assert::assert($server->listen()); $oort = $server->getsockname()['port']; go(function () use ($server) { $client = $server->accept(); $client->send("HTTP/1.1 200 OK\r\nContent-Length: 99999\r\n\r\n"); while ($client->send('a')) { CO::sleep(0.001); } $server->close(); }); $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $oort); $cli->set(['timeout' => 0.1]); Assert::false($cli->download('/get', FILE)); Assert::true(file_exists(FILE)); echo "OK\n"; }); @unlink(FILE); ?> --EXPECT-- OK OK ================================================ FILE: tests/swoole_http_client_coro/download_filename_bug.phpt ================================================ --TEST-- swoole_http_client_coro: The bug of the filename parameter of download() --SKIPIF-- --FILE-- f = $f; } public function withSavedFileName($savedFileName) { $self = clone $this; $self->savedFileName = $savedFileName; return $self; } public function getSavedFileName() { return $this->savedFileName; } } function download($pm, $fileName) { $basename = substr($fileName, 0, -2); $fileName = $basename . '.jpg'; $c1 = new C1($fileName); $c1 = $c1->withSavedFileName($fileName); $client = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $client->set(['timeout' => 5]); $client->download('/', $fileName); Assert::true(file_get_contents(TEST_IMAGE) == file_get_contents($fileName)); } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm) { Co\run(function () use($pm) { download($pm, '/tmp/test-1.*'); }); Co\run(function () use($pm) { download($pm, '/tmp/test-2.*'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(['log_file' => '/dev/null']); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->sendfile(TEST_IMAGE); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/duplicate_header.phpt ================================================ --TEST-- swoole_http_client_coro: duplicate header --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $uuid) { Co\run(function () use ($pm, $uuid) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->get('/')); Assert::eq($client->headers['values-1'], ['hello', 'swoole', $uuid]); Assert::eq($client->headers['values-2'], ['hello', $uuid]); $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $uuid) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) use ($uuid) { $response->header('values-1', ['hello', 'swoole', $uuid]); $response->header('values-2', ['hello', $uuid]); $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/error_handler.phpt ================================================ --TEST-- swoole_http_client_coro: error handler --SKIPIF-- --FILE-- initRandomData(MAX_CONCURRENCY_MID); $rdata = []; for ($c = MAX_CONCURRENCY_MID; $c--;) { $rdata[] = $pm->getRandomData(); } $pm->parentFunc = function () use ($pm, $rdata) { run(function () use ($pm, $rdata) { $cli_map = []; $data_map = []; for ($c = MAX_CONCURRENCY_MID; $c--;) { $cli_map[$c] = $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->setDefer(true); $cli->get('/?c=' . $c); $data_map[$cli->socket->fd] = $rdata[$c]; } foreach ($cli_map as $cli) { Assert::assert($cli->recv()); Assert::same($cli->body, $data_map[$cli->socket->fd]); } $pm->kill(); $pm->wait(); System::sleep(0.2); // when we enable reconnect, we will get connect error foreach ($cli_map as $cli) { $cli->setDefer(false); Assert::assert(!$cli->get('/')); Assert::same($cli->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED); Assert::same($cli->errCode, SOCKET_ECONNREFUSED); } }); echo "OK\n"; }; $pm->childFunc = function () use ($pm, $rdata) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Request $request, Response $response) use ($pm, $server, $rdata) { static $i = 0; $i++; if ($i <= MAX_CONCURRENCY_MID) { $response->end($rdata[$request->get['c']]); } else { $server->close($request->fd); } }); $server->on('shutdown', function (Server $server) use ($pm) { foreach ($server->connections as $fd) { $server->close($fd); } $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/get.phpt ================================================ --TEST-- swoole_http_client_coro: http client --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('WorkerStart', function (Server $serv) { /* * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $domain = TEST_DOMAIN_3; $cli = new Client($domain, 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ 'Host' => $domain, 'User-Agent' => TEST_USER_AGENT, 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); Assert::assert($cli->socket instanceof Socket); if (!$ret) { $response->end('ERROR:' . $cli->errCode . "\n"); return; } $response->end("OK\n"); $cli->close(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/get_header_out_after_close.phpt ================================================ --TEST-- swoole_http_client_coro: getHeaderOut after close --SKIPIF-- --FILE-- set([ 'timeout' => -1, 'keep_alive' => false, ]); $http->execute('/get'); swoole_string($http->getHeaderOut())->contains('httpbin.org'); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/get_twice.phpt ================================================ --TEST-- swoole_http_client_coro: get twice --SKIPIF-- --FILE-- set(['timeout' => 5,]); for ($i = 0; $i < N; $i++) { $rand = mt_rand(100000, 999999999); $path = "/index.php?email=" . $rand . "@" . substr(md5(microtime(true)), 0, 8) . ".com"; $result = $client->get($path); if (!$result) { var_dump("ERROR: ".$client->getStatusCode()); } Assert::assert($result); Assert::assert($client->getStatusCode() == 200); $client->close(); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/get_twice_keepalive.phpt ================================================ --TEST-- swoole_http_client_coro: get twice and keepalive --SKIPIF-- --FILE-- set(['timeout' => 5,]); for ($i = 0; $i < N; $i++) { $rand = mt_rand(100000, 999999999); $path = "/index.php?email=" . $rand . "@" . substr(md5(microtime(true)), 0, 8) . ".com"; Assert::assert($client->get($path)); Assert::assert($client->getStatusCode() == 200); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/get_without_content_length.phpt ================================================ --TEST-- swoole_http_client_coro: http GET without Content-Length header --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { co::create(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 10 ]); $cli->setHeaders([ 'Connection' => 'close', 'Accept' => '*/*' ]); $ret = $cli->get('/'); Assert::true($ret); Assert::same($cli->statusCode, 200); Assert::assert(strlen($cli->body) > 1024 * 5); $pm->kill(); echo "OK\n"; }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'log_file' => '/dev/null' )); $serv->on('WorkerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $threadId, $data) { $serv->send($fd, "HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"); foreach (range(0, 5) as $i) { co::sleep(0.1); $serv->send($fd, str_repeat('A', rand(1024, 2048))); } $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/h2c_upgrade.phpt ================================================ --TEST-- swoole_http_client_coro: upgrade bug --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli->setHeaders([ 'host' => $host, // wrong case auto fix 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Language' => 'zh-CN,zh;q=0.9', 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5478.400 QQBrowser/10.1.1550.400', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); Assert::assert($ret); Assert::assert(str_contains($cli->body, 'httpbin.org')); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/head_method.phpt ================================================ --TEST-- swoole_http_client_coro: http client with HEAD method --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli->setMethod('HEAD'); $cli->get('/'); Assert::same($cli->statusCode, 200); Assert::assert(count($cli->headers) > 0); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/host.phpt ================================================ --TEST-- swoole_http_client_coro: host --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/http_chunk.phpt ================================================ --TEST-- swoole_http_client_coro: http chunk --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $chunks, $body) { Co\run(function () use ($pm, $chunks, $body) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->get('/')); Assert::eq($cli->getBody(), $body); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $chunks) { Co\run(function () use ($pm, $chunks) { Event::defer(function () use ($pm) { $pm->wakeup(); }); $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function ($req, $resp) use ($server, $chunks) { foreach ($chunks as $chunk) { $resp->write($chunk); usleep(mt_rand(10, 50) * 100); } }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/http_proxy.phpt ================================================ --TEST-- swoole_http_client_coro: http client with http_proxy --SKIPIF-- --FILE-- setHeaders(['Host' => $domain]); // without host header it can also work well $cli->set([ 'timeout' => 30, 'http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT ]); $result = $cli->get('/'); Assert::assert($result); Assert::assert(stripos($cli->body, '百度') !== false); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/http_proxy_443.phpt ================================================ --TEST-- swoole_http_client_coro: http client with http_proxy --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $domain = 'mail.qq.com'; $cli = new Swoole\Coroutine\Http\Client($domain, 443, true); $cli->setHeaders(['Host' => $domain]); $cli->set([ 'timeout' => 30, 'http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT ]); $result = $cli->get('/'); Assert::assert($result); Assert::assert(stripos($cli->body, 'tencent') !== false); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/http_proxy_with_host_port.phpt ================================================ --TEST-- swoole_http_client_coro: http client with http_proxy and host and port --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 1234); $cli->set([ 'timeout' => 30, 'http_proxy_host' => '127.0.0.1', 'http_proxy_port' => $pm->getFreePort(), ]); $cli->setHeaders([ 'Host' => '127.0.0.1:1234', ]); $cli->get('/'); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'log_file' => '/dev/null', 'open_eof_check' => true, 'package_eof' => "\r\n\r\n", ]); $server->on('Receive', function ($server, $fd, $reactor_id, $data) { echo $data; $server->close($fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- GET http://127.0.0.1:1234/ HTTP/1.1 Host: 127.0.0.1:1234 Connection: keep-alive Accept-Encoding: gzip, deflate, br ================================================ FILE: tests/swoole_http_client_coro/http_upload_big.phpt ================================================ --TEST-- swoole_http_client_coro: upload a big file --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $content = str_repeat(get_safe_random(1024), 5 * 1024); file_put_contents('/tmp/test.jpg', $content); $cli->addFile('/tmp/test.jpg', 'test.jpg'); $cli->setHeaders([ 'md5' => md5($content), ]); $ret = $cli->post('/', ['name' => 'rango']); Assert::assert($ret); Assert::assert(count($cli->headers) > 0); Assert::same($cli->statusCode, 200); Assert::eq($cli->body, 'success'); $cli->close(); @unlink('/tmp/test.jpg'); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', 'package_max_length' => 10 * 1024 * 1024, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Assert::eq(md5_file($request->files['test_jpg']['tmp_name']) ,$request->header['md5']); $response->end('success'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/https.phpt ================================================ --TEST-- swoole_http_client_coro: https client --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli->setHeaders([ 'Host' => 'www.baidu.com', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = ($cli->get('/')); if (!$ret) { echo("ERROR\n"); var_dump($cli->errCode); return; } else { echo("OK\n"); $cli->close(); } }); Swoole\Event::wait(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/https_upload_big.phpt ================================================ --TEST-- swoole_http_client_coro: upload a big file --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(), true); $content = str_repeat(get_safe_random(1024), 5 * 1024); file_put_contents('/tmp/test.jpg', $content); $cli->addFile('/tmp/test.jpg', 'test.jpg'); $cli->setHeaders([ 'md5' => md5($content), ]); $ret = $cli->post('/', ['name' => 'rango']); Assert::assert($ret); Assert::same($cli->statusCode, 200); Assert::eq($cli->body, 'success'); $cli->close(); @unlink('/tmp/test.jpg'); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'log_file' => '/dev/null', 'package_max_length' => 10 * 1024 * 1024, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Assert::eq(md5_file($request->files['test_jpg']['tmp_name']) ,$request->header['md5']); $response->end('success'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/issue_2664.phpt ================================================ --TEST-- swoole_http_client_coro: set_cookie_headers --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\Run(function () use ($pm) { var_dump(httpRequest("http://127.0.0.1:{$pm->getFreePort()}")['set_cookie_headers']); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $time_left = time() + 84600; $response->cookie('key1', 'val1', $time_left, '/', 'id.test.com'); $response->cookie('key1', '', 0, '/', 'test.com'); $response->cookie('key2', 'val2', $time_left, '/', 'id.test.com'); $response->cookie('key2', '', 0, '/', 'test.com'); $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(4) { [0]=> string(91) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com" [1]=> string(87) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=test.com" [2]=> string(91) "key2=val2; expires=%s; Max-Age=84600; path=/; domain=id.test.com" [3]=> string(87) "key2=deleted; expires=%s; Max-Age=0; path=/; domain=test.com" } ================================================ FILE: tests/swoole_http_client_coro/keep_alive.phpt ================================================ --TEST-- swoole_http_client_coro: really keep alive --SKIPIF-- --FILE-- set(['timeout' => 5]); Assert::assert($cli->get('/')); Assert::assert(strpos($cli->body, 'pecl') !== false); co::sleep(75); Assert::assert($cli->get('/')); Assert::assert(strpos($cli->body, 'pecl') !== false); }); Swoole\Event::wait(); echo "OK\n"; ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/long_domain.phpt ================================================ --TEST-- swoole_http_client_coro: long domain --SKIPIF-- --FILE-- set(['timeout' => 5]); $cli->setHeaders([ 'Host' => $domain, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip' ]); Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::assert(!empty($cli->body)); } go(function () { http_get('http://888888888888888888888888888888888888888888888888888888888888888.com'); }); go(function () { http_get('http://www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.com'); }); go(function () { http_get('http://www.mamashuojiusuannizhucedeyumingzaichanggoogledounengsousuochulai.cn'); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/lowercase_header.phpt ================================================ --TEST-- swoole_http_client_coro: lowercase header --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(), false); $cli->set(['timeout' => 10, 'lowercase_header' => false, ]); Assert::assert($cli->get('/')); Assert::assert(array_key_exists('Hello-world', $cli->getHeaders())); Assert::assert(!array_key_exists('hello-world', $cli->getHeaders())); $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(), false); Assert::assert($cli->get('/')); Assert::assert(!array_key_exists('Hello-world', $cli->getHeaders())); Assert::assert(array_key_exists('hello-world', $cli->getHeaders())); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null' ]); $http->on('WorkerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('Hello-world', 'swoole', false); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/multi.phpt ================================================ --TEST-- swoole_http_client_coro: multi http client --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli1->setHeaders([ 'Host' => 'www.baidu.com', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli1->setDefer(1); $cli2 = new Swoole\Coroutine\Http\Client('www.qq.com', 443, true); $cli2->set(['timeout' => 10]); $cli2->setHeaders([ 'Host' => 'www.qq.com', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli2->setDefer(1); $ret1 = ($cli1->get('/')); $ret2 = ($cli2->get('/')); if (!$ret1 or !$ret2) { echo "ERROR\n"; var_dump($cli1->errCode, $cli1->errMsg); var_dump($cli2->errCode, $cli2->errMsg); } else { Assert::assert($cli1->recv()); Assert::assert($cli2->recv()); Assert::contains($cli1->body, "baidu"); Assert::contains($cli2->body, "Tencent"); $cli1->close(); $cli2->close(); echo "OK\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/multi_and_reuse.phpt ================================================ --TEST-- swoole_http_client_coro: reuse defer client --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli->setHeaders([ 'Host' => $host, 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->setDefer(true); return $cli; } $baidu = createDeferCli('www.baidu.com', true); $qq = createDeferCli('news.qq.com', true); //first $baidu->get('/'); $qq->get('/'); $baidu->recv(10); $qq->recv(10); Assert::same($baidu->statusCode, 200); Assert::assert(stripos($baidu->body, 'baidu') !== false); Assert::same($qq->statusCode, 200); Assert::assert(stripos($qq->body, 'tencent') !== false); //reuse $baidu->get('/duty/'); $qq->get('/ch/tech/'); $baidu->recv(10); $qq->recv(10); Assert::same($baidu->statusCode, 200); Assert::assert(stripos($baidu->body, 'baidu') !== false); Assert::same($qq->statusCode, 200); Assert::assert(stripos($qq->body, 'tencent') !== false); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/parser.phpt ================================================ --TEST-- swoole_http_client_coro: http header field normal chars --SKIPIF-- --FILE-- 63 ? */ // 1, 1, 1, 1, 1, 1, 1, 0, // /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ // 1, 1, 1, 1, 1, 1, 1, 1, // /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ // 1, 1, 1, 1, 1, 1, 1, 0 }; /* Tokens as defined by rfc 2616. Also lowercases them. * token = 1* * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ static $normal_chars = [ '!', '$', '%', '&', '*', '+', '-', '.', '\'', // not support numeric header name in swoole // '0', // '1', // '2', // '3', // '4', // '5', // '6', // '7', // '8', // '9', // will be split and not allowed as a start in http parser // ':', // case insensitive // 'A', // 'B', // 'C', // 'D', // 'E', // 'F', // 'G', // 'H', // 'I', // 'J', // 'K', // 'L', // 'M', // 'N', // 'O', // 'P', // 'Q', // 'R', // 'S', // 'T', // 'U', // 'V', // 'W', // 'X', // 'Y', // 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~', // not allowed as a start in http parser '"', // start at 40 'x(', 'x)', 'x,', '/', 'x;', 'x<', 'x=', 'x>', 'x@', 'x[', 'x\\', 'x]', 'x{', '}', ]; $start = 40; $pm = new ProcessManager; $pm->initRandomData((count($normal_chars) + 1) * 4); $pm->parentFunc = function () use ($pm, &$normal_chars, $start) { // use curl $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept-Encoding: gzip']); curl_setopt($ch, CURLOPT_ENCODING, "gzip"); $output = curl_exec($ch); curl_close($ch); list($headers, $body) = explode("\r\n\r\n", $output); $headers = explode("\r\n", $headers); array_shift($headers); foreach ($headers as $header) { list($name, $value) = explode(': ', $header); if (in_array(strtolower($name), $normal_chars)) { Assert::same($value, ($s = $pm->getRandomData())); } } Assert::same($body, $pm->getRandomData()); // use swoole http client go(function () use ($pm, &$normal_chars, $start) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); Assert::assert($cli->get('/valid')); foreach ($cli->headers as $name => $value) { if (in_array($name, $normal_chars)) { Assert::same($value, $pm->getRandomData()); } } Assert::same($cli->body, $pm->getRandomData()); for ($i = $start; $i < sizeof($normal_chars); $i++) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); Assert::false($cli->get('/invalid?key=' . $i)); Assert::same($cli->errMsg, "Http invalid protocol"); $cli->close(); } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { global $normal_chars; global $start; if ($request->server['request_uri'] == '/') { foreach ($normal_chars as $char) { $response->header($char, $pm->getRandomData()); } } else if ($request->server['request_uri'] == '/valid') { for ($i = 0; $i < $start; $i++) { $response->header($normal_chars[$i], $pm->getRandomData()); } } else { $response->header($normal_chars[$request->get['key']], $pm->getRandomData()); } $response->end($pm->getRandomData()); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/post_array.phpt ================================================ --TEST-- swoole_http_client_coro: add array data --SKIPIF-- --FILE-- 'POST', 'data' => $data]); $form = json_decode($body, true)['form']; Assert::eq($form, $data); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/reconnect_but_failed.phpt ================================================ --TEST-- swoole_http_client_coro: reconnect but failed --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); for ($n = MAX_REQUESTS; $n--;) { $ret = $cli->get('/'); Assert::same($ret, !($n % 2)); if ($ret) { Assert::same($cli->body, $pm->getRandomData()); } } $pm->kill(); usleep(100000); Assert::assert(!$cli->get('/')); Assert::same($cli->errCode, SOCKET_ECONNREFUSED); Assert::same($cli->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED); for ($n = MAX_REQUESTS; $n--;) { Assert::assert(!$cli->get('/')); Assert::same($cli->errCode, SOCKET_ECONNREFUSED); Assert::same($cli->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED); } }); Swoole\Event::wait(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'enable_coroutine' => false, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server) { static $i = 0; $i++; if ($i % 2) { $server->close($request->fd); } else { $response->end($pm->getRandomData()); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/recv_slow_timeout.phpt ================================================ --TEST-- swoole_http_client_coro: recv_all data from slow server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); $s = microtime(true); $ret = $cli->get('/'); $s = microtime(true) - $s; phpt_var_dump($s); if (Assert::assert(!$ret)) { Assert::assert($cli->errCode === SOCKET_ETIMEDOUT); Assert::assert($cli->statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT); time_approximate(1, $s); } $cli->close(); }); } Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); $pm->wakeup(); while ($client = $server->accept()) { go(function () use ($server, $client) { Assert::assert($client instanceof Socket); $data = "HTTP/1.1 200 OK\r\n" . "Connection: keep-alive\r\n" . "Server: gunicorn/19.9.0\r\n" . "Date: Wed, 26 Dec 2018 23:56:51 GMT\r\n" . "Content-Type: text/html; charset=utf-8\r\n" . "Content-Length: 10122\r\n" . "Access-Control-Allow-Origin: *\r\n" . "Access-Control-Allow-Credentials: true\r\n" . "Via: 1.1 vegur\r\n" . "\r\n"; for ($n = 0; $n < strlen($data); $n++) { var_dump_return("send {$n}\n"); $client->send($data[$n]); usleep(mt_rand(10, 800) * 1000); } }); } }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/recv_timeout.phpt ================================================ --TEST-- swoole_http_client_coro: recv timeout --SKIPIF-- --FILE-- bind('127.0.0.1'); $GLOBALS['socket']->listen(); $GLOBALS['port'] = (int)$GLOBALS['socket']->getsockname()['port']; go(function () { for ($c = MAX_CONCURRENCY_MID; $c--;) { $conn = $GLOBALS['socket']->accept(); Assert::assert($conn instanceof Co\Socket); $GLOBALS['connections'][] = $conn; } }); for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () { $cli = new Co\Http\Client('127.0.0.1', $GLOBALS['port']); $cli->setDefer(); $config_timeout = mt_rand(100, 500) / 1000; $cli->set(['timeout' => $config_timeout]); Assert::assert($cli->get('/')); $arg_timeout = mt_rand(100, 500) / 1000; $s = microtime(true); if (mt_rand(0, 1)) { $ret = $cli->recv(); time_approximate($config_timeout, microtime(true) - $s); } else { $ret = $cli->recv($arg_timeout); time_approximate($arg_timeout, microtime(true) - $s); } Assert::assert(!$ret); Assert::assert($cli->errCode === SOCKET_ETIMEDOUT); }); } Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/set_basic_auth.phpt ================================================ --TEST-- swoole_http_client_coro: http client set basic auth --SKIPIF-- --FILE-- set(['timeout' => 10]); $cli->setHeaders([ 'host' => 'httpbin.org', 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $username = get_safe_random(); $password = get_safe_random(); $cli->setBasicAuth($username, $password); $ret = $cli->get("/basic-auth/{$username}/{$password}"); if ($ret && !empty($cli->statusCode === 200)) { echo "OK\n"; } else { echo "ERROR\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/slow_server.phpt ================================================ --TEST-- swoole_http_client_coro: slow server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { co::create(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 10 ]); $cli->setHeaders([ 'Connection' => 'close', 'Accept' => '*/*' ]); $ret = $cli->get('/'); Assert::true($ret); Assert::same($cli->statusCode, 200); Assert::assert(strlen($cli->body) > 1024 * 5); Assert::same($cli->headers['server'], 'nginx'); Assert::same($cli->headers['x-server'], 'swoole'); Assert::same($cli->headers['content-type'], 'text/html'); Assert::eq($cli->headers['content-length'], strlen($cli->body) ); $pm->kill(); echo "OK\n"; }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'log_file' => '/dev/null' )); $serv->on('WorkerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $threadId, $data) { $html = base64_encode(random_bytes(rand(1024, 65536))); $len = strlen($html); $data = "HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: $len\r\nX-Server: swoole\r\n\r\n$html"; $chunks = str_split($data, 5); foreach ($chunks as $out) { $serv->send($fd, $out); usleep(100); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_client_coro/socks5_proxy.phpt ================================================ --TEST-- swoole_http_client_coro: socks5 proxy --SKIPIF-- --FILE-- setHeaders(['Host' => $domain]); $cli->set([ 'timeout' => 5, 'socks5_host' => SOCKS5_PROXY_HOST, 'socks5_port' => SOCKS5_PROXY_PORT, ]); $ret = $cli->get('/'); if (!$ret) { die("ERROR\n"); } Assert::same($cli->statusCode, 200); Assert::assert(stripos($cli->body, 'google.com') !== false); $cli->close(); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/socks5_proxy_ipv6.phpt ================================================ --TEST-- swoole_http_client_coro: socks5 proxy with IPv6 --SKIPIF-- --FILE-- setHeaders([ 'Host' => $domain, ]); $client->set([ 'ssl_host_name' => $domain, 'socks5_host' => SOCKS5_PROXY_HOST, 'socks5_port' => SOCKS5_PROXY_PORT, ]); Assert::true($client->get('/')); $json = json_decode($client->body); Assert::eq($json->code, 403); $client->close(); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/ssl.phpt ================================================ --TEST-- swoole_http_client_coro: error handler --SKIPIF-- --FILE-- set([ 'timeout' => 10, 'ssl_host_name' => $domain ]); $random = get_safe_random(16); Assert::assert($cli->get('/get?foo=' . $random)); Assert::assert(strpos($cli->body, $random) !== false); echo "DONE\n"; }); Swoole\Event::wait(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/ssl_host_name.phpt ================================================ --TEST-- swoole_http_client_coro: https client with ssl_host_name --SKIPIF-- --FILE-- set([ 'timeout' => 5, 'ssl_host_name' => 'httpbin.org' ]); $c->get('/'); Assert::assert(strlen($c->body) > 0); Assert::same($c->statusCode, 200); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/ssl_verify_peer_1.phpt ================================================ --TEST-- swoole_http_client_coro: ssl_verify_peer [1] --SKIPIF-- --FILE-- SWOOLE_LOG_WARNING]); define('SSL_DIR', realpath(__DIR__.'/../../examples/ssl')); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { //allow_self_signed $client = new Client('127.0.0.1', $pm->getFreePort(), true); $client->set([ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, ]); $client->setHeaders([ 'Host' => "localhost", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $result = $client->get("/"); Assert::eq($result, true); Assert::eq($client->getBody(), "OK"); //no allow_self_signed $client = new Client('127.0.0.1', $pm->getFreePort(), true); $client->set([ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => false, ]); $client->setHeaders([ 'Host' => "localhost", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $result = $client->get("/"); Assert::eq($result, false); Assert::eq($client->getStatusCode(), -1); Assert::eq($client->errCode, SWOOLE_ERROR_SSL_VERIFY_FAILED); }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $server->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_DIR . '/ssl.crt', 'ssl_key_file' => SSL_DIR . '/ssl.key', ]); $server->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Request $request, Response $response) { $response->end('OK'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/ssl_verify_peer_2.phpt ================================================ --TEST-- swoole_http_client_coro: ssl_verify_peer [2] --SKIPIF-- --FILE-- set([ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, ]); $result = $client->get("/"); Assert::eq($result, true); Assert::eq($client->getStatusCode(), 200); $info = openssl_x509_parse($client->getPeerCert()); Assert::contains($info['name'], 'baidu.com'); Assert::contains($client->getBody(), 'baidu'); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/timeout_before_connect.phpt ================================================ --TEST-- swoole_http_client_coro: use timeout and timeout before connect --SKIPIF-- --FILE-- $host ]; $ip = Co::gethostbyname($host); $cli1 = new Swoole\Coroutine\Http\Client($ip, 443, true); $cli1->setHeaders($requestHeaders); $cli1->set(['timeout' => 0.001]); $cli1->setDefer(true); Assert::false($cli1->get('/')); Assert::false($cli1->recv()); Assert::same($cli1->errCode, SOCKET_ETIMEDOUT); Assert::same($cli1->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED); $cli2 = new Swoole\Coroutine\Http\Client($ip, 443, true); $cli2->setHeaders($requestHeaders); $cli2->setDefer(true); Assert::false($cli1->get('/')); Assert::true($cli2->get('/')); Assert::false($cli1->recv()); Assert::true($cli2->recv()); Assert::same($cli1->errCode, SOCKET_ETIMEDOUT); Assert::same($cli1->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED); Assert::same($cli2->errCode, 0); Assert::assert($cli2->statusCode === 200 && strpos($cli2->body, 'tencent') !== false); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/timeout_when_recv.phpt ================================================ --TEST-- swoole_http_client_coro: timeout in recv --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $port) { go(function () use ($pm, $port) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $port); $cli->set(['timeout' => 0.1]); $cli->setHeaders([ 'hello' => 'swoole' ]); $cli->setDefer(); $cli->get('/'); Assert::assert(!$cli->recv()); Assert::assert(empty($cli->body)); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Http\Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set([ 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function (Swoole\Http\Server $serv) { global $pm; $pm->wakeup(); }); $serv->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Assert::same($request->header['hello'], 'swoole'); co::sleep(2); $response->end('ok!'); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/unixsocket.phpt ================================================ --TEST-- swoole_http_client_coro: http unix-socket --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('unix:' . str_repeat('/', mt_rand(0, 2)) . UNIXSOCK_PATH); for ($n = MAX_REQUESTS; $n--;) { Assert::assert($client->get('/'), "statusCode={$client->statusCode}, error={$client->errCode}"); Assert::same($client->body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server(UNIXSOCK_PATH, 0, SERVER_MODE_RANDOM, SWOOLE_UNIX_STREAM); $server->set(['log_file' => '/dev/null']); $server->on(\Swoole\Constant::EVENT_START, function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('Hello Swoole!'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_client_coro/upload.phpt ================================================ --TEST-- swoole_http_client_coro: upload file --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function() use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->addFile(TEST_IMAGE, 'test.jpg'); $cli->post('/upload_file', array('name' => 'rango')); Assert::same($cli->statusCode, 200); $ret = json_decode($cli->body, true); Assert::assert($ret and is_array($ret)); Assert::same(md5_file(TEST_IMAGE), $ret['md5']); $cli->close(); }); Swoole\Event::wait(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { include __DIR__ . '/../include/api/http_server.php'; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/upload_huge.phpt ================================================ --TEST-- swoole_http_client_coro: upload a big file --SKIPIF-- --FILE-- set(['timeout' => 10]); $content = str_repeat(get_safe_random(IS_IN_CI ? 16 : 64), 1024 * 1024); // 64M file_put_contents('/tmp/test.jpg', $content); $cli->addFile('/tmp/test.jpg', 'test.jpg'); $ret = $cli->post('/post', ['name' => 'twosee']); if ($ret) { Assert::assert(count($cli->headers) > 0); Assert::assert($cli->statusCode === 200); $body = json_decode($cli->body, true); Assert::assert($body['files']['test.jpg'] === $content); echo "SUCCESS\n"; } $cli->close(); @unlink('/tmp/test.jpg'); }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_client_coro/upload_with_null_args.phpt ================================================ --TEST-- swoole_http_client_coro: upload file with null args --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->addFile(TEST_IMAGE, 'test.jpg', null, null, 0, 0); $cli->post('/upload_file', ['name' => 'rango']); Assert::same($cli->statusCode, 200); $ret = json_decode($cli->body, true); Assert::assert($ret and is_array($ret)); Assert::same($ret['files']['test_jpg']['name'], 'test.jpg'); Assert::same($ret['files']['test_jpg']['type'], 'image/jpeg'); Assert::assert(preg_match('#/tmp/swoole\.upfile\.#', $ret['files']['test_jpg']['tmp_name'])); Assert::same($ret['files']['test_jpg']['error'], 0); Assert::same($ret['files']['test_jpg']['size'], filesize(TEST_IMAGE)); Assert::same(md5_file(TEST_IMAGE), $ret['md5']); $cli->close(); }); Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { include __DIR__ . '/../include/api/http_server.php'; }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/websocket/1.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client & server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $cli->setHeaders([]); $ret = $cli->upgrade('/'); if (!$ret) { echo "ERROR\n"; return; } echo $cli->recv()->data; for ($i = 0; $i < 5; $i++) { $cli->push('hello server'); echo $cli->recv()->data; co::sleep(0.1); } }); Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $ws->set([ 'log_file' => '/dev/null', ]); $ws->on('WorkerStart', function (Server $serv) { /* * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $ws->on('open', function (Server $serv, Request $request) { $ip = Co::gethostbyname(TEST_DOMAIN_1); if ($ip) { $serv->push($request->fd, "start\n"); } else { $serv->push($request->fd, 'error: ' . swoole_last_error() . "\n"); } }); $ws->on('message', function ($serv, $frame) { co::sleep(0.1); $serv->push($frame->fd, "hello client\n"); }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- start hello client hello client hello client hello client hello client ================================================ FILE: tests/swoole_http_client_coro/websocket/auto_pong.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: auto pong --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); $client->push('hello world', SWOOLE_WEBSOCKET_OPCODE_TEXT); while ($client->recv()) { $client->push('hello world', SWOOLE_WEBSOCKET_OPCODE_TEXT); } }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_websocket_pong_frame' => true ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $data = []; $count = 0; $server->on('message', function (Server $server, Frame $frame) use ($pm, &$data, &$count) { if ($count == 1000) { $server->disconnect($frame->fd); for ($i = 0; $i < 1000; $i++) { Assert::true($data[$i]->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); } return; } if ($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG) { $count++; $data[] = $frame; } $server->push($frame->fd, "hello world", SWOOLE_WEBSOCKET_OPCODE_TEXT); $ping = new Frame(); $ping->data = $frame->data; $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; $server->push($frame->fd, $ping); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/bug_01.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: handshake + frame --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Co\http\Client('127.0.0.1', $pm->getFreePort()); $ret = $cli->upgrade('/'); if (!$ret) { echo "ERROR\n"; return; } echo "CONNECTED\n"; echo $cli->recv()->data; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $ws->set(array( 'log_file' => '/dev/null' )); $ws->on('WorkerStart', function (Server $serv) { global $pm; $pm->wakeup(); }); $ws->on('receive', function ($serv, $fd, $threadId, $data) { $sendData = "HTTP/1.1 101 Switching Protocols\r\n"; $sendData .= "Upgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: IFpdKwYy9wdo4gTldFLHFh3xQE0=\r\n"; $sendData .= "Sec-Websocket-Version: 13\r\nServer: swoole-http-server\r\n\r\n"; $sendData .= WebSockerServer::pack("hello world\n"); $serv->send($fd, $sendData); }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- CONNECTED hello world ================================================ FILE: tests/swoole_http_client_coro/websocket/bug_02.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: bug use client in server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $ret = $cli->upgrade('/'); Assert::assert($ret); echo $cli->recv()->data; for ($i = 0; $i < 5; $i++) { $cli->push("hello server\n", SWOOLE_WEBSOCKET_OPCODE_TEXT, true); echo ($cli->recv(1))->data; co::sleep(0.1); } $cli->close(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $ws->set([ 'log_file' => '/dev/null', 'worker_num' => 1 ]); $ws->on('workerStart', function (Swoole\WebSocket\Server $serv) use ($pm) { $pm->wakeup(); }); $ws->on('open', function (Swoole\WebSocket\Server $ws, Swoole\Http\Request $request) { $ws->push($request->fd, "server: hello, welcome\n"); }); $ws->on('message', function (Swoole\WebSocket\Server $ws, Swoole\WebSocket\Frame $frame) { echo "client: {$frame->data}"; $frame->data = str_replace('server', 'client', $frame->data); $ws->push($frame->fd, "server-reply: {$frame->data}"); }); $ws->on('close', function (Swoole\WebSocket\Server $ws, int $fd) { echo "client-{$fd} is closed\n"; }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- server: hello, welcome client: hello server server-reply: hello client client: hello server server-reply: hello client client: hello server server-reply: hello client client: hello server server-reply: hello client client: hello server server-reply: hello client client-1 is closed ================================================ FILE: tests/swoole_http_client_coro/websocket/close_socket.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: close socket --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $cli->setHeaders([]); $ret = $cli->upgrade('/'); if (!$ret) { echo "ERROR\n"; return; } Assert::assert($cli->socket->close()); Assert::false($cli->recv()); Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); Assert::false($cli->push('hello server')); Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $ws->set(array( 'log_file' => '/dev/null' )); $ws->on('WorkerStart', function (Swoole\Server $serv) { global $pm; $pm->wakeup(); }); $ws->on('open', function ($serv, Swoole\Http\Request $request) { }); $ws->on('message', function ($serv, $frame) { $serv->push($frame->fd, "hello client\n"); }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frame_finish_flag.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: continue frame finish flag --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); Assert::eq($frame->finish, true); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == $data1 . $data2 . $data3); Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); Assert::eq($frame->finish, true); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frame_finish_flag2.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: continue frame finish flag - 2 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); Assert::eq($frame->finish, true); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); Assert::eq($frame->finish, true); $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frames.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client continue frames - 1 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == $data1 . $data2 . $data3); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frames2.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client continue frames - 2 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == $data1 . $data2 . $data3); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s All fragments of a message, except for the initial frame, must use the continuation frame opcode(0). ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frames3.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client continue frames - 3 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == $data1 . $data2 . $data3); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s A continuation frame cannot stand alone and MUST be preceded by an initial frame whose opcode indicates either text or binary data. ================================================ FILE: tests/swoole_http_client_coro/websocket/continue_frames4.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client continue frames - 4 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $results = []; $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $results[] = $client->recv(); Assert::true($results[0]->data == $data1 . $data2 . $data3); $client->push('222', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $results[] = $client->recv(); Assert::true($results[1]->data == $data3 . $data2 . $data1); Assert::true($results[0]->data != $results[1]->data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { if ($frame->data == '111') { $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); } else { $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/control_frame_compress.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: control frame can not compress --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['websocket_compression' => true]); $ret = $client->upgrade('/'); Assert::assert($ret); $closeFrame = new CloseFrame(); $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $closeFrame->reason = 'hahahahaha'; $closeFrame->flags = 0; $client->push($closeFrame, SWOOLE_WEBSOCKET_FLAG_RSV1 | SWOOLE_WEBSOCKET_FLAG_COMPRESS); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'websocket_compression' => true, 'open_websocket_close_frame' => true, 'package_max_length' => 300 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm) { Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_FIN) == 1); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/control_frame_fragmented.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: Control frames must not be fragmented --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'websocket_compression' => true, 'open_websocket_close_frame' => true, ]); $ret = $client->upgrade('/'); Assert::assert($ret); $closeFrame = new CloseFrame(); $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $closeFrame->reason = 'hahahahaha'; $closeFrame->flags = 0; $client->push($closeFrame, 0); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'websocket_compression' => true, 'open_websocket_close_frame' => true, 'package_max_length' => 300 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm) { Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_FIN) == 1); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/disconnect.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: test disconnect function --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::true($client->disconnect(SWOOLE_WEBSOCKET_CLOSE_NORMAL, 'close it')); Assert::true($client->connected == 0); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_websocket_close_frame' => true ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) { var_dump($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); var_dump($frame->reason == 'close it'); }); $server->on('close', function (Swoole\Server $server, int $fd, int $reactorId) { var_dump($reactorId); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- bool(true) bool(true) int(0) ================================================ FILE: tests/swoole_http_client_coro/websocket/open_websocket_frame.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: open websocket frame --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('Hello World!!!'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); $frame = $client->recv(); Assert::true($frame == ''); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == 'Hello World!!!'); $ping = new Frame(); $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; $server->push($frame->fd, $ping); $pong = new Frame(); $pong->opcode = SWOOLE_WEBSOCKET_OPCODE_PONG; $server->push($frame->fd, $pong); $server->disconnect($frame->fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/ping.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: test ping function --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['open_websocket_pong_frame' => true]); $ret = $client->upgrade('/'); Assert::true($client->ping('Hello World')); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); Assert::true($frame->data == 'Hello World'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/priority.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: control frame priority - 1 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'open_websocket_ping_frame' => true ]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('Hello World!!!'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data2 . $data3); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == 'Hello World!!!'); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $ping = new Frame(); $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; $ping->data = 'PING'; $server->push($frame->fd, $ping); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/priority1.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: control frame priority - 2 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'open_websocket_close_frame' => true ]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('Hello World!!!'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); $frame = $client->recv(); Assert::true($frame == ''); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { Assert::true($frame->data == 'Hello World!!!'); $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->disconnect($frame->fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/send_more_continue_frame.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: send more continue frames - websocket server --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $results = []; $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['websocket_compression' => true]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3); $frame = $client->recv(); Assert::true($frame->data == $data2 . $data1 . $data3); $frame = $client->recv(); Assert::true($frame->data == $data3 . $data2 . $data1); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'package_max_length' => 100 * 1024 * 1024, 'websocket_compression' => true ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { $context = deflate_init(ZLIB_ENCODING_RAW); $server->push($frame->fd, deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $server->push($frame->fd, deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $server->push($frame->fd, deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/send_more_continue_frame2.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: send more continue frames - coroutine websocket server --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $results = []; $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['websocket_compression' => true]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3); $frame = $client->recv(); Assert::true($frame->data == $data2 . $data1 . $data3); $frame = $client->recv(); Assert::true($frame->data == $data3 . $data2 . $data1); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set(['websocket_compression' => true]); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); $context = deflate_init(ZLIB_ENCODING_RAW); $response->push(deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $response->push(deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/server_push_first.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: websocket server push first --SKIPIF-- --FILE-- initFreePorts(); $pm->initRandomDataArray(2, 0, true); Co\run(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); go(function () use ($pm, $server) { $server->handle( '/websocket', function ($request, $ws) use ($pm) { $ws->upgrade(); $ws->push($pm->getRandomDataElement(0)); $ws->push($pm->getRandomDataElement(1)); } ); $server->handle( '/shutdown', function ($request, $response) use ($server) { echo "shutdown\n"; $response->status(200); $server->shutdown(); } ); $server->start(); }); go(function () use ($pm, $server) { $wr = WaitRef::create(); $childs = []; for ($c = MAX_CONCURRENCY_LOW; $c--;) { $childs[] = go(function () use ($pm, $wr) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $ret = $cli->recv(); Assert::same($ret->data, $pm->getRandomDataElement(0)); $ret = $cli->recv(); Assert::same($ret->data, $pm->getRandomDataElement(1)); }); } WaitRef::wait($wr); echo "DONE\n"; $server->shutdown(); }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/websocket/ssl_1.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: ssl recv --SKIPIF-- --FILE-- upgrade('/'); if (!$ret) { echo "ERROR\n"; return; } $n = 16; while ($n--) { $data = base64_encode(random_bytes(rand(1, 16*1024))); $cli->push($data); $frame = $cli->recv(); Assert::true(is_object($frame)); Assert::eq($frame->data, $data); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_client_coro/websocket/ssl_2.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: ssl recv [2] --SKIPIF-- --FILE-- SWOOLE_LOG_TRACE, 'trace_flags' => SWOOLE_TRACE_ALL]); Co\run(function () { $cli = new Co\http\Client('www.bitmex.com', 443, true); if (($http_proxy_conf = getenv('https_proxy'))) { $uri = parse_url($http_proxy_conf); $cli->set([ 'socks5_host' => $uri['host'], 'socks5_port' => $uri['port'], ]); } $ret = $cli->upgrade('/realtime'); if (!$ret) { echo "ERROR\n"; return; } echo "CONNECT SUCCESS, StatusCode={$cli->getStatusCode()}\n"; $n = 16; $cli->push('{"op": "subscribe", "args": ["orderBookL2_25:XBTUSD"]}'); while ($n--) { $frame = $cli->recv(); if (!$frame or empty($frame->data)) { echo "ERROR $n [2]\n"; var_dump($cli->errCode, $cli->errMsg); break; } } echo "FINISH\n"; }); ?> --EXPECT-- CONNECT SUCCESS, StatusCode=101 FINISH ================================================ FILE: tests/swoole_http_client_coro/websocket/timeout.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: websocket client & server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Co\http\Client('127.0.0.1', $pm->getFreePort()); $ret = $cli->upgrade('/'); if (!$ret) { echo "ERROR\n"; return; } echo $cli->recv()->data; $cli->push('hello server'); Assert::false($cli->recv(.1)); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); $cli->errCode = 0; Assert::false($cli->recv(.1)); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $ws->set(array( 'log_file' => '/dev/null' )); $ws->on('WorkerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $ws->on('open', function ($serv, Swoole\Http\Request $request) { $serv->push($request->fd, "start\n"); }); $ws->on('message', function ($serv, $frame) { }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- start ================================================ FILE: tests/swoole_http_client_coro/websocket/upgrade_after_get.phpt ================================================ --TEST-- swoole_http_client_coro/websocket: client & server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort(), false); $client->setHeaders([ "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); Assert::assert($client->get('/')); echo $client->getBody(); Assert::assert($client->upgrade('/')); echo $client->recv(2)->data; $client->push("hello"); echo $client->recv(2)->data; $client->close(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $ws->set(array( 'log_file' => '/dev/null' )); $ws->on('WorkerStart', function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $ws->on('open', function ($serv, Swoole\Http\Request $request) { $serv->push($request->fd, "msg 1\n"); }); $ws->on('message', function ($serv, $frame) { co::sleep(0.1); $serv->push($frame->fd, "msg 2\n"); }); $ws->on('request', function ($req, $resp) { $resp->end("OK\n"); }); $ws->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK msg 1 msg 2 ================================================ FILE: tests/swoole_http_client_coro/write_func_1.phpt ================================================ --TEST-- swoole_http_client_coro: write func 1 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $chunks) { Co\run(function () use ($pm, $chunks) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $index = 0; $cli->set(['write_func' => function ($client, $data) use ($chunks, &$index) { Assert::eq($chunks[$index], $data); $index++; }]); Assert::assert($cli->get('/')); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $chunks) { Co\run(function () use ($pm, $chunks) { Event::defer(function () use ($pm) { $pm->wakeup(); }); $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function ($req, $resp) use ($server, $chunks) { foreach ($chunks as $chunk) { $resp->write($chunk); usleep(mt_rand(10, 50) * 1000); } }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_client_coro/write_func_2.phpt ================================================ --TEST-- swoole_http_client_coro: write func 1 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $chunks) { Co\run(function () use ($pm, $chunks) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $index = 0; $cli->set(['write_func' => function ($client, $data) use ($chunks, &$index) { Assert::eq($chunks[$index], $data); $index++; if ($index == N / 2) { // reset connection $client->close(); } }]); Assert::false($cli->get('/')); Assert::eq($cli->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $chunks) { Co\run(function () use ($pm, $chunks) { Event::defer(function () use ($pm) { $pm->wakeup(); }); $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function ($req, $resp) use ($server, $chunks) { foreach ($chunks as $chunk) { $resp->write($chunk); usleep(mt_rand(10, 50) * 1000); } $resp->end(); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/0.phpt ================================================ --TEST-- swoole_http_server: basic functions --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $html) { go(function () use ($pm, $html) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::same($data, $html); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $html) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($html) { Assert::true($resp->isWritable()); $resp->end($html); Assert::false($resp->isWritable()); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/100-continue.phpt ================================================ --TEST-- swoole_http_server: 100-continue --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect: 100-continue']); $file = TEST_IMAGE; $post_data = array('test' => str_repeat('a', 80)); if (function_exists("curl_file_create")) { $cfile = curl_file_create($file); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . $file; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); Assert::assert(!empty($res)); Assert::same($res, md5_file($file)); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(md5_file($request->files['file']['tmp_name'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/If_Modified_Since.phpt ================================================ --TEST-- swoole_http_server: If-Modified-Since --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data2 = file_get_contents(TEST_IMAGE); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg"); $lastModified = $response['headers']['last-modified']; Assert::same($response['statusCode'], 200); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['headers' => ['-If-Modified-Since' => 'aaaa', 'If-Modified-Since' => $lastModified]]); Assert::same($response['statusCode'], 304); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', 'static_handler_locations' => ['/static', '/'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/accept_encoding.phpt ================================================ --TEST-- swoole_http_server: accept encoding type --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () use ($pm) { $url = "http://127.0.0.1:".$pm->getFreePort(); curl_request('br', $url); curl_request('gzip', $url); curl_request('deflate', $url); curl_request('zstd', $url); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'http_compression' => true, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/buffer_output_size.phpt ================================================ --TEST-- swoole_http_server: buffer output size --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $response = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}", ['timeout' => 0.1]); Assert::same(strrpos($response, RANDOM_CHAR) + 1, OUTPUT_BUFFER_SIZE - HTTP_HEADER_SIZE); Assert::throws(function () use ($pm) { $response = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/full", ['timeout' => 0.1]); }, Exception::class); echo file_get_contents(TEST_LOG_FILE); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { @unlink(TEST_LOG_FILE); $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => TEST_LOG_FILE, 'http_compression' => false, 'output_buffer_size' => OUTPUT_BUFFER_SIZE, ]); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { $length = $request->server['request_uri'] === '/full' ? OUTPUT_BUFFER_SIZE + 4096 : OUTPUT_BUFFER_SIZE - HTTP_HEADER_SIZE; $response->end(str_repeat(RANDOM_CHAR, $length)); }); $server->start(); @unlink(TEST_LOG_FILE); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] WARNING ProcessFactory::finish() (ERRNO %d): The length of data [%d] exceeds the output buffer size[%d], please use the sendfile, chunked transfer mode or adjust the output_buffer_size DONE ================================================ FILE: tests/swoole_http_server/bug_2368.phpt ================================================ --TEST-- swoole_http_server: bug Github#2368 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::assert($cli->set_cookie_headers === [ 'name=' . urlencode(COOKIE), ] ); }); Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(array( 'log_file' => '/dev/null', )); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->cookie('name', COOKIE); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/bug_2444.phpt ================================================ --TEST-- swoole_http_server: bug #2444 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test") . PHP_EOL; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null', 'hook_flags' => SWOOLE_HOOK_ALL]); $server->on('start', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { switch ($request->server['request_uri']) { case '/test': $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->get('/'); if (!Assert::assert($cli->statusCode === 200)) { _error: $response->status(500); $response->end('ERROR'); return; } $cli->close(); $db = new mysqli(); $db->set_opt(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1); if (!Assert::assert($db->connect(MYSQL_SERVER_HOST, MYSQL_SERVER_USER, MYSQL_SERVER_PWD, MYSQL_SERVER_DB, MYSQL_SERVER_PORT))) { goto _error; } if (!Assert::assert($db->query('select 1')->fetch_all()[0][0] === 1)) { goto _error; } $db->close(); break; } $response->end('OK'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/bug_2608.phpt ================================================ --TEST-- swoole_http_server: bug #2608 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Coroutine\run(function () use ($pm, $http2) { $data = httpGetBody( "http://127.0.0.1:{$pm->getFreePort()}/examples/test.jpg", ['http2' => $http2] ); Assert::assert(!empty($data)); Assert::assert(md5($data) === md5_file(TEST_IMAGE)); $data = httpGetBody( "http://127.0.0.1:{$pm->getFreePort()}/../../examples/test.jpg", ['http2' => $http2] ); Assert::same($data, "hello world"); }); } $pm->kill(); unlink(__DIR__ . '/examples'); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => __DIR__, 'static_handler_locations' => ["/examples",] ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { if (!file_exists(__DIR__ . '/examples')) { symlink(dirname(dirname(__DIR__)) . '/examples/', __DIR__ . '/examples'); } $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/bug_2751.phpt ================================================ --TEST-- swoole_http_server: bug 2751 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { echo httpGetStatusCode("http://127.0.0.1:{$pm->getFreePort()}/test™") . PHP_EOL; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on(Constant::EVENT_WORKER_START, function () use ($pm) { $pm->wakeup(); }); $http->on(Constant::EVENT_REQUEST, function (Swoole\Http\Request $request, Swoole\Http\Response $response) { var_dump('never here'); $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 400 ================================================ FILE: tests/swoole_http_server/bug_2786.phpt ================================================ --TEST-- swoole_http_server: Bug Github#2786 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:" . $pm->getFreePort() . '/'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "[]"); curl_setopt($ch, CURLOPT_POST, 1); $headers = []; $headers[] = 'Transfer-Encoding: chunked'; $headers[] = 'Content-Type: application/json;charset=UTF-8'; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $result = curl_exec($ch); curl_close($ch); echo $result . PHP_EOL; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { Assert::same($request->server['request_method'], 'POST'); $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/bug_2947.phpt ================================================ --TEST-- swoole_http_server: Bug Github#2947 --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $headers = httpGetHeaders( "http://127.0.0.1:{$pm->getFreePort()}", [ 'headers' => ['Accept-Encoding' => 'gzip, br'], 'data' => $pm->getRandomData() ] ); $encoding = $headers['content-encoding'] ?? ''; if (defined('SWOOLE_HAVE_BROTLI')) { Assert::same($encoding, 'br'); } elseif (defined('SWOOLE_HAVE_ZLIB')) { Assert::same($encoding, 'gzip'); } if (defined('SWOOLE_HAVE_COMPRESSION')) { phpt_var_dump($encoding); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { Assert::same($request->server['request_method'], 'POST'); Assert::same($request->rawContent(), $pm->getRandomData()); $response->end(str_repeat('OK', 16)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/bug_2988.phpt ================================================ --TEST-- swoole_http_server: bug Github#2988 --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (Assert::true($client->connect('127.0.0.1', $pm->getFreePort()))) { if (Assert::eq($client->sendAll(ILLEGAL_REQUEST), strlen(ILLEGAL_REQUEST))) { $response = $client->recv(); phpt_var_dump($response); Assert::contains($response, $pm->getRandomData()); } } }); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->end($pm->getRandomData()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/bug_4261.phpt ================================================ --TEST-- swoole_http_server: bug Github#4261 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $uuid = urlencode(uniqid('swoole')); $port = $pm->getFreePort(); $out = shell_exec("curl -sS --location --request POST 'http://127.0.0.1:{$port}' -H 'Content-Type:multipart/form-data;charset=UTF-8' --form 'token=$uuid'"); Assert::contains($out, $uuid); $pm->kill(); echo "SUCCESS\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->end(var_export($request->post, true)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/bug_4857.phpt ================================================ --TEST-- swoole_http_server: bug Github#4857 Invalid "Transfer-Encoding: chunked" header appended --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { // without special content-length $headers = httpGetHeaders( "http://127.0.0.1:{$pm->getFreePort()}?encoding=1", [ 'headers' => ['Accept-Encoding' => 'gzip, br'], ] ); var_dump($headers); // without content-length $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); var_dump($headers); // with content-length $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}?normal=1"); var_dump($headers); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $data = '宛如繁星般,宛如皎月般'; if (isset($request->get['normal'])) { $response->header('Content-Length', mb_strlen($data)); $response->end($data); } elseif (isset($request->get['encoding'])) { $response->header('Content-Length', 1000); $response->end($data); } else { $response->header('Content-Length', 100); $response->write($data); $response->end(); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s array(6) { ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) %s ["connection"]=> string(10) "keep-alive" ["content-type"]=> string(9) "text/html" ["content-encoding"]=> string(%d) %s ["content-length"]=> string(%d) %s } %s array(5) { ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) %s ["connection"]=> string(10) "keep-alive" ["content-type"]=> string(9) "text/html" ["transfer-encoding"]=> string(7) "chunked" } %s array(6) { ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) %s ["connection"]=> string(10) "keep-alive" ["content-type"]=> string(9) "text/html" ["content-encoding"]=> string(%d) %s ["content-length"]=> string(%d) %s } DONE ================================================ FILE: tests/swoole_http_server/bug_5107.phpt ================================================ --TEST-- swoole_http_server: bug Github#5107 Error response status --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); var_dump($headers); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->status(200, "status"); $response->end("Hello World"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(5) { ["server"]=> string(18) "swoole-http-server" ["date"]=> string(%d) %s ["connection"]=> string(10) "keep-alive" ["content-type"]=> string(9) "text/html" ["content-length"]=> string(2) "11" } DONE ================================================ FILE: tests/swoole_http_server/bug_5114.phpt ================================================ --TEST-- swoole_http_server: bug #5114 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/UPPER.TXT"); Assert::same($response['statusCode'], 200); Assert::same($response['headers']['content-type'], 'text/plain'); Assert::same($response['body'], "HELLO WORLD!\n"); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/test.txt"); Assert::same($response['statusCode'], 200); Assert::same($response['headers']['content-type'], 'text/plain'); Assert::same($response['body'], "hello world!\n"); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', 'static_handler_locations' => ['/static', '/'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/bug_5146.phpt ================================================ --TEST-- swoole_http_server: Github#5146 HTTP服务器,添加响应cookie时,如果设置了过期时间会内存泄漏 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $previous = memory_get_usage(); $response->cookie( 'test_cookie', 'hello', time() + (24 * 60 * 60) ); global $previous; global $item; $current = memory_get_usage(); $stats = [ 'id' => $http->getWorkerId(), 'item' => $item++, 'prev_mem' => $previous, 'curr_mem' => $current, 'diff_mem' => $current - $previous, ]; $previous = $current; echo json_encode($stats), PHP_EOL; $response->end('test response'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- {"id":%d,"item":null,"prev_mem":null,"curr_mem":%d,"diff_mem":%d} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":%d} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} DONE ================================================ FILE: tests/swoole_http_server/bug_5186.phpt ================================================ --TEST-- swoole_http_server: GitHub issue #5186 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $content = "POST / HTTP/1.1\r\nHost: 127.0.0.1:{$pm->getFreePort()}\r\nConnection: keep-alive\r\nContent-Length: 44\r\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryOldDnwBESVoBBtI5\r\nAccept-Encoding: gzip, deflate\r\n\r\n------WebKitFormBoundaryOldDnwBESVoBBtI5--\r\n\r\n"; $client = new Client(SWOOLE_SOCK_TCP); $client->connect('127.0.0.1', $pm->getFreePort(), 0.5); $client->send($content); $client->close(); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { var_dump($request->files); $response->end('Hello World'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE NULL ================================================ FILE: tests/swoole_http_server/bug_6007.phpt ================================================ --TEST-- swoole_http_server: Github bug #6007 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->get('/'); Assert::assert($cli->set_cookie_headers === [ 'userId=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; domain=my.web.site; HttpOnly; Partitioned', ] ); }); Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $cookie = new Swoole\Http\Cookie(); $cookie->withName('userId') ->withValue('') // <-- ->withExpires(time() - 84600) ->withPath('/') ->withDomain('my.web.site') ->withHttpOnly(true) ->withPartitioned(true); $response->setCookie($cookie); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/bug_compression_level.phpt ================================================ --TEST-- swoole_http_server: bug http_compression_level not work --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (Assert::true($client->connect('127.0.0.1', $pm->getFreePort()))) { if (Assert::eq($client->sendAll(HTTP_GET_REQUEST), strlen(HTTP_GET_REQUEST))) { $response = $client->recv(); if (Assert::greaterThan(preg_match('/Content-Length: (\d+)/', $response, $match), 0)) { global $contentLengthArray; $contentLengthArray[] = intval($match[1]); } } } $client->close(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $level, $randomBytes) { phpt_var_dump($level); $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', 'http_compression' => true, 'http_compression_level' => $level ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http, $randomBytes) { $response->end($randomBytes); }); $http->start(); }; $pm->childFirst(); $pm->run(); } $sortedContentLengthArray = $contentLengthArray; rsort($sortedContentLengthArray); phpt_var_dump($contentLengthArray); phpt_var_dump($sortedContentLengthArray); if (!Assert::same($sortedContentLengthArray, $contentLengthArray)) { var_dump($contentLengthArray); var_dump($sortedContentLengthArray); } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/bug_get_request_data_after_end.phpt ================================================ --TEST-- swoole_http_server: bug get request data after response end --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/", ['data' => $pm->getRandomData()]) . PHP_EOL; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); switch_process(); Assert::notEmpty($request->rawContent()); Assert::notEmpty($request->getData()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/callback_new_obj_method.phpt ================================================ --TEST-- swoole_http_server: http server callback use new object method --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); for ($i = MAX_REQUESTS_LOW; $i--;) { Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::same($cli->body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { class TestCo { public function foo(Swoole\Http\Request $request, Swoole\Http\Response $response) { co::sleep(0.001); $cid = go(function () use ($response) { co::yield(); $response->end('Hello Swoole!'); }); co::resume($cid); echo @$this->test; } } $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', [new TestCo, 'foo']); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/callback_new_static_method.phpt ================================================ --TEST-- swoole_http_server: http server callback use static method --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); for ($i = MAX_REQUESTS_LOW; $i--;) { Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::same($cli->body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { class TestCo { private static $test = ''; public static function foo(Swoole\Http\Request $request, Swoole\Http\Response $response) { co::sleep(0.001); $cid = go(function () use ($response) { co::yield(); $response->end('Hello Swoole!'); }); co::resume($cid); echo self::$test; } } $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', [TestCo::class, 'foo']); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/callback_string.phpt ================================================ --TEST-- swoole_http_server: http server callback use function by string --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); for ($i = MAX_REQUESTS_LOW; $i--;) { Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::same($cli->body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { function foo(Swoole\Http\Request $request, Swoole\Http\Response $response) { static $test = ''; co::sleep(0.001); $cid = go(function () use ($response) { co::yield(); $response->end('Hello Swoole!'); }); co::resume($cid); echo $test; } $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', 'foo'); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/callback_with_internal_function.phpt ================================================ --TEST-- swoole_http_server: http server callback use new object method --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', 'var_dump'); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- object(Swoole\Http\Request)#%d (%d) { ["fd"]=> int(1) %A"header"]=> array(3) { ["host"]=> string(%d) "%s" ["connection"]=> string(10) "keep-alive" ["accept-encoding"]=> string(%d) "%s" } ["server"]=> array(11) { ["request_method"]=> string(3) "GET" ["request_uri"]=> string(1) "/" ["path_info"]=> string(1) "/" ["request_time"]=> int(%d) ["request_time_float"]=> float(%f) ["server_protocol"]=> string(8) "HTTP/1.1" ["server_port"]=> int(%d) ["remote_port"]=> int(%d) ["server_addr"]=> string(9) "127.0.0.1" ["remote_addr"]=> string(9) "127.0.0.1" ["master_time"]=> int(%d) } ["cookie"]=> NULL ["get"]=> NULL ["files"]=> NULL ["post"]=> NULL ["tmpfiles"]=> NULL } object(Swoole\Http\Response)#%d (%d) { ["fd"]=> int(1) ["socket"]=> NULL ["header"]=> NULL ["cookie"]=> NULL ["trailer"]=> NULL } ================================================ FILE: tests/swoole_http_server/callback_with_private.phpt ================================================ --TEST-- swoole_http_server: http server with private callback --SKIPIF-- --FILE-- setWaitTimeout(0); $pm->parentFunc = function () { }; $pm->childFunc = function () use ($pm) { class TestCo_9 { private function foo(Swoole\Http\Request $request, Swoole\Http\Response $response) { co::sleep(0.001); $cid = go(function () use ($response) { co::yield(); $response->end('Hello Swoole!'); }); co::resume($cid); echo @$this->test; } } $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', [new TestCo_9, 'foo']); $http->start(); }; $pm->childFirst(); $pm->run(true); //Fatal Error $pm->expectExitCode(255); $output = $pm->getChildOutput(); Assert::contains($output, "Swoole\Server\Port::on(): function 'TestCo_9::foo' is not callable"); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/callback_with_protected.phpt ================================================ --TEST-- swoole_http_server: http server with protected callback --SKIPIF-- --FILE-- end('Hello Swoole!'); }); co::resume($cid); echo @$this->test; } } $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('request', [new TestCo, 'foo']); $http->start(); }); //Fatal Error $pm->expectExitCode(255); $output = $pm->getChildOutput(); Assert::contains($output, 'Swoole\Server\Port::on(): function \'TestCo::foo\' is not callable'); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/chunk.phpt ================================================ --TEST-- swoole_http_server: http chunk --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(!empty($data)); Assert::eq(md5($data), md5_file(TEST_IMAGE)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ //'log_file' => '/dev/null', ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $data = str_split(file_get_contents(TEST_IMAGE), 8192); foreach ($data as $chunk) { Assert::true($response->write($chunk)); } Assert::true($response->end()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/chunk_with_end_data.phpt ================================================ --TEST-- swoole_http_server: send data in the end method with chunked encoding --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(!empty($data)); Assert::eq(md5($data), md5_file(TEST_IMAGE)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ //'log_file' => '/dev/null', ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $data = str_split(file_get_contents(TEST_IMAGE), 8192); foreach ($data as $k => $chunk) { if ($k == count($data) - 1) { break; } $response->write($chunk); } $response->end($data[count($data) - 1]); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/chunked_pipeline_request.phpt ================================================ --TEST-- swoole_http_server: chunked and pipeline request --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { chunked_request($pm); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', // 'log_level' => SWOOLE_LOG_DEBUG, // 'trace_flags' => SWOOLE_TRACE_ALL, 'http_compression' => false, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->end($request->rawContent() . EOF); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/client_ca.phpt ================================================ --TEST-- swoole_http_server: ssl client ca --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $html) { go(function () use ($pm, $html) { $commnd = "curl https://127.0.0.1:" . $pm->getFreePort() . " --cert " . SSL_FILE_DIR. "/client.crt" . " --key ". SSL_FILE_DIR. "/client.key -k -vvv --stderr /tmp/client_ca.txt"; $out = shell_exec($commnd); Assert::eq($out, $html); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $html) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', 'ssl_verify_peer' => true, 'ssl_verify_depth' => 10, 'ssl_cafile' => SSL_FILE_DIR . '/ca.crt', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($html) { $resp->end($html); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/client_compress.phpt ================================================ --TEST-- swoole_http_server: compress response by http client --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpRequest("http://127.0.0.1:{$pm->getFreePort()}", ['headers' => ['accept-encoding' => 'br']]); $headers = $data['headers']; Assert::assert(!empty($headers)); Assert::eq($headers['content-encoding'], 'gzip'); Assert::eq($data['body'], file_get_contents(TEST_IMAGE)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, $response) { $gzipEncoded = gzencode(file_get_contents(TEST_IMAGE), 9, FORCE_GZIP); $response->setHeader('Content-Encoding', 'gzip'); $response->end($gzipEncoded); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/co_switching.phpt ================================================ --TEST-- swoole_http_server: co switching --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($i = MAX_CONCURRENCY; $i--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); for ($i = MAX_REQUESTS; $i--;) { Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::same($cli->body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'worker_num' => swoole_cpu_num() ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { go(function () { for ($i = 5; $i--;) { co::sleep(0.001); } }); $response->end('Hello Swoole!'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/compression.phpt ================================================ --TEST-- swoole_http_server: http_compression --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(md5_file(__DIR__ . '/../../README.md') == md5($data)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'http_gzip_level' => 9, 'http_compression' => true, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/compression_min_length.phpt ================================================ --TEST-- swoole_http_server: compression_min_length --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['Accept-Encoding' => 'gzip', ]); $cli->get('/?bytes=128'); Assert::eq($cli->getHeaders()['content-encoding'], 'gzip'); $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['Accept-Encoding' => 'gzip', ]); $cli->get('/?bytes=127'); Assert::assert(!isset($cli->getHeaders()['content-encoding'])); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set(['compression_min_length' => 128,]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(str_repeat('A', $request->get['bytes'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/compression_types.phpt ================================================ --TEST-- swoole_http_server: compression types --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { http_compression_types_test($pm); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set([ 'http_compression_types' => [ 'text/html', 'application/json' ], ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { if ($request->server['request_uri'] == '/html') { $response->end(str_repeat('A', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/json') { $response->setHeader('Content-Type', 'application/json'); $response->end(str_repeat('B', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/raw') { $response->setHeader('Content-Type', 'text/raw'); $response->end(str_repeat('C', $request->get['bytes'])); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/cookieAlias.phpt ================================================ --TEST-- swoole_http_server: cookie alias --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\Run(function () use ($pm) { var_dump(httpRequest("http://127.0.0.1:{$pm->getFreePort()}")['set_cookie_headers']); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cookie = new Swoole\Http\Cookie(); $cookie->withName('key1') ->withValue('val1') ->withExpires(time() + 84600) ->withPath('/') ->withDomain('id.test.com') ->withSecure(true) ->withHttpOnly(true) ->withSameSite('None') ->withPriority('High') ->withPartitioned(true); $response->setCookie($cookie); $response->setCookie('key1', 'val1', time() + 84600, '/', 'id.test.com', true, true, 'None', 'High', true); $response->setRawCookie('key1', 'val1', time() + 84600, '/', 'id.test.com', true, true, 'None', 'High', true); $cookie->withValue(''); $response->setCookie($cookie); $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(4) { [0]=> string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" [1]=> string(152) "key1=val1; expires=%s; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" [2]=> string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" [3]=> string(151) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" } ================================================ FILE: tests/swoole_http_server/cookie_delete.phpt ================================================ --TEST-- swoole_http_server: delete cookie (same behavior with php-fpm) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cookie = '123_,; abc'; Assert::assert($cli->get('/?cookie=' . urlencode($cookie))); Assert::same($cli->statusCode, 200); Assert::assert($cli->set_cookie_headers === [ 'cookie1=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie2=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie3=cookie3', 'cookie4=cookie4', 'cookie5=cookie5; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie6=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie7=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie8=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', ] ); }); Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->cookie('cookie1', null); $response->cookie('cookie2', ''); $response->cookie('cookie3', 'cookie3', 0); // must be > 0 $response->cookie('cookie4', 'cookie4', -1); // must be > 0 $response->cookie('cookie5', 'cookie5', 1); $response->cookie('cookie6', null, 0); $response->cookie('cookie7', null, -1); $response->cookie('cookie8', null, 1); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/cookie_samesite.phpt ================================================ --TEST-- swoole_http_server: cookie with samesite --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->get('/'); Assert::assert($cli->set_cookie_headers === [ 'a=123; SameSite=Lax', ] ); }); Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->cookie('a', '123', 0, '', '', false, false, 'Lax'); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/cookie_vs_rawcookie.phpt ================================================ --TEST-- swoole_http_server: cookie vs rawcookie --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cookie = '123_,; abc'; $cookie_encoded = urlencode($cookie); Assert::assert($cli->get('/?cookie=' . $cookie_encoded)); Assert::same($cli->statusCode, 200); Assert::eq($cli->set_cookie_headers, [ 'cookie=' . $cookie_encoded, 'rawcookie=' . $cookie_encoded, ]); }); for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $random = get_safe_random(); Assert::assert($cli->get('/?cookie=' . $random)); Assert::same($cli->statusCode, 200); Assert::assert($cli->set_cookie_headers === [ 'cookie=' . urlencode($random), 'rawcookie=' . $random ] ); }); } Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $cookie = $request->get['cookie']; $response->cookie('cookie', $cookie); $response->rawcookie('rawcookie', urlencode($cookie)); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- SUCCESS ================================================ FILE: tests/swoole_http_server/cookies.phpt ================================================ --TEST-- swoole_http_server: cookies --SKIPIF-- --FILE-- 'RSU8HYED', '8MLP_5753_lastvisit' => '1426120671', 'attentiondomain' => '2z.cn,chinaz.com,kuaishang.cn,cxpcms.com', '8MLP_5753_security_cookiereport' => 'c014Hgufskpv55xgM9UaB/ZZdMrcN0QqBYdcGomTu8OlTDWzTA0z', '8MLP_5753_ulastactivity' => 'e4a1aRIbgdzoRDd8NlT5CMIwLnWjyjr2hWyfn6T5g82RitUOdf3o', 'mytool_user' => 'uSHVgCUFWf5Sv2Y8tKytQRUJW3wMVT3rw5xQLNGQFIsod4C6vYWeGA==', 'PHPSESSID' => 't3hp9h4o8rb3956t5pajnsfab1', '8MLP_5753_st_p' => '1024432|1428040399|f7599ba9053aa27e12e9e597a4c372ce', '8MLP_5753_st_t' => '1024432|1428040402|46d40e02d899b10b431822eb1d39f6a1', '8MLP_5753_forum_lastvisit' => 'D_140_1427103032D_165_1427427405D_168_1427870172D_167_1427870173D_166_1428021390D_163_1428040402', '8MLP_5753_sid' => 'k25gxK', 'cmstop_page-view-mode' => 'view', 'cmstop_rememberusername' => 'error', 'cmstop_auth' => 'Jcn2qzVn9nsjqtodER9OphcW3PURDWNx6mO7j0Zbb9k=', 'cmstop_username' => 'error', 'Hm_lvt_aecc9715b0f5d5f7f34fba48a3c511d6' => '1427967317,1428021376,1428036617,1428040224', 'Hm_lpvt_aecc9715b0f5d5f7f34fba48a3c511d6' => '1428050417', 'YjVmNm_timeout' => '0', ); $pm = new ProcessManager; $pm->parentFunc = function ($pid) use ($pm, $cookies) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 1)) { exit("connect failed. Error: {$client->errCode}\n"); } $header = "GET /index.php HTTP/1.1\r\n"; $header .= "Host: 127.0.0.1\r\n"; $header .= "Connection: keep-alive\r\n"; $header .= "Cache-Control: max-age=0\r\n"; $cookieStr = ''; foreach($cookies as $k => $v) { $cookieStr .= "$k=$v; "; } $cookieStr .= "end=1"; $cookies['end'] = "1"; $header .= "Cookie: $cookieStr\r\n"; $header .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"; $header .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36\r\n"; $header .= "\r\n"; $_sendStr = $header; $client->send($_sendStr); $data = $client->recv(); $client->close(); list(, $_respCookieStr) = explode("\r\n\r\n", $data); $respCookie = json_decode($_respCookieStr, true); Assert::same($respCookie, $cookies); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(json_encode($request->cookie)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/cookies_parse.phpt ================================================ --TEST-- swoole_http_server: cookies parse --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort(), false); $client->setHeaders([ 'Cookie' => 'PHPSESSID=5359a08f4ddbf825f0e99a3393e5dc9e; HttpOnly; q=URVVma5UgEDm9RmQvBfXs7rCEG9hs9td9CXXmBRQ' ]); $client->get('/'); $client->close(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->on("Start", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($request, $response) use ($serv){ var_dump($request->cookie); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(3) { ["PHPSESSID"]=> string(32) "5359a08f4ddbf825f0e99a3393e5dc9e" ["HttpOnly"]=> string(0) "" ["q"]=> string(40) "URVVma5UgEDm9RmQvBfXs7rCEG9hs9td9CXXmBRQ" } ================================================ FILE: tests/swoole_http_server/create_request.phpt ================================================ --TEST-- swoole_http_server: parse request --SKIPIF-- --FILE-- header, 0); Assert::false($req->isCompleted()); $data1 = substr($data, 0, rand(100, 600)); $data2 = substr($data, strlen($data1)); Assert::eq($req->parse($data1), strlen($data1)); Assert::false($req->isCompleted()); Assert::eq($req->parse($data2), strlen($data2)); Assert::false($req->isCompleted()); Assert::eq($req->parse("\r\n"), 2); Assert::true($req->isCompleted()); Assert::false($req->parse('error data')); Assert::eq("GET", $req->getMethod()); Assert::greaterThan(count($req->header), 4); Assert::eq(count($req->cookie), 3); Assert::eq($req->getData(), $data."\r\n"); $req2 = Request::create(['parse_cookie' => false]); Assert::eq($req2->parse($data . "\r\n"), strlen($data) + 2); Assert::null($req2->cookie); $data = "POST /index.html?hello=world&test=2123 HTTP/1.1\r\n"; $data .= "Host: 127.0.0.1\r\n"; $data .= "Connection: keep-alive\r\n"; $data .= "Pragma: no-cache\r\n"; $data .= "Cache-Control: no-cache\r\n"; $data .= "Upgrade-Insecure-Requests: \r\n"; $data .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36\r\n"; $data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"; $data .= "Accept-Encoding: gzip, deflate, br\r\n"; $data .= "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ja;q=0.6\r\n"; $data .= "Cookie: env=pretest; phpsessid=fcccs2af8673a2f343a61a96551c8523d79ea; username=hantianfeng\r\n"; $req3 = Request::create(); $req3->parse($data); Assert::eq("POST", $req3->getMethod()); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/data_parse.phpt ================================================ --TEST-- swoole_http_server: http server data parse test --SKIPIF-- --FILE-- $value) { $value = (string)($value); $ret .= "--{$boundary}\r\nContent-Disposition: form-data; name=\"{$name}\"\r\n\r\n{$value}\r\n"; } $ret .= "--{$boundary}--\r\n"; return $ret; } function sendData(string $host, int $port, array $get, array $post): string { $client = new Co\Client(SWOOLE_SOCK_TCP); if (!$client->connect($host, $port, 1)) { exit("connect failed. Error: {$client->errCode}\n"); } $get = http_build_query($get); $content_type = ''; switch (mt_rand(0, 1)) { case 0: { $content_type = 'application/x-www-form-urlencoded'; $post = http_build_query($post); break; } case 1: { $boundary = '++++' . md5(get_safe_random(16)); $content_type = "multipart/form-data; boundary={$boundary}"; $post = arrayToMultipartString($post, $boundary); break; } case 2: { $content_type = 'application/json'; $post = json_encode($post); break; } } $content_length = strlen($post); $CR = "\r"; $data = <<send($data)); $data = ''; while ($tmp = $client->recv()) { $data .= $tmp; } return $data; } $pm = new ProcessManager; $pm->parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm) { $get = getRandomData(50); $post = getRandomData(100); $ret = sendData('127.0.0.1', $pm->getFreePort(), $get, $post); list($_, $body) = explode("\r\n\r\n", $ret); Assert::same($body, var_dump_return($get, $post)); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { $response->end(var_dump_return($request->get, $request->post)); $http->close($request->fd); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/disable_compression.phpt ================================================ --TEST-- swoole_http_server: disable compression --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->get('/')); Assert::eq(md5_file(__DIR__ . '/../../README.md'), md5($client->getBody())); Assert::keyNotExists($client->headers, 'content-encoding'); $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { // Set Content-Encoding header to empty to disable compression $response->header('Content-Encoding', ''); $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/disable_coroutine.phpt ================================================ --TEST-- swoole_http_server: disable coroutine and use go --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { for ($n = 0; $n > MAX_REQUESTS; $n++) { Assert::assert(httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") == $n); } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'enable_coroutine' => false, // close build-in coroutine ]); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { go(function () use ($response) { co::sleep(0.001); $cid = go(function () use ($response) { co::yield(); $response->end(Co::getuid()); }); co::resume($cid); }); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/dispatch_mode_7.phpt ================================================ --TEST-- swoole_http_server: dispatch_mode=7 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::eq($data, "hello"); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("0.0.0.0", $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'reactor_num' => 2, 'worker_num' => 2, 'dispatch_mode' => 7, 'reload_async' => true, 'log_level' => SWOOLE_LOG_WARNING ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function ($request, Swoole\Http\Response $response) use ($http) { $response->write("hello"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/duplicate_header.phpt ================================================ --TEST-- swoole_http_server: duplicate header --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Test-Header1: value1', 'X-Test-Header2: value2', 'X-Test-Header2: value3', 'X-Test-Header3: value4', 'X-Test-Header3: value5', 'X-Test-Header3: value6', ]); curl_setopt($ch, CURLOPT_HEADER, true); echo curl_exec($ch); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $msg = "hello world"; Assert::eq($request->header['x-test-header1'], 'value1'); Assert::eq($request->header['x-test-header2'], ['value2', 'value3']); Assert::eq($request->header['x-test-header3'], ['value4', 'value5', 'value6']); $response->header("content-length", strlen($msg) . " "); $response->header("Test-Value", [ "a\r\n", "b1234 ", "d5678", "e \n ", null, 5678, 3.1415926, ]); $response->end($msg); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- HTTP/1.1 200 OK Content-Length: 11 Test-Value: a Test-Value: b1234 Test-Value: d5678 Test-Value: e Test-Value: 5678 Test-Value: 3.1415926 Server: swoole-http-server Date: %s Connection: keep-alive Content-Type: text/html hello world ================================================ FILE: tests/swoole_http_server/enable_coroutine.phpt ================================================ --TEST-- swoole_http_server: enable_coroutine setting in server --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . "\n"; echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/co") . "\n"; echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/co") . "\n"; echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/co") . "\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'enable_coroutine' => false, // close build-in coroutine 'worker_num' => 1, 'log_level' => SWOOLE_LOG_NONE, ]); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header("Content-Type", "text/plain"); if ($request->server['request_uri'] == '/co') { go(function () use ($response) { $response->end(Co::getuid()); }); } else { $response->end(Co::getuid()); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- -1 1 2 3 ================================================ FILE: tests/swoole_http_server/error_1203.phpt ================================================ --TEST-- swoole_http_server: http_compression --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { try { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); } catch(Exception $e) { Assert::contains($e->getMessage(), 'Connection reset by peer'); } $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'http_compression' => false, 'log_file' => '/dev/null', 'buffer_output_size' => 128 * 1024, ]); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { Assert::eq($response->end(str_repeat('A', 256 * 1024)), false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_DATA_LENGTH_TOO_LARGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/error_413.phpt ================================================ --TEST-- swoole_http_server: 413 error --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:" . $pm->getFreePort() . '/'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); $post_data = ['data' => str_repeat('A', 65536)]; curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); $result = curl_exec($ch); Assert::isEmpty($result); $info = curl_getinfo($ch); Assert::eq($info['http_code'], 413); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set(['package_max_length' => 8192, 'log_file' => '/dev/null']); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on('request', function ($req, Swoole\Http\Response $resp) { $resp->end('hello'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/event_stream.phpt ================================================ --TEST-- swoole_http_server: event stream --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $data) { Co\run(function () use ($pm, $data) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $index = 0; $buffer = ''; $client->set([ 'timeout' => 60, 'write_func' => function($client, $chunk) use (&$buffer, &$index, $data) { $buffer .= $chunk; while(true) { $position = mb_strpos($buffer, "\n\n"); if ($position === false) { break; } Assert::eq(mb_substr($buffer, 0, $position) . "\n\n", $data[$index]); $buffer = mb_substr($buffer, $position + 2); $index++; } return true; } ]); Assert::true($client->get('/')); Assert::isEmpty($client->getBody()); Assert::keyNotExists($client->headers, 'content-length'); Assert::eq($client->headers['content-type'], "text/event-stream"); $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('WorkerStart', function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on('request', function ($req, Swoole\Http\Response $resp) use ($http, $data) { $resp->header("Content-Type", "text/event-stream"); $resp->header("Cache-Control", "no-cache"); $resp->header("Connection", "keep-alive"); $resp->header("X-Accel-Buffering", "no"); for ($i = 0; $i < N; $i++) { Co::sleep(0.01); $resp->write($data[$i]); } $resp->write("data: [DONE]\n\n"); $resp->end(); Co::sleep(0.05); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/event_stream2.phpt ================================================ --TEST-- swoole_http_server: event stream 2 --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $data) { Co\run(function () use ($pm, $data) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->get('/')); Assert::isEmpty($client->getBody()); Assert::keyNotExists($client->headers, 'content-length'); Assert::eq($client->headers['content-type'], "text/event-stream"); for ($i = 0; $i < N; $i++) { Co::sleep(0.01); $line1 = $client->socket->recvLine(); $line2 = $client->socket->recvLine(); Assert::eq($line1 . $line2, $data[$i]); } $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('WorkerStart', function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on('request', function ($req, Swoole\Http\Response $resp) use ($http, $data) { $resp->header("Content-Type", "text/event-stream"); $resp->header("Cache-Control", "no-cache"); $resp->header("Connection", "keep-alive"); $resp->header("X-Accel-Buffering", "no"); $resp->header('Content-Encoding', ''); $resp->header("Content-Length", ''); $resp->end(); Co::sleep(0.05); for ($i = 0; $i < N; $i++) { Co::sleep(0.01); $http->send($resp->fd, $data[$i]); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/form_data_1.phpt ================================================ --TEST-- swoole_http_server: form data 1 --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { form_data_test($pm, [250]); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set(['log_file' => '/dev/null']); $http->on('WorkerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('Request', function (Request $request, Response $response) use ($http) { $response->end(json_encode($request->post)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/form_data_with_charset.phpt ================================================ --TEST-- swoole_http_server: http server parse form data with charset --SKIPIF-- --FILE-- $value) { $value = (string)($value); $ret .= "--{$boundary}\r\nContent-Disposition: form-data; name=\"{$name}\"\r\n\r\n{$value}\r\n"; } $ret .= "--{$boundary}--\r\n"; return $ret; } function sendData(string $host, int $port, array $get, array $post): string { $client = new Co\Client(SWOOLE_SOCK_TCP); if (!$client->connect($host, $port, 1)) { exit("connect failed. Error: {$client->errCode}\n"); } $get = http_build_query($get); $boundary = '++++' . md5(get_safe_random(16)); $content_type = "multipart/form-data; boundary={$boundary}; charset=UTF-8"; $post = arrayToMultipartString($post, $boundary); $content_length = strlen($post); $CR = "\r"; $data = <<send($data)); $data = ''; while ($ret = $client->recv()) { $data .= $ret; if (strrpos($data, EOF) !== false) { $data = substr($data, 0, strlen($data) - strlen(EOF)); break; } } return $data; } $pm = new ProcessManager; $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { foreach (range(1, 100) as $_) { $get = getRandomData(50); $post = getRandomData(100); $ret = sendData('127.0.0.1', $pm->getFreePort(), $get, $post); list($_, $body) = explode("\r\n\r\n", $ret); Assert::same($body, var_dump_return($get, $post)); } $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on('WorkerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('Request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { $response->end(var_dump_return($request->get, $request->post)); $http->send($request->fd, EOF); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/head_method.phpt ================================================ --TEST-- swoole_http_server: HEAD method --SKIPIF-- --FILE-- 'ok', 'error' => false, 'payload' => 'Hello World' ]); $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm, $data) { //request 1, HEAD $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:" . $pm->getFreePort() . '/'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD'); curl_setopt($ch, CURLOPT_NOBODY, true); $result = curl_exec($ch); Assert::isEmpty($result); $info = curl_getinfo($ch); Assert::eq(strlen($data), $info['download_content_length']); //request 2, GET $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:" . $pm->getFreePort() . '/'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); Assert::eq($data, $result); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on('request', function ($req, Swoole\Http\Response $resp) use ($data) { $resp->header('Content-Type', 'application/json'); if ($req->server['request_method'] == 'HEAD') { $resp->header('Content-Length', strlen($data)); $resp->end(); return; } $resp->end($data); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/headers_sent.phpt ================================================ --TEST-- swoole_http_server: headers sent (coroutine disabled) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); echo curl_exec($ch); echo curl_exec($ch); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { ob_start(); echo 'Test'; $output = ob_get_clean(); $response->write('buffered_output=' . $output . "\n"); $response->write('headers_sent=' . (int)headers_sent() . "\n"); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- buffered_output=Test headers_sent=0 buffered_output=Test headers_sent=0 ================================================ FILE: tests/swoole_http_server/headers_sent_coroutine.phpt ================================================ --TEST-- swoole_http_server: headers sent (coroutine enabled) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); echo curl_exec($ch); echo curl_exec($ch); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'enable_coroutine' => true, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { ob_start(); echo 'Test'; $output = ob_get_clean(); $response->write('buffered_output=' . $output . "\n"); $response->write('headers_sent=' . (int)headers_sent() . "\n"); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- buffered_output=Test headers_sent=0 buffered_output=Test headers_sent=0 ================================================ FILE: tests/swoole_http_server/http_autoindex.phpt ================================================ --TEST-- swoole_http_server: http_autoindex --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $files = scan_dir(DOCUMENT_ROOT); foreach ($files as $f) { Assert::contains($data, basename($f)); } $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir1"); $files = scan_dir(DOCUMENT_ROOT.'/dir1'); foreach ($files as $f) { Assert::contains($data, basename($f)); } $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir2"); $files = scan_dir(DOCUMENT_ROOT.'/dir2'); foreach ($files as $f) { Assert::contains($data, basename($f)); } $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'enable_static_handler' => true, 'document_root' => DOCUMENT_ROOT, 'http_autoindex' => true, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->end("dynamic request"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/http_index_files.phpt ================================================ --TEST-- swoole_http_server: http_index_files --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $index_content = file_get_contents(DOCUMENT_ROOT . '/index.html'); $dir2_index_txt_content = file_get_contents(DOCUMENT_ROOT . '/dir2/index.txt'); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::same($data, $index_content); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir1"); Assert::same($data, 'dynamic request'); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir2"); Assert::assert($data, $dir2_index_txt_content); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'enable_static_handler' => true, 'document_root' => DOCUMENT_ROOT, 'http_index_files' => ['index.html', 'index.txt'], ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { $response->end("dynamic request"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/http_index_files_autoindex.phpt ================================================ --TEST-- swoole_http_server: http_index_files_autoindex --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $index_content = file_get_contents(DOCUMENT_ROOT . '/index.html'); $dir2_index_txt_content = file_get_contents(DOCUMENT_ROOT . '/dir2/index.txt'); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::same($data, $index_content); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir1"); $files = scan_dir(DOCUMENT_ROOT.'/dir1'); foreach ($files as $f) { Assert::contains($data, basename($f)); } $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/dir2"); Assert::assert($data, $dir2_index_txt_content); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'enable_static_handler' => true, 'document_root' => DOCUMENT_ROOT, 'http_index_files' => ['index.html', 'index.txt'], 'http_autoindex' => true, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { $response->end("dynamic request"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/https.phpt ================================================ --TEST-- swoole_http_server: https --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $html) { go(function () use ($pm, $html) { $data = httpGetBody("https://127.0.0.1:{$pm->getFreePort()}/"); Assert::same($data, $html); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $html) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($html) { $resp->end($html); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/issue_2360.phpt ================================================ --TEST-- swoole_http_server: issue 2360 (Swoole\Http\Server silently fails to read requests) --SKIPIF-- --FILE-- setRandomFunc(function () { $size = mt_rand(SOCKET_BUFFER_SIZE, SOCKET_BUFFER_SIZE << 3); // 1024 ~ 65536 $data = ''; for ($i = 0; $i < $size; $i++) { $data .= sprintf('%01x', $i % 16); } return $data; }); $pm->initRandomDataEx(1, MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['socket_buffer_size' => SOCKET_BUFFER_SIZE]); for ($n = MAX_REQUESTS; $n--;) { $data = $pm->getRandomData(); Assert::true($cli->post('/', $data)); Assert::same($cli->statusCode, 200); Assert::same($cli->body, $data); phpt_echo('posting ' . strlen($data) . " bytes\n"); } $cli->close(); }); Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'socket_buffer_size' => SOCKET_BUFFER_SIZE, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Request $request, Response $response) use ($pm) { phpt_echo("received {$request->header['content-length']} bytes\n"); if (Assert::assert($request->rawContent() === $pm->getRandomData())) { $response->end($request->rawContent()); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/json_encode.phpt ================================================ --TEST-- swoole_http_server: json_encode or serialize Swoole\Http\Request::class OR Swoole\Http\Response::class --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test") . PHP_EOL; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('start', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { Assert::true($request->fd > 0); Assert::true($response->fd > 0); $result = json_decode(json_encode($request), true); Assert::true($result['fd'] > 0); $result = json_decode(json_encode($response), true); Assert::true($result['fd'] > 0); $response->end('OK'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/json_encode2.phpt ================================================ --TEST-- swoole_http_server: json_encode or serialize Swoole\Http\Request::class OR Swoole\Http\Response::class --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test") . PHP_EOL; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('start', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $result = json_decode(json_encode($request), true); Assert::true($result['fd'] > 0); $result = json_decode(json_encode($response), true); Assert::true($result['fd'] > 0); Assert::true($request->fd > 0); Assert::true($response->fd > 0); $response->end('OK'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/large_url.phpt ================================================ --TEST-- swoole_http_server: large url --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 1)) { exit("connect failed. Error: {$client->errCode}\n"); } $header = "GET /home/explore/?a=" . str_repeat('A', $len = rand(1024, 2048)) . " HTTP/1.1\r\n"; $header .= "Host: 127.0.0.1\r\n"; $header .= "Connection: keep-alive\r\n"; $header .= "Cache-Control: max-age=0\r\n"; $cookie = "8MLP_5753_saltkey=RSU8HYED; 8MLP_5753_lastvisit=1426120671; pgv_pvi=1454765056; CNZZDATA1000008050=684878078-1426123263-http%253A%252F%252Fcznews-team.chinaz.com%252F%7C1426485386; attentiondomain=2z.cn%2cchinaz.com%2ckuaishang.cn%2ccxpcms.com; CNZZDATA33217=cnzz_eid%3D1036784254-1426122273-http%253A%252F%252Fcznews-team.chinaz.com%252F%26ntime%3D1427414208; CNZZDATA433095=cnzz_eid%3D1613871160-1426123273-http%253A%252F%252Fcznews-team.chinaz.com%252F%26ntime%3D1427848205; CNZZDATA1254679775=309722566-1427851758-http%253A%252F%252Fcznews-team.chinaz.com%252F%7C1427851758; 8MLP_5753_security_cookiereport=c014Hgufskpv55xgM9UaB%2FZZdMrcN0QqBYdcGomTu8OlTDWzTA0z; 8MLP_5753_ulastactivity=e4a1aRIbgdzoRDd8NlT5CMIwLnWjyjr2hWyfn6T5g82RitUOdf3o; 8MLP_5753_auth=9351LJpv7Xa%2FPUylJDQgRiAONZ5HysOaj%2BqRGb6jYmpqZpRkVc2ibPXm7LAfArC%2FpIpY2Fx%2B59AHqzr843qozZWxWNZi; mytool_user=uSHVgCUFWf5Sv2Y8tKytQRUJW3wMVT3rw5xQLNGQFIsod4C6vYWeGA==; 8MLP_5753_lip=220.160.111.22%2C1428036585; pgv_si=s4245709824; PHPSESSID=t3hp9h4o8rb3956t5pajnsfab1; 8MLP_5753_st_p=1024432%7C1428040399%7Cf7599ba9053aa27e12e9e597a4c372ce; 8MLP_5753_viewid=tid_7701248; 8MLP_5753_smile=5D1; 8MLP_5753_st_t=1024432%7C1428040402%7C46d40e02d899b10b431822eb1d39f6a1; 8MLP_5753_forum_lastvisit=D_140_1427103032D_165_1427427405D_168_1427870172D_167_1427870173D_166_1428021390D_163_1428040402; 8MLP_5753_sid=k25gxK; 8MLP_5753_lastact=1428040403%09misc.php%09patch; cmstop_page-view-mode=view; cmstop_rememberusername=error; cmstop_auth=Jcn2qzVn9nsjqtodER9OphcW3PURDWNx6mO7j0Zbb9k%3D; cmstop_userid=6; cmstop_username=error; Hm_lvt_aecc9715b0f5d5f7f34fba48a3c511d6=1427967317,1428021376,1428036617,1428040224; Hm_lpvt_aecc9715b0f5d5f7f34fba48a3c511d6=1428050417; YjVmNm_timeout=0"; $header .= "Cookie: $cookie\r\n"; $header .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"; $header .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36\r\n"; $header .= "\r\n"; $_sendStr = $header; $client->send(substr($_sendStr, 0, 512)); usleep(200000); $client->send(substr($_sendStr, 512, 512)); usleep(200000); $client->send(substr($_sendStr, 1024)); $data = $client->recv(); $client->close(); if (Assert::assert(!empty($data))) { Assert::same((int)explode("\r\n\r\n", $data)[1], $len); } $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header("Content-Type", "text/plain"); $response->end(strlen($request->get['a'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/max-age.phpt ================================================ --TEST-- swoole_http_server: cookies (max-age) --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $uri = "http://127.0.0.1:{$pm->getFreePort()}"; $cookies = httpRequest($uri)['set_cookie_headers']; var_dump(strpos($cookies[0], 'test=123456789') !== false); var_dump(strpos($cookies[0], 'expires='.date('D, d-M-Y H:i:s \G\M\T', time() + 3600)) !== false); var_dump(strpos($cookies[0], 'Max-Age=3600') !== false); var_dump(strpos($cookies[0], 'path=/') !== false); var_dump(strpos($cookies[0], 'domain=example.com') !== false); var_dump(strpos($cookies[0], 'secure') !== false); var_dump(strpos($cookies[0], 'HttpOnly') !== false); var_dump(strpos($cookies[0], 'SameSite=None') !== false); var_dump(strpos($cookies[1], 'test=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT') !== false); var_dump(strpos($cookies[1], 'Max-Age=0') !== false); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->cookie('test', '123456789', time() + 3600, '/', 'example.com', true, true, 'None'); $response->cookie('test', ''); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) DONE ================================================ FILE: tests/swoole_http_server/max_concurrency.phpt ================================================ --TEST-- swoole_http_server: max_concurrency --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $url = 'http://127.0.0.1:' . $pm->getFreePort(0) . '/index.html'; $n = SERVER_MAX_CONCURRENCY; // 200 $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($url) { $c = Swoole\Coroutine\Http\get($url); Assert::eq($c->getStatusCode(), 200); Assert::eq($c->getBody(), GREETING_MESSAGE); }); } System::sleep(0.005); // 403, high concurrency $n = SERVER_MAX_CONCURRENCY; while ($n--) { $cid_list[] = go(function () use ($url) { $c = Swoole\Coroutine\Http\get($url); Assert::eq($c->getStatusCode(), 503); }); } // wait Coroutine::join($cid_list); // low concurrency $n = SERVER_MAX_CONCURRENCY; $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($url) { $c = Swoole\Coroutine\Http\get($url); Assert::eq($c->getStatusCode(), 200); Assert::eq($c->getBody(), GREETING_MESSAGE); }); } Coroutine::join($cid_list); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['log_file' => '/dev/null', 'max_concurrency' => SERVER_MAX_CONCURRENCY]); $http->on('start', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { System::sleep(0.1); $response->end(GREETING_MESSAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/max_coro_num.phpt ================================================ --TEST-- swoole_http_server: max coro num --SKIPIF-- --FILE-- initRandomData(2); $pm->parentFunc = function ($pid) use ($pm) { switch_process(); go(function () use ($pm) { $statusCode = httpGetStatusCode("http://127.0.0.1:{$pm->getFreePort()}", ['timeout' => 0.1]); echo PHP_EOL . $statusCode . PHP_EOL; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { Co::set(['max_coroutine' => 1]); $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); Co::yield(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { echo "never here\n"; $response->end($pm->getRandomData()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Server::start(): exceed max number of coroutine 1 in %s on line %d Warning: Swoole\Server::start(): Swoole\Http\Server->onRequest handler error in %s on line %d 503 ================================================ FILE: tests/swoole_http_server/max_execution_time.phpt ================================================ --TEST-- swoole_http_server: max execution time --SKIPIF-- --FILE-- initRandomData(2); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $result = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); var_dump($result); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set( [ 'enable_coroutine' => true, 'hook_flags' => SWOOLE_HOOK_ALL, ] ); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { try { Swoole\Coroutine::setTimeLimit(1); sleep(5); $response->header('Content-Type', 'text/plain'); $response->end('Hello World'); } catch (\Throwable $e) { Assert::true($e instanceof \Swoole\Coroutine\TimeoutException); $response->end('execution timeout'); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- string(17) "execution timeout" ================================================ FILE: tests/swoole_http_server/max_input_vars.phpt ================================================ --TEST-- swoole_http_server: max_input_vars --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $maxInputVars = ini_get('max_input_vars') + 10; $data = []; $cookies = []; $temp = 'max_input_vars'; for ($i = 0; $i < $maxInputVars; $i++) { $data[$temp . $i] = $temp; $cookies[] = $temp . $i.'='.$temp; } // post method httpRequest("http://127.0.0.1:{$pm->getFreePort()}", ['data' => $data, 'headers' => ['Cookie' => implode(';', $cookies)]]); // get method httpRequest("http://127.0.0.1:{$pm->getFreePort()}?".http_build_query($data)); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response){ $maxInputVars = ini_get('max_input_vars'); if ($request->get) { var_dump(count($request->get) == $maxInputVars); } if ($request->post) { var_dump(count($request->post) == $maxInputVars); } if ($request->cookie) { var_dump(count($request->cookie) == $maxInputVars); } $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s To increase the limit change max_input_vars in php.ini. %s To increase the limit change max_input_vars in php.ini. bool(true) bool(true) %s To increase the limit change max_input_vars in php.ini. bool(true) DONE ================================================ FILE: tests/swoole_http_server/mixed_server.phpt ================================================ --TEST-- swoole_http_server: http mixed server --SKIPIF-- --FILE-- true, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 2, ]; $pm = new ProcessManager; $pm->initFreePorts(2); // client side $pm->parentFunc = function ($pid) use ($pm, $tcp_options) { go(function () use ($pm, $tcp_options) { // http $http_client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(0)); Assert::assert($http_client->post('/', 'Swoole Http')); var_dump($http_client->body); // http2 $http2_client = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort(0)); $http2_client->connect(); $http2_request = new Swoole\Http2\Request; $http2_request->method = 'POST'; $http2_request->data = 'Swoole Http2'; $http2_client->send($http2_request); $http2_response = $http2_client->recv(); var_dump($http2_response->data); // websocket $http_client->upgrade('/'); $http_client->push('Swoole Websocket'); var_dump($http_client->recv()->data); // tcp $tcp_client = new Swoole\Coroutine\Client(SWOOLE_TCP); $tcp_client->set($tcp_options); $tcp_client->connect('127.0.0.1', $pm->getFreePort(1)); $tcp_client->send(tcp_pack('Swoole Tcp')); var_dump(tcp_unpack($tcp_client->recv())); $pm->kill(); }); }; // server side $pm->childFunc = function () use ($pm, $tcp_options) { $server = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http2_protocol' => true ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); // http && http2 $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('Hello ' . $request->rawcontent()); }); // websocket $server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $server->push($frame->fd, 'Hello ' . $frame->data); }); // tcp $tcp_server = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_TCP); $tcp_server->set($tcp_options); $tcp_server->on('receive', function (Swoole\Server $server, int $fd, int $reactor_id, string $data) { $server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- string(17) "Hello Swoole Http" string(18) "Hello Swoole Http2" string(22) "Hello Swoole Websocket" string(16) "Hello Swoole Tcp" ================================================ FILE: tests/swoole_http_server/new_cookie.phpt ================================================ --TEST-- swoole_http_server: new cookie --SKIPIF-- --FILE-- withName('test') ->withValue('123456789') ->withExpires(time() + 3600) ->withPath('/path') ->withDomain('example.com') ->withSecure(true) ->withHttpOnly(true) ->withSameSite('None'); var_dump($cookie->toArray()); $cookie->reset(); var_dump($cookie->toArray()); ?> --EXPECTF-- array(11) { ["name"]=> string(4) "test" ["value"]=> string(9) "123456789" ["path"]=> string(5) "/path" ["domain"]=> string(11) "example.com" ["sameSite"]=> string(4) "None" ["priority"]=> string(0) "" ["encode"]=> bool(true) ["expires"]=> int(%d) ["secure"]=> bool(true) ["httpOnly"]=> bool(true) ["partitioned"]=> bool(false) } array(11) { ["name"]=> string(0) "" ["value"]=> string(0) "" ["path"]=> string(0) "" ["domain"]=> string(0) "" ["sameSite"]=> string(0) "" ["priority"]=> string(0) "" ["encode"]=> bool(true) ["expires"]=> int(0) ["secure"]=> bool(false) ["httpOnly"]=> bool(false) ["partitioned"]=> bool(false) } ================================================ FILE: tests/swoole_http_server/no_compression.phpt ================================================ --TEST-- swoole_http_server: no compression --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(md5_file(__DIR__ . '/../../README.md') == md5($data)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->set(['http_compression' => false,]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->end(co::readFile(__DIR__ . '/../../README.md')); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/numeric_header_name.phpt ================================================ --TEST-- swoole_http_server: numeric header name --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->get('/')); Assert::eq($client->headers['12345'], 'hello'); Assert::eq($client->headers['12345.678'], 'world'); $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { $response->header(12345, 'hello'); $response->header(12345.678, 'world'); $response->end('OK'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/objectCookie.phpt ================================================ --TEST-- swoole_http_server: new cookie --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\Run(function () use ($pm) { var_dump(httpRequest("http://127.0.0.1:{$pm->getFreePort()}")['set_cookie_headers']); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $server->set(['log_file' => '/dev/null']); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $cookie = new Swoole\Http\Cookie(); $cookie->withName('key1') ->withValue('val1') ->withExpires(time() + 84600) ->withPath('/') ->withDomain('id.test.com') ->withSecure(true) ->withHttpOnly(true) ->withSameSite('None') ->withPriority('High') ->withPartitioned(true); $response->setCookie($cookie); $cookie->withValue(''); $response->setCookie($cookie); $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- array(2) { [0]=> string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" [1]=> string(151) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" } ================================================ FILE: tests/swoole_http_server/objectCookieMemory.phpt ================================================ --TEST-- swoole_http_server: new cookie memory --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $previous = memory_get_usage(); $cookie = new Swoole\Http\Cookie(); $i = 10000; while($i--) { $cookie->withName('key1') ->withValue('val1') ->withExpires(time() + 84600) ->withPath('/') ->withDomain('id.test.com') ->withSecure(true) ->withHttpOnly(true) ->withSameSite('None') ->withPriority('High') ->withPartitioned(true); } global $previous; global $item; $current = memory_get_usage(); $stats = [ 'id' => $http->getWorkerId(), 'item' => $item++, 'prev_mem' => $previous, 'curr_mem' => $current, 'diff_mem' => $current - $previous, ]; $previous = $current; echo json_encode($stats), PHP_EOL; $response->end('test response'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- {"id":%d,"item":null,"prev_mem":null,"curr_mem":%d,"diff_mem":%d} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":%d} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} {"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} DONE ================================================ FILE: tests/swoole_http_server/octane_bug_651.phpt ================================================ --TEST-- swoole_http_server: Octane bug 651 https://github.com/laravel/octane/issues/651 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->get('/')); Assert::eq($client->getBody(), 'timeout'); $pm->kill(); }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $http->set(['log_file' => '/dev/null']); $timerTable = new Swoole\Table(250); $timerTable->column('worker_pid', Swoole\Table::TYPE_INT); $timerTable->column('time', Swoole\Table::TYPE_INT); $timerTable->column('fd', Swoole\Table::TYPE_INT); $timerTable->create(); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on('start', function ($server) use ($timerTable) { Swoole\Timer::tick(500, function ($id) use ($timerTable, $server) { foreach ($timerTable as $workerId => $row) { if ((time() - $row['time']) > 3) { $timerTable->del($workerId); $newRes = Swoole\Http\Response::create($server, $row['fd']);; if ($newRes) { Swoole\Timer::clear($id); $newRes->status(408); $newRes->end('timeout'); Swoole\Process::kill($row['worker_pid'], 9); return; } } } }); }); $http->on('Request', function ($request, $response) use ($http, $timerTable) { $timerTable->set($http->getWorkerId(), [ 'worker_pid' => $http->getWorkerPid(), 'time' => time(), 'fd' => $request->fd, ]); sleep(10); $response->end('Hello'); $timerTable->del($http->getWorkerId()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_http_server/pipeline.phpt ================================================ --TEST-- swoole_http_server: http pipeline --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 1)) { exit("connect failed. Error: {$client->errCode}\n"); } $host = 'localhost'; foreach (range(1, N) as $_) { $get = http_build_query(['a' => str_repeat('A', rand(1024, 4096)), 'b' => 3.1415926]); $CR = "\r"; $data = <<send($data); } $html = ''; while (1) { $data = $client->recv(); if (!$data) { echo "ERROR\n"; break; } $html .= $data; if (substr_count($html, "HTTP/1.1 200 OK") == N) { break; } } $pm->kill(); Assert::same(substr_count($html, "HTTP/1.1 200 OK"), N); Assert::same(substr_count($html, "swoole-http-server"), N); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(var_dump_return($request->get, $request->server)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/post.phpt ================================================ --TEST-- swoole_http_server: post --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $html) { Co\run(function () use ($pm, $html) { $index = rand(8192, strlen($html) - 8192); $reqData = [ 'data1' => substr($html, 0, $index), 'data2' => substr($html, $index) ]; $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}/", $reqData); Assert::assert($resp); $respData = json_decode($resp, true); Assert::same($respData['data1'], $reqData['data1']); Assert::same($respData['data2'], $reqData['data2']); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $html) { $mode = SERVER_MODE_RANDOM; $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), $mode); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($html) { $resp->end(json_encode($req->post)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/purge_method.phpt ================================================ --TEST-- swoole_http_server: PURGE method --SKIPIF-- --FILE-- Purged'; $pm->parentFunc = function ($pid) use ($pm, $html) { go(function () use ($pm, $html) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/", ['method' => 'PURGE']); Assert::same($data, $html); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $html) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $http->on('request', function ($request, $response) use ($html) { if ($request->server['request_method'] == 'PURGE') { $response->end($html); return; } $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/range.phpt ================================================ --TEST-- swoole_http_server: range --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data2 = file_get_contents(TEST_IMAGE); // range $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15']]); Assert::same($response['statusCode'], 206); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); Assert::same('bytes 0-15/218787', $response['headers']['content-range']); $lastModified = $response['headers']['last-modified'] ?? null; Assert::notNull($lastModified); Assert::null($response['headers']['accept-ranges'] ?? null); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-']]); Assert::same($response['statusCode'], 206); Assert::same('bytes 0-218786/218787', $response['headers']['content-range']); Assert::same(bin2hex($response['body']), bin2hex($data2)); // exit; $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=16-31']]); Assert::same($response['statusCode'], 206); Assert::same('bytes 16-31/218787', $response['headers']['content-range']); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 16, 16))); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=-16']]); Assert::same($response['statusCode'], 206); Assert::same('bytes 218771-218786/218787', $response['headers']['content-range']); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, -16))); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=128-']]); Assert::same($response['statusCode'], 206); Assert::same('bytes 128-218786/218787', $response['headers']['content-range']); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 128))); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-0,-1']]); Assert::same($response['statusCode'], 206); Assert::isEmpty($response['headers']['content-range'] ?? null); Assert::notEq(preg_match('/multipart\/byteranges; boundary=(.+)/', $response['headers']['content-type'] ?? '', $matches), false); $boundary = $matches[1]; $expect = sprintf(<<getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15,32-63']]); Assert::same($response['statusCode'], 206); Assert::notEq(preg_match('/multipart\/byteranges; boundary=(.+)/', $response['headers']['content-type'] ?? '', $matches), false); $boundary = $matches[1]; $expect = sprintf(<<getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => $lastModified]]); Assert::same($response['statusCode'], 206); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => 'test']]); Assert::same($response['statusCode'], 206); Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); $lastModifiedTime = strtotime($lastModified); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => date(DATE_RFC7231, $lastModifiedTime - 1)]]); Assert::same($response['statusCode'], 200); Assert::same(bin2hex($response['body']), bin2hex($data2)); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => date(DATE_RFC7231, $lastModifiedTime + 1)]]); Assert::same($response['statusCode'], 200); Assert::same(bin2hex($response['body']), bin2hex($data2)); // head $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'method' => 'HEAD']); Assert::same($response['statusCode'], 200); Assert::isEmpty($response['body']); Assert::same($response['headers']['accept-ranges'], 'bytes'); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'method' => 'HEAD', 'headers' => ['Range' => 'bytes=0-15']]); Assert::same($response['statusCode'], 206); Assert::same('bytes 0-15/218787', $response['headers']['content-range']); Assert::isEmpty($response['body']); Assert::null($response['headers']['accept-ranges'] ?? null); // data boundary $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'abc']]); Assert::same($response['statusCode'], 200); Assert::same(bin2hex($response['body']), bin2hex($data2)); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=abc']]); Assert::same($response['statusCode'], 416); Assert::isEmpty($response['body']); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=-999999']]); Assert::same($response['statusCode'], 206); Assert::same(bin2hex($response['body']), bin2hex($data2)); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=999999']]); Assert::same($response['statusCode'], 416); Assert::isEmpty($response['body']); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', 'static_handler_locations' => ['/static', '/'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/range2.phpt ================================================ --TEST-- swoole_http_server: range - confusing header --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data2 = file_get_contents(TEST_IMAGE); // range $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['-Range' => 'none', 'Range' => 'bytes=0-15']]); Assert::same($response['statusCode'], 206); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', 'static_handler_locations' => ['/static', '/'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/rawContent.phpt ================================================ --TEST-- swoole_http_server: raw-content --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $randomData = get_safe_random(); $httpClient = new Co\http\Client(HTTP_SERVER_HOST, $pm->getFreePort(), false); $httpClient->setMethod("POST"); $httpClient->setData($randomData); $ok = $httpClient->execute("/rawContent"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::same($httpClient->body, $randomData); $ok = $httpClient->execute("/getContent"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::same($httpClient->body, $randomData); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { if ($request->server['request_uri'] === '/rawContent') { $response->end($request->rawContent()); } else { $response->end($request->getContent()); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/rawCookie.phpt ================================================ --TEST-- swoole_http_server: raw-cookie --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function() use ($pm) { $httpClient = new Co\http\Client(HTTP_SERVER_HOST, $pm->getFreePort(), false); $httpClient->setMethod("POST"); $httpClient->setData("HELLO"); $ok = $httpClient->execute("/rawcookie"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::same($httpClient->body, "Hello World!"); $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm, $simple_http_server) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $name = "name"; $value = "value"; // $expire = $request->swoole_server["request_time"] + 3600; $expire = 0; $path = "/"; $domain = ""; $secure = false; $httpOnly = true; // string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); $expect = "name=value; path=/; HttpOnly"; Assert::assert(in_array($expect, $response->cookie, true)); $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); $response->rawcookie("rawcontent", $request->rawcontent()); $response->end("Hello World!"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/redirect.phpt ================================================ --TEST-- swoole_http_server: http redirect --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/nonexistent"); Assert::assert(!empty($data)); Assert::assert(md5($data) === md5_file(TEST_IMAGE)); $pm->kill(); }); Swoole\Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function ($request, Swoole\Http\Response $response) { if ($request->server['path_info'] == '/nonexistent') { $response->redirect('/test.jpg'); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/reset_concurrency_with_base.phpt ================================================ --TEST-- swoole_http_server: reset concurrency [SWOOLE_BASE] --SKIPIF-- --FILE-- column('pid', Table::TYPE_INT); $table->create(); $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm) { run(function () use ($pm) { $n = N; $coroutines = []; while ($n--) { $coroutines[] = go(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['timeout' => 10]); Assert::eq($client->get('/'), false); Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); }); } Co::join($coroutines); Co::sleep(0.1); $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::assert($client->get('/')); $stats = json_decode($client->getBody()); Assert::eq($stats->concurrency, 1); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm, $counter, $table) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 4, 'max_concurrency' => 160, 'log_file' => '/dev/null', ]); $http->on('workerStart', function ($server, $wid) use ($pm, $table) { if ($wid === 0) { $pm->wakeup(); } $pid = posix_getpid(); $table->set('worker_' . $wid, ['pid' => $pid]); // echo "Worker #{$wid}(pid=$pid) is started\n"; }); $http->on('request', function (Request $request, Response $response) use ($http, $counter, $table) { $c = $counter->add(); if ($c < N) { Co::sleep(100); } elseif ($c == N) { $stats = $http->stats(); Assert::eq($stats['concurrency'], N); $pid = posix_getpid(); foreach ($table as $val) { if ($val['pid'] !== $pid) { posix_kill($val['pid'], SIGKILL); } } posix_kill($pid, SIGKILL); } else { $stats = $http->stats(); Assert::eq($stats['concurrency'], 1); $response->end(json_encode($stats)); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/reset_concurrency_with_process.phpt ================================================ --TEST-- swoole_http_server: reset concurrency [SWOOLE_PROCESS] --SKIPIF-- --FILE-- column('pid', Table::TYPE_INT); $table->create(); $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm) { run(function () use ($pm) { $n = N; $coroutines = []; while ($n--) { $coroutines[] = go(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['timeout' => 10]); Assert::eq($client->get('/'), false); Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); }); } Co::sleep(0.1); $pm->wait(); $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::assert($client->get('/')); $stats = json_decode($client->getBody()); Assert::eq($stats->concurrency, 1); /** * PROCESS 模式下 Worker 进程退出时连接不会被关闭,这与 BASE 模式不同,因此需要先关闭服务器,其他正在运行的协程才会获得返回值 */ $pm->kill(); Co::join($coroutines); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm, $counter, $table) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 4, 'max_concurrency' => 160, 'log_file' => '/dev/null', ]); $http->on('workerStart', function ($server, $wid) use ($pm, $table) { if ($wid === 0) { $pm->wakeup(); } $pid = posix_getpid(); $table->set('worker_' . $wid, ['pid' => $pid]); // echo "Worker #{$wid}(pid=$pid) is started\n"; }); $http->on('request', function (Request $request, Response $response) use ($http, $counter, $table) { $c = $counter->add(); if ($c < N) { Co::sleep(100); } elseif ($c == N) { $stats = $http->stats(); Assert::eq($stats['concurrency'], N); $pid = posix_getpid(); foreach ($table as $val) { if ($val['pid'] !== $pid) { posix_kill($val['pid'], SIGKILL); } } posix_kill($pid, SIGKILL); } else { $stats = $http->stats(); Assert::eq($stats['concurrency'], 1); $response->end(json_encode($stats)); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/response_create.phpt ================================================ --TEST-- swoole_http_server: response create --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go( function () use ($pm) { $body = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}", ['timeout' => 0.1]); Assert::eq($body, 'hello world'); $pm->kill(); } ); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([Constant::OPTION_LOG_FILE => '/dev/null']); $serv->on( Constant::EVENT_WORKER_START, function () use ($pm) { $pm->wakeup(); } ); $serv->on( 'Receive', function ($serv, $fd, $tid, $data) { $resp = Swoole\Http\Response::create($serv, $fd); $resp->end("hello world"); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/send_500_to_client.phpt ================================================ --TEST-- swoole_http_server: When the process restarts, send a 500 status code to the clients waiting in the queue --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::setHookFlags(SWOOLE_HOOK_ALL); $statusCodes = []; run(function () use ($pm, &$statusCodes) { $waitGroup = new WaitGroup(); $index = 0; go(function () use ($waitGroup, $pm, &$index, &$statusCodes) { $waitGroup->add(); $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['timeout' => 15]); $client->get('/?id=' . $index++); if (!isset($statusCodes[$client->statusCode])) { $statusCodes[$client->statusCode] = 0; } $statusCodes[$client->statusCode]++; $waitGroup->done(); }); sleep(1); for ($i = 0; $i < 10; $i++) { go(function () use ($waitGroup, $pm, &$index, &$statusCodes) { $waitGroup->add(); $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['timeout' => 15]); $client->get('/?id=' . $index++); if (!isset($statusCodes[$client->statusCode])) { $statusCodes[$client->statusCode] = 0; } $statusCodes[$client->statusCode]++; $waitGroup->done(); }); } $waitGroup->wait(); $pm->kill(); }); Assert::greaterThanEq($statusCodes[503], 8); Assert::lessThanEq($statusCodes[200], 3); echo 'DONE'; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'enable_coroutine' => true, 'worker_max_concurrency' => 1, 'max_wait_time' => 10, 'reload_async' => true, 'hook_flags' => SWOOLE_HOOK_ALL, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $http->reload(); sleep(2); $response->end("id=" . $request->get['id']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/send_empty_file.phpt ================================================ --TEST-- swoole_http_server: send empty file --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { file_put_contents(TMP_FILE, ''); $recv_file = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); unlink(TMP_FILE); Assert::same($recv_file, ''); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->sendfile(TMP_FILE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/send_yield.phpt ================================================ --TEST-- swoole_http_server: http chunk with send yield --SKIPIF-- --FILE-- initRandomData(1, 64 * ONE_MEGABYTES); $pm->parentFunc = function ($pid) use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert($data === $pm->getRandomData()); phpt_var_dump(strlen($data)); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $mode) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), $mode); $http->set([ 'log_file' => '/dev/null', 'send_yield' => true, 'http_compression' => false ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $data = $pm->getRandomData(); $data_len = strlen($data); $offset = 0; do { $send_bytes = min($data_len - $offset, ONE_MEGABYTES); Assert::assert($response->write(substr($data, $offset, $send_bytes)) === true); $offset += $send_bytes; } while ($offset < $data_len); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); } ?> --EXPECT-- DONE DONE ================================================ FILE: tests/swoole_http_server/sendfile.phpt ================================================ --TEST-- swoole_http_server: sendfile --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = MAX_REQUESTS; $i--;) { $send_file = get_safe_random(mt_rand(0, 65535 * 10)); file_put_contents('/tmp/sendfile.txt', $send_file); $recv_file = file_get_contents("http://127.0.0.1:{$pm->getFreePort()}"); Assert::same($send_file, $recv_file); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('Content-Type', 'application/octet-stream'); $response->header('Content-Disposition', 'attachment; filename=recvfile.txt'); $response->sendfile('/tmp/sendfile.txt'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_client_reset.phpt ================================================ --TEST-- swoole_http_server: client reset when sending file --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Runtime::enableCoroutine(SWOOLE_HOOK_ALL); Co\run(function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 128 * 1024]); Assert::true($client->connect('127.0.0.1', $pm->getFreePort())); $client->send("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"); $resp = ''; Co\go(function () use ($pm) { $file = file_get_contents('http://127.0.0.1:' . $pm->getFreePort()); Assert::eq(md5_file(TMP_FILE), md5($file)); }); while (true) { $data = $client->recv(); System::sleep(0.01); Assert::notEmpty($data); $resp .= $data; if (strlen($resp) > 2 * 1024 * 1024) { $client->close(); break; } } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->header('Content-Type', 'application/octet-stream'); $response->header('Content-Disposition', 'attachment; filename=recvfile.txt'); $response->sendfile(TMP_FILE); }); $http->start(); }; $pm->childFirst(); $pm->run(); unlink(TMP_FILE); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_dir.phpt ================================================ --TEST-- swoole_http_server: sendfile dir --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $recv_file = @file_get_contents("http://127.0.0.1:{$pm->getFreePort()}"); Assert::eq($recv_file, false); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function ($request, $response) { $filename = '/tmp'; $response->header('Content-Type', 'application/octet-stream', true); Assert::eq(@$response->sendfile($filename), false); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_link.phpt ================================================ --TEST-- swoole_http_server: sendfile link --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $send_file) { $recv_file = @file_get_contents("http://127.0.0.1:{$pm->getFreePort()}"); Assert::eq($recv_file, $send_file); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function ($request, $response) { $response->header('Content-Type', 'application/octet-stream', true); Assert::eq($response->sendfile(LINK), true); }); $http->start(); }; $pm->childFirst(); $pm->run(); unlink(LINK); unlink(FILE); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_no_keepalive.phpt ================================================ --TEST-- swoole_http_server: sendfile with dispatch_mode=7 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $n = MAX_CONCURRENCY_LOW; while ($n--) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['KeepAlive' => 'off', 'Connection' => 'close']); for ($i = MAX_REQUESTS_LOW; $i--;) { $cli->get('/'); Assert::contains($cli->getBody(), 'swoole_http_server: sendfile with dispatch_mode=7'); } }); } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $http->set([ 'log_file' => '/dev/null', 'dispatch_mode' => 7, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->sendfile(__FILE__); }); $http->on('message', function(){}); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_with_dispatch_mode_7.phpt ================================================ --TEST-- swoole_http_server: sendfile with dispatch_mode=7 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $n = MAX_CONCURRENCY_LOW; while ($n--) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); for ($i = MAX_REQUESTS_LOW; $i--;) { $cli->get('/'); Assert::contains($cli->getBody(), 'swoole_http_server: sendfile with dispatch_mode=7'); } }); } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $http->set([ 'log_file' => '/dev/null', 'dispatch_mode' => 7, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->sendfile(__FILE__); }); $http->on('message', function(){}); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/sendfile_with_ssl.phpt ================================================ --TEST-- swoole_http_server: sendfile with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = MAX_REQUESTS; $i--;) { $send_file = get_safe_random(mt_rand(0, 65535 * 10)); file_put_contents('/tmp/sendfile.txt', $send_file); $ctxArr = [ 'verify_peer' => false, ]; $ctx = stream_context_create(['ssl' => $ctxArr]); $recv_file = file_get_contents("https://127.0.0.1:{$pm->getFreePort()}", false, $ctx); Assert::same($send_file, $recv_file); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('Content-Type', 'application/octet-stream'); $response->header('Content-Disposition', 'attachment; filename=recvfile.txt'); $response->sendfile('/tmp/sendfile.txt'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/server_addr.phpt ================================================ --TEST-- swoole_http_server: add server addr --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm, $ips) { Co\run(function () use ($pm, $ips) { $body = httpGetBody("http://{$ips[1]}:{$pm->getFreePort()}"); Assert::eq($body, 'Hello World'); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $ips) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $ips) { $server = $request->server; Assert::eq($server['server_addr'], $ips[1]); Assert::eq($server['remote_addr'], $ips[1]); Assert::true($server['server_port'] != $server['remote_port']); $response->status(200, "status"); $response->end("Hello World"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_http_server/server_addr2.phpt ================================================ --TEST-- swoole_http_server: add server addr2 --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $body = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); Assert::eq($body, 'Hello World'); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $server = $request->server; Assert::eq($server['server_addr'], '127.0.0.1'); Assert::eq($server['remote_addr'], '127.0.0.1'); Assert::true($server['server_port'] != $server['remote_port']); $response->status(200, "status"); $response->end("Hello World"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_http_server/set_content_length.phpt ================================================ --TEST-- swoole_http_server: allow setting content length header --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:" . $pm->getFreePort() . '/'); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($response, 0, $header_size); Assert::assert(strrpos($header, 'Content-Length: 50') > 0); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on('request', function ($req, Swoole\Http\Response $resp) use ($data) { $resp->header('Content-Type', 'application/json'); $resp->header('Content-Length', 50); $resp->end($data); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/shutdown_in_event_worker.phpt ================================================ --TEST-- swoole_http_server: shutdown in worker process --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); }); usleep(100000); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 2, 'log_file' => '/dev/null', ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('workerStop', function ($server) { echo "worker exit\n"; }); $serv->on('Request', function (Request $request, Response $response) use ($serv, $pm) { $response->end('Hello Swoole'); $serv->shutdown(); $pm->wakeup(); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- worker exit worker exit ================================================ FILE: tests/swoole_http_server/shutdown_in_task_worker.phpt ================================================ --TEST-- swoole_http_server: shutdown in task process --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); }); usleep(100000); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 2, 'task_worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('workerStop', function ($server) { echo "worker exit\n"; }); $serv->on('Task', function ($server, $taskId, $workerId, $data) use ($pm) { $server->shutdown(); $pm->wakeup(); }); $serv->on('Request', function (Request $request, Response $response) use ($serv) { $response->end('Hello Swoole'); $serv->task('a'); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- worker exit worker exit worker exit ================================================ FILE: tests/swoole_http_server/slow_client.phpt ================================================ --TEST-- swoole_http_server: slow client --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); $client->connect("127.0.01", $pm->getFreePort()); $html = base64_encode(random_bytes(rand(1024, 65536))); $len = strlen($html); $data = "POST /index.html HTTP/1.1\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: $len\r\nX-Server: swoole\r\n\r\n$html"; $chunks = str_split($data, rand(5, 255)); foreach ($chunks as $out) { $client->send($out); usleep(100); } $data = $client->recv(); Assert::stringNotEmpty($data); Assert::true(swoole_string($data)->contains('HTTP/1.1 200 OK')); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { Assert::same($request->header['server'], 'nginx'); Assert::same($request->header['x-server'], 'swoole'); Assert::same($request->header['content-type'], 'text/html'); Assert::eq($request->header['content-length'], strlen($request->getContent())); $response->end("OK"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/slow_large_post.phpt ================================================ --TEST-- swoole_http_server: slow large post --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); $client->connect("127.0.0.1", $pm->getFreePort()); $post_data = KEY . '=' . urlencode(VALUE); $len = strlen($post_data); $data = "POST /index.html HTTP/1.1\r\nServer: nginx\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: $len\r\nX-Server: swoole\r\n\r\n$post_data"; $client->send(substr($data, 0, N1)); usleep(30000); $client->send(substr($data, N1, N2)); usleep(30000); $client->send(substr($data, N1 + N2)); $data = $client->recv(); Assert::stringNotEmpty($data); Assert::true(swoole_string($data)->contains('HTTP/1.1 200 OK')); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, ]); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { Assert::same($request->header['server'], 'nginx'); Assert::same($request->header['x-server'], 'swoole'); Assert::same($request->header['content-type'], 'application/x-www-form-urlencoded'); Assert::eq($request->header['content-length'], strlen($request->getContent())); Assert::eq(VALUE, $request->post[KEY]); $response->end("OK"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/sni/server.phpt ================================================ --TEST-- swoole_http_server/sni: server --SKIPIF-- --FILE-- useConstantPorts = true; $pm->parentFunc = function () use ($pm) { $flags = STREAM_CLIENT_CONNECT; $ctxArr = [ 'cafile' => __DIR__ . '/sni_server_ca.pem', 'capture_peer_cert' => true, 'verify_peer' => false, ]; $port = $pm->getFreePort(); $ctxArr['peer_name'] = 'cs.php.net'; $ctx = stream_context_create(['ssl' => $ctxArr]); $client = stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx); $cert = stream_context_get_options($ctx)['ssl']['peer_certificate']; var_dump(openssl_x509_parse($cert)['subject']['CN']); $ctxArr['peer_name'] = 'uk.php.net'; $ctx = stream_context_create(['ssl' => $ctxArr]); $client = @stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx); $cert = stream_context_get_options($ctx)['ssl']['peer_certificate']; var_dump(openssl_x509_parse($cert)['subject']['CN']); $ctxArr['peer_name'] = 'us.php.net'; $ctx = stream_context_create(['ssl' => $ctxArr]); $client = @stream_socket_client("tls://127.0.0.1:$port", $errno, $errstr, 1, $flags, $ctx); $cert = stream_context_get_options($ctx)['ssl']['peer_certificate']; var_dump(openssl_x509_parse($cert)['subject']['CN']); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $http->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', 'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3 | SWOOLE_SSL_TLSv1_1 | SWOOLE_SSL_SSLv2, 'ssl_sni_certs' => [ "cs.php.net" => [ 'ssl_cert_file' => SSL_FILE_DIR . "/sni_server_cs_cert.pem", 'ssl_key_file' => SSL_FILE_DIR . "/sni_server_cs_key.pem" ], "uk.php.net" => [ 'ssl_cert_file' => SSL_FILE_DIR . "/sni_server_uk_cert.pem", 'ssl_key_file' => SSL_FILE_DIR . "/sni_server_uk_key.pem" ], "us.php.net" => [ 'ssl_cert_file' => SSL_FILE_DIR . "/sni_server_us_cert.pem", 'ssl_key_file' => SSL_FILE_DIR . "/sni_server_us_key.pem", ], ] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->end("hello world"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- string(%d) "cs.php.net" string(%d) "uk.php.net" string(%d) "us.php.net" DONE ================================================ FILE: tests/swoole_http_server/static_handler/locations.phpt ================================================ --TEST-- swoole_http_server/static_handler: static handler with locations --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/examples/test.jpg", ['http2' => $http2]); Assert::assert(!empty($data)); Assert::assert(md5($data) === md5_file(TEST_IMAGE)); /** * 命中location,但文件不存在,直接返回 404 */ $status = httpGetStatusCode("http://127.0.0.1:{$pm->getFreePort()}/examples/test2.jpg", ['http2' => $http2]); Assert::assert($status == 404); /** * 动态请求 */ $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2]); Assert::assert($data == TEST_IMAGE); }); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(dirname(__DIR__))) . '/', 'static_handler_locations' => ['/examples'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->end(TEST_IMAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/mimetype_not_exists.phpt ================================================ --TEST-- swoole_http_server/static_handler: mimetype not exists --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { Assert::eq(get("http://127.0.0.1:{$pm->getFreePort()}/examples/not_exists.xyz")->getStatusCode(), 404); Assert::eq(get("http://127.0.0.1:{$pm->getFreePort()}/not_exists.xyz")->getStatusCode(), 500); $resp = get("http://127.0.0.1:{$pm->getFreePort()}/examples/exists.xyz"); Assert::eq($resp->getBody(), TEST_RANDOM_BYTES); Assert::eq($resp->getHeaders()['content-type'], 'application/octet-stream'); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => TEST_DOCUMENT_ROOT, 'static_handler_locations' => ['/examples'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { }); $http->start(); }; $pm->childFirst(); $pm->run(); //unlink(TEST_FILE); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/read_link_2.phpt ================================================ --TEST-- swoole_http_server/static_handler: link to a file outside the document root --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $doc_root, $image_dir, $image_link) { Swoole\Coroutine\run(function () use ($pm, $doc_root, $image_dir, $image_link) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/{$image_dir}/image.jpg"); Assert::assert(md5($data) === md5_file(TEST_IMAGE)); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $doc_root, $image_dir, $image_link) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => $doc_root, 'static_handler_locations' => ['/image'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { }); $http->start(); }; $pm->childFirst(); $pm->run(); $cleanup_fn(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/read_link_file.phpt ================================================ --TEST-- swoole_http_server/static_handler: static handler with locations --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/examples/test_link.jpg"); if (is_file(TEST_LINK_IMAGE)) { unlink(TEST_LINK_IMAGE); } Assert::assert(md5($data) === md5_file(TEST_IMAGE)); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(__DIR__, 3) . '/', 'static_handler_locations' => ['/examples'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/relative_path.phpt ================================================ --TEST-- swoole_http_server/static_handler: static handler with relative path --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/examples/test.jpg", ['http2' => $http2]); Assert::notEmpty($data); Assert::same(md5($data), md5_file(TEST_IMAGE)); /** * 命中location,但文件不存在,直接返回 404 */ $status = httpGetStatusCode("http://127.0.0.1:{$pm->getFreePort()}/examples/test2.jpg", ['http2' => $http2]); Assert::same($status, 404); /** * 动态请求 */ $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2]); Assert::same($data, TEST_IMAGE); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => __DIR__ . '/../../../', 'static_handler_locations' => ['/examples'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end(TEST_IMAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/relative_path_2.phpt ================================================ --TEST-- swoole_http_server/static_handler: static handler with relative path [2] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../examples/test.jpg", ['http2' => $http2]); Assert::notEmpty($data); Assert::same(md5($data), md5_file(TEST_IMAGE)); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docs/swoole-logo.svg", ['http2' => $http2]); Assert::eq("hello world", $data); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => SOURCE_ROOT_PATH . '/examples', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end("hello world"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/relative_path_3.phpt ================================================ --TEST-- swoole_http_server/static_handler: doc root with same prefix --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $doc1_root, $doc2_root) { Swoole\Coroutine\run(function () use ($pm, $doc1_root, $doc2_root) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot/image.jpg"); Assert::assert(md5($data) === md5_file(TEST_IMAGE)); $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot2/uuid.txt"); Assert::isEmpty($data); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $doc1_root, $doc2_root) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => $doc1_root, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { }); $http->start(); }; $pm->childFirst(); $pm->run(); $cleanup_fn(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler/urldecode.phpt ================================================ --TEST-- swoole_http_server/static_handler: http url decode (#2676) --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $filename, $content) { foreach ([false, true] as $http2) { Coroutine\run(function () use ($pm, $http2, $filename, $content) { $data = httpGetBody( "http://127.0.0.1:{$pm->getFreePort()}/" . urlencode($filename), ['http2' => $http2] ); Assert::same($data, $content); }); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => __DIR__ ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { var_dump('never here'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/static_handler.phpt ================================================ --TEST-- swoole_http_server: static file handler --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2]); Assert::assert(!empty($data)); $data2 = file_get_contents(TEST_IMAGE); for ($i = 0; $i < strlen($data); $i++) { if (!isset($data2[$i])) { echo "no index {$i}\n"; var_dump(substr($data, $i)); break; } if ($data[$i] != $data2[$i]) { var_dump($i); break; } } Assert::same(md5($data), md5_file(TEST_IMAGE)); #3084 $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/empty.txt"); Assert::same($response['statusCode'], 200); Assert::isEmpty($response['body']); #3131 $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/moc.moc"); Assert::same($response['statusCode'], 200); Assert::same($response['body'], file_get_contents(__DIR__ . '/../../examples/http/moc.moc')); Assert::same($response['headers']['content-type'], 'application/x-mocha'); }); } echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, 'document_root' => dirname(dirname(__DIR__)) . '/examples/', 'static_handler_locations' => ['/static', '/'] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end('hello world'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/task/enable_coroutine.phpt ================================================ --TEST-- swoole_http_server/task: use async io and coroutine in task process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { for ($n = MAX_REQUESTS; $n--;) { if (!Assert::assert(($res = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/task?n={$n}")) === 'OK')) { echo "{$res}\n"; break; } } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'task_worker_num' => 1, 'task_enable_coroutine' => true ]); $server->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { global $randoms; $n = $request->get['n']; switch ($request->server['path_info']) { case '/task': { list($ret_n, $ret_random) = $server->taskCo([$n], 1)[0]; if ($ret_n !== $n) { $response->end("ERROR MATCH {$ret_n} with {$n}"); return; } elseif ($ret_random !== $randoms[$n]) { $response->end("ERROR EQUAL {$ret_n}(" . strlen($ret_random) . ") with {$n}(" . strlen($randoms[$n]) . ")"); return; } $response->end('OK'); break; } case '/random': { $response->end($randoms[$n]); break; } } }); $server->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->get("/random?n={$task->data}"); $task->finish([$task->data, $cli->body]); }); $server->on('finish', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/task/enable_coroutine_with_wrong_usage.phpt ================================================ --TEST-- swoole_http_server/task: use async io and coroutine in task process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { usleep(100 * 1000); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'task_worker_num' => 1, 'task_enable_coroutine' => true ]); $server->on('workerStart', function (Swoole\Http\Server $server, int $wid) use ($pm) { if ($wid === 0) { $server->taskCo(['foo'], 1); } }); $server->on('workerError', function (Swoole\Http\Server $server) use ($pm) { $pm->wakeup(); }); $server->on('request', function () { }); $server->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) use ($pm) { $server->finish('bar'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: Swoole\Server::finish(): please use Swoole\Server\Task->finish instead when task_enable_coroutine is enable in %s/task/enable_coroutine_with_wrong_usage.php on line %d --EXPECTF_85-- Fatal error: Swoole\Server::finish(): please use Swoole\Server\Task->finish instead when task_enable_coroutine is enable in %s/task/enable_coroutine_with_wrong_usage.php on line %d Stack trace: #0 %s(%d): Swoole\Server->finish('bar') #1 [internal function]: {closure:{closure:%s:%d}:%d}(Object(Swoole\Http\Server), Object(Swoole\Server\Task)) #2 {main} ================================================ FILE: tests/swoole_http_server/task/use_object.phpt ================================================ --TEST-- swoole_http_server/task: task_use_object --SKIPIF-- --FILE-- set([ 'log_file' => '/dev/null', 'task_worker_num' => 1, 'task_use_object' => true, ]); $server->on('request', function (Request $request, Response $response) use ($server) { $response->end("Hello Swoole\n"); }); $server->on('managerStart', function (Server $server) { $server->task(''); }); $server->on('task', function ($_, Task $task) use ($server) { var_dump(func_num_args()); var_dump(func_get_args()[1]); Assert::same($task->flags & SWOOLE_TASK_NOREPLY, SWOOLE_TASK_NOREPLY); usleep(100000); $server->shutdown(); }); $server->start(); ?> --EXPECTF-- int(2) object(Swoole\Server\Task)#%d (%d) { ["data"]=> string(0) "" ["dispatch_time"]=> float(%f) ["id"]=> int(0) ["worker_id"]=> int(0) ["flags"]=> int(%d) } ================================================ FILE: tests/swoole_http_server/tmp-content-type.phpt ================================================ --TEST-- swoole_http_server: tmp content-type --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $client = new GuzzleHttpClient(); $baseUrl = 'http://127.0.0.1:' . $pm->getFreePort(); $res = $client->post($baseUrl . '/', [ 'multipart' => [ [ 'name' => 'file', 'contents' => fopen(__FILE__, 'r'), 'filename' => basename(__FILE__), 'headers' => ['Content-Type' => 'application/php-script'] ], ], ]); $status = $res->getStatusCode(); $body = $res->getBody()->getContents(); Assert::eq($status, 200); $result = json_decode($body, true); Assert::eq($result['file']['name'], basename(__FILE__)); Assert::eq($result['file']['type'], 'application/php-script'); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) use ($http) { $response->end(json_encode($request->files)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/too_many_special_chars_in_cookie.phpt ================================================ --TEST-- swoole_http_server: too many special chars in cookie --SKIPIF-- --FILE-- setRandomFunc(function () { static $str = '!#$&\'()*+/:;=?@{}『』'; return str_shuffle(str_repeat($str, mt_rand(128, 1024) / strlen($str))); }); $pm->initRandomDataEx(1, MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); for ($n = MAX_REQUESTS; $n--;) { Assert::assert($cli->get('/')); Assert::same($cli->statusCode, 200); Assert::same($cli->cookies['foo'], $pm->getRandomData()); } }); Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { static $pre_cookie; if ($pre_cookie) { Assert::same($request->cookie['foo'], $pre_cookie); } $response->cookie('foo', $pre_cookie = $pm->getRandomData(), time() + 60 * 30, '/'); $response->end(); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/trailer.phpt ================================================ --TEST-- swoole_http_server: trailer --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Swoole\Coroutine\run(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->get('/'); Assert::eq(md5('hello world'), $cli->headers['content-md5']); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('trailer', 'Content-MD5'); $data = 'hello world'; Assert::true($response->write($data)); Assert::true($response->trailer('Content-MD5', md5($data))); Assert::true($response->end()); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/unixsocket.phpt ================================================ --TEST-- swoole_http_server: http unix-socket --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_UNIX_STREAM); Assert::assert($client->connect(UNIXSOCK_PATH, 0, -1)); for ($n = MAX_REQUESTS; $n--;) { $client->send("GET / HTTP/1.1\r\n\r\n"); list($headers, $body) = explode("\r\n\r\n", @$client->recv()); Assert::assert(count(explode("\n", $headers)) >= 5); Assert::same($body, 'Hello Swoole!'); } }); } Swoole\Event::wait(); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server(UNIXSOCK_PATH, 0, SERVER_MODE_RANDOM, SWOOLE_UNIX_STREAM); $server->set(['log_file' => '/dev/null']); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('Hello Swoole!'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server/unixsocket2.phpt ================================================ --TEST-- swoole_http_server: http unix socket [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, SOCKET); curl_setopt($ch, CURLOPT_URL, "http://localhost/?a=hello&b=12345&test=world"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); var_dump($output); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(SOCKET, 0, SWOOLE_PROCESS, SWOOLE_SOCK_UNIX_STREAM); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, Response $resp) { $resp->end(json_encode($req->get, true)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- string(40) "{"a":"hello","b":"12345","test":"world"}" ================================================ FILE: tests/swoole_http_server/unset_response_header.phpt ================================================ --TEST-- swoole_http_server: unset header of response --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->get('/')); echo "{$cli->statusCode}\n"; Assert::true(!isset($cli->headers['foo'])); Assert::same($cli->headers['bar'], 'Foo'); echo "{$cli->body}\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on("workerStart", function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('Foo', 'Bar'); $response->status(500); try { unset($response->header); } catch (Error $e) { echo $e->getMessage() . PHP_EOL; $response->header('Foo', null); } $response->header('Bar', 'Foo'); $response->end("just an 500 error for fun\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Property header of class Swoole\Http\Response cannot be unset 500 just an 500 error for fun ================================================ FILE: tests/swoole_http_server/upload.phpt ================================================ --TEST-- swoole_http_server: upload 01 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); $file = TEST_IMAGE; $post_data = array('test' => str_repeat('a', 80)); if (function_exists("curl_file_create")) { $cfile = curl_file_create($file); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . $file; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); Assert::assert(!empty($res)); Assert::same($res, md5_file($file)); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(md5_file($request->files['file']['tmp_name'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload4.phpt ================================================ --TEST-- swoole_http_server: upload 04 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); $file = TEST_IMAGE; $post_data = array('test' => str_repeat('a', 80)); $cfile = curl_file_create($file); $cfile->setPostFilename(FILENAME); $post_data['file'] = $cfile; curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); Assert::assert(!empty($res)); Assert::eq($res, FILENAME); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null' ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->files['file']['name']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/uploadFile.phpt ================================================ --TEST-- swoole_http_server: upload file --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); $post_data = ['test' => str_repeat('a', 80)]; if (function_exists("curl_file_create")) { $cfile = curl_file_create(TEST_IMAGE); $post_data['upfile'] = $cfile; } else { $post_data['upfile'] = '@' . TEST_IMAGE; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); echo curl_exec($ch); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { if ( empty($request->files['upfile']) || md5_file(TEST_IMAGE) !== md5_file($request->files['upfile']['tmp_name']) || !is_uploaded_file($request->files['upfile']['tmp_name']) ) { $response->end("ERROR\n"); } else { $response->end("OK\n"); } }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server/upload_02.phpt ================================================ --TEST-- swoole_http_server: upload raw --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $sock = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}"); $length = mt_rand(100000, 200000); $content = "POST / HTTP/1.1\r\n" . "Host: local.swoole.com\r\n" . "Content-Type: multipart/form-data; boundary=Boundary+D80E45AE1BB1E1E1\r\n" . "Connection: keep-alive\r\n" . "Accept: */*\r\n" . "User-Agent: SCRM/1.0.1 (iPhone; iOS 10.3.3; Scale/2.00)\r\n" . "Accept-Language: zh-Hans-CN;q=1\r\n" . "Content-Length: {{content_length}}\r\n" . "\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"folder_id\"\r\n" . "\r\n" . "0\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"name\"\r\n" . "\r\n" . "IMG_0941(17).png\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"servers_id\"\r\n" . "\r\n" . "1000\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"size\"\r\n" . "\r\n" . "103972\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"token\"\r\n" . "\r\n" . "08ccbb0e11cb5cbc718c71fa7be2adfb\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"type\"\r\n" . "\r\n" . "0\r\n" . "--Boundary+D80E45AE1BB1E1E1\r\n" . "Content-Disposition: form-data; name=\"file\"; filename=\"IMG_0941(17).png\"\r\n" . "Content-Type: application/octet-stream\r\n\r\n" . "{{file_content}}\r\n" . "--Boundary+D80E45AE1BB1E1E1--\r\n"; $content = content_hook_replace( $content, [ 'content_length' => $length, 'file_content' => str_repeat(get_safe_random(1), $length - 718) ] ); fwrite($sock, $content); stream_set_chunk_size($sock, 2 * 1024 * 1024); $data = fread($sock, 2 * 1024 * 1024); Assert::assert(!empty($data)); $json = json_decode(explode("\r\n\r\n", $data, 2)[1], true); Assert::assert(is_array($json)); Assert::true(isset($json['file'])); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(json_encode($request->files + $request->post)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/upload_03.phpt ================================================ --TEST-- swoole_http_server: upload raw --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $sock = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}"); $boundary = "Boundary+D80E45AE1BB1E1E1"; $body = implode("\r\n", [ "--$boundary\r\nContent-Disposition: form-data; name=\"folder_id\"\r\n\r\n999999955", "--$boundary\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n" . str_repeat('A', rand(100, 200)), "--$boundary--" ]); $body .= "\r\n"; $len = strlen($body); $data = implode("\r\n", [ "POST /file_service/v3/file/upload_do HTTP/1.1", "Content-Type: multipart/form-data; boundary=$boundary; error=bad", "Content-Length: $len", "\r\n", $body, ]); fwrite($sock, $data); stream_set_chunk_size($sock, 2 * 1024 * 1024); $data = fread($sock, 2 * 1024 * 1024); Assert::assert(!empty($data)); $json = json_decode(explode("\r\n\r\n", $data, 2)[1], true); Assert::assert(is_array($json)); Assert::true(isset($json['folder_id'])); Assert::true(isset($json['name'])); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(['log_file' => '/dev/null']); $http->on("WorkerStart", function ($serv, $wid) { global $pm; $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(json_encode($request->post)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload_big_file.phpt ================================================ --TEST-- swoole_http_server: upload big file --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); $file = TEST_IMAGE; $post_data = array( 'test' => str_repeat('a', 80), 'hello' => base64_encode(random_bytes(rand(10, 128))), 'world' => base64_encode(random_bytes(rand(1024, 8192))), ); if (function_exists("curl_file_create")) { $cfile = curl_file_create($file); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . $file; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 1000); $res = curl_exec($ch); Assert::assert(!empty($res)); Assert::same($res, md5_file($file)); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'package_max_length' => 64 * 1024, 'upload_max_filesize' => 8 * 1024 * 1024, ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(md5_file($request->files['file']['tmp_name'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload_file_array_default.phpt ================================================ --TEST-- swoole_http_server: upload files array default format --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $formData = [ 'file' => curl_file_create(TEST_IMAGE, 'application/octet-stream', 'image.jpg'), 'form[file]' => curl_file_create(TEST_IMAGE, 'image/jpeg', 'photo.jpg'), 'form[group][file]' => curl_file_create(TEST_IMAGE2, 'image/svg+xml', 'swoole-logo.svg'), ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_POSTFIELDS, $formData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); $json = json_decode($result, true); assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', filesize(TEST_IMAGE), 0); assert_upload_file($json['form']['file'], '/tmp/swoole.upfile.fixture2', 'photo.jpg', 'image/jpeg', filesize(TEST_IMAGE), 0); assert_upload_file($json['form']['group']['file'], '/tmp/swoole.upfile.fixture3', 'swoole-logo.svg', 'image/svg+xml', filesize(TEST_IMAGE2), 0); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $files = $request->files; if (!is_array($files) || empty($files['file']['tmp_name']) || empty($files['form']['file']['tmp_name']) || empty($files['form']['group']['file']['tmp_name']) ) { $response->end(); return; } $files['file']['tmp_name'] = '/tmp/swoole.upfile.fixture1'; $files['form']['file']['tmp_name'] = '/tmp/swoole.upfile.fixture2'; $files['form']['group']['file']['tmp_name'] = '/tmp/swoole.upfile.fixture3'; $response->end(json_encode($files)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload_file_array_parsed.phpt ================================================ --TEST-- swoole_http_server: upload files array parsed format --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $formData = [ 'file' => curl_file_create(TEST_IMAGE, 'application/octet-stream', 'image.jpg'), 'form[file]' => curl_file_create(TEST_IMAGE, 'image/jpeg', 'photo.jpg'), 'form[group][file]' => curl_file_create(TEST_IMAGE2, 'image/svg+xml', 'swoole-logo.svg'), ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_POSTFIELDS, $formData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); $json = json_decode($result, true); assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', filesize(TEST_IMAGE), 0); assert_upload_file($json['form'], [ 'file' => '/tmp/swoole.upfile.fixture2', 'group' => [ 'file' => '/tmp/swoole.upfile.fixture3', ], ], [ 'file' => 'photo.jpg', 'group' => [ 'file' => 'swoole-logo.svg', ], ], [ 'file' => 'image/jpeg', 'group' => [ 'file' => 'image/svg+xml', ], ], [ 'file' => filesize(TEST_IMAGE), 'group' => [ 'file' => filesize(TEST_IMAGE2), ], ], [ 'file' => 0, 'group' => [ 'file' => 0, ], ]); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null', 'http_parse_files' => true, ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $files = $request->files; if (!is_array($files) || empty($files['file']['tmp_name']) || empty($files['form']['tmp_name']['file']) || empty($files['form']['tmp_name']['group']['file']) ) { $response->end(); return; } $files['file']['tmp_name'] = '/tmp/swoole.upfile.fixture1'; $files['form']['tmp_name']['file'] = '/tmp/swoole.upfile.fixture2'; $files['form']['tmp_name']['group']['file'] = '/tmp/swoole.upfile.fixture3'; $response->end(json_encode($files)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload_file_empty.phpt ================================================ --TEST-- swoole_http_server: upload files empty --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $boundary = "------------------------d3f990cdce762596"; $body = implode("\r\n", [ "--$boundary", 'Content-Disposition: form-data; name="file1"; filename="empty.txt"', 'Content-Type: text/plain', '', '', "--$boundary", 'Content-Disposition: form-data; name="file2"; filename=""', 'Content-Type: application/octet-stream', '', '', "--$boundary--", '', ]); $request = implode("\r\n", [ 'POST / HTTP/1.1', "Content-Type: multipart/form-data; boundary=$boundary", 'Content-Length: ' . strlen($body), '', $body, ]); $sock = stream_socket_client("tcp://127.0.0.1:{$pm->getFreePort()}"); fwrite($sock, $request); stream_set_chunk_size($sock, 2 * 1024 * 1024); $response = fread($sock, 2 * 1024 * 1024); fclose($sock); [$header, $body] = explode("\r\n\r\n", $response); $json = json_decode($body, true); Assert::true(is_array($json)); Assert::true(isset($json['file1'])); assert_upload_file($json['file1'], '/tmp/swoole.upfile.fixture1', 'empty.txt', 'text/plain', 0, 0); Assert::true(isset($json['file2'])); assert_upload_file($json['file2'], '', '', '', 0, 4); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'log_file' => '/dev/null' ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $files = $request->files; if (isset($files['file1']['tmp_name'])) { $files['file1']['tmp_name'] = '/tmp/swoole.upfile.fixture1'; } $response->end(json_encode($files)); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/upload_max_filesize.phpt ================================================ --TEST-- swoole_http_server: upload max filesize --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); $file = TEST_IMAGE; $post_data = array( 'test' => str_repeat('a', 80), 'hello' => base64_encode(random_bytes(rand(10, 128))), 'world' => base64_encode(random_bytes(rand(1024, 8192))), ); if (function_exists("curl_file_create")) { $cfile = curl_file_create($file); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . $file; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 1000); $res = curl_exec($ch); Assert::isEmpty(($res)); Assert::eq(curl_getinfo($ch)['http_code'], 413); curl_close($ch); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'log_file' => '/dev/null', 'package_max_length' => 64 * 1024, 'upload_max_filesize' => 128 * 1024, ]); $http->on("WorkerStart", function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end(md5_file($request->files['file']['tmp_name'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server/url_rewrite.phpt ================================================ --TEST-- swoole_http_server: url rewrite --SKIPIF-- --FILE-- parentFunc = function () use ($pm, $file1, $file2) { foreach ([false, true] as $http2) { Swoole\Coroutine\run(function () use ($pm, $http2, $file1, $file2) { $options = ['http2' => $http2]; $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/view/post/3", $options); Assert::same($response['statusCode'], 200); Assert::same($response['body'], 'user_john_action_settings'); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/view/post/99", $options); Assert::same($response['statusCode'], 404); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/article/settings.html", $options); Assert::same($response['statusCode'], 200); Assert::same($response['body'], 'post_99_query_foo=bar'); $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/article/not_exists.html", $options); Assert::same($response['statusCode'], 404); }); } echo "DONE\n"; $pm->kill(); unlink($file1); unlink($file2); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort()); $http->set([ 'log_file' => '/dev/null', 'document_root' => __DIR__, 'enable_static_handler' => true, 'open_http2_protocol' => true, 'static_handler_locations' => ['/static'], 'url_rewrite_rules' => [ '~^/view/post/(\d+)$~' => '/static/$1.html', '/article/' => '/static/article/' ] ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Request $request, Response $response) { $response->end('dynamic_response'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/worker_max_concurrency.phpt ================================================ --TEST-- swoole_http_server: worker_max_concurrency --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $url = 'http://127.0.0.1:' . $pm->getFreePort(0) . '/'; $n = SERVER_WORKER_MAX_CONCURRENCY * 3; $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($url) { $c = Swoole\Coroutine\Http\get($url); Assert::eq($c->getStatusCode(), 200); Assert::eq($c->getBody(), GREETING_MESSAGE); }); } // wait Coroutine::join($cid_list); $c = Swoole\Coroutine\Http\get($url.'stats'); Assert::eq($c->getStatusCode(), 200); $json = json_decode($c->getBody()); Assert::true(isset($json->coroutine_peek_num)); Assert::eq($json->coroutine_peek_num, SERVER_WORKER_MAX_CONCURRENCY); echo "DONE\n"; $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { // $mode = SWOOLE_BASE; $mode = SERVER_MODE_RANDOM; $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), $mode); $http->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'worker_max_concurrency' => SERVER_WORKER_MAX_CONCURRENCY, ]); $http->on('start', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $http) { if ($request->server['request_uri'] == '/stats') { $response->end(json_encode($http->stats())); return; } System::sleep(0.1); $response->end(GREETING_MESSAGE); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server/zstd.phpt ================================================ --TEST-- swoole_http_server: support zstd compress --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $data) { run(function () use ($pm, $data) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->setHeaders(['Accept-Encoding' => 'zstd']); $client->get('/'); Assert::true($client->body == $data); Assert::true($client->headers['content-encoding'] == 'zstd'); Assert::true($client->headers['content-length'] != strlen($client->body)); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $data) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort()); $serv->set([ 'compression_level' => 20 ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) use ($data) { $resp->end($data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/bad_request.phpt ================================================ --TEST-- swoole_http_server_coro: bad request --SKIPIF-- --FILE-- handle('/', function (Request $request, Response $response) use ($server) { echo "never here\n"; }); $server->start(); }); Coroutine::sleep(0.001); Coroutine::create(function () use ($server) { $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (Assert::true($socket->connect('127.0.0.1', $server->port, -1))) { $ret = $socket->sendAll(REQUEST); Assert::same($ret, strlen(REQUEST)); Assert::contains($socket->recv(), 'HTTP/1.1 400 Bad Request'); $server->shutdown(); echo "DONE\n"; } }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/buffer_clear.phpt ================================================ --TEST-- swoole_http_server_coro: websocket buffer clear --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function() use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); $client->set(['open_websocket_pong_frame' => true]); $client->push('hello world'); $client->push('ping', SWOOLE_WEBSOCKET_OPCODE_PING); $frame1 = $client->recv(); $frame2 = $client->recv(); Assert::eq($frame1->data, 'received: hello world'); Assert::eq($frame1->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); Assert::eq($frame2->data, 'ping'); Assert::eq($frame2->opcode, SWOOLE_WEBSOCKET_OPCODE_PONG); }); Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { $response->upgrade(); while ($frame = $response->recv()) { $response->push('received: ' . $frame->data); } }); $pm->wakeup(); $server->start(); }); Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/bug_2682.phpt ================================================ --TEST-- swoole_http_server_coro: bug 2682 getData/getRawContent --SKIPIF-- --FILE-- handle('/api', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { var_dump(str_replace("\r\n", PHP_EOL, $request->getData())); echo PHP_EOL; var_dump($request->rawContent()); $response->end('OK'); Co::sleep(0.01); $server->shutdown(); }); $server->start(); }); go(function () { $client = new Swoole\Coroutine\Client(SWOOLE_TCP); $client->connect('127.0.0.1', PORT); $client->send(REQUEST); Assert::contains($client->recv(), '200 OK'); $client->close(); }); }); ?> --EXPECTF-- string(143) "POST /api HTTP/1.1 Host: localhost User-Agent: curl Accept: */* Content-Type: application/json Content-Length: 28 {"id": 1,"name": "xiaoming"}" string(28) "{"id": 1,"name": "xiaoming"}" ================================================ FILE: tests/swoole_http_server_coro/bug_3025.phpt ================================================ --TEST-- swoole_http_server_coro: bug 3025 getData --SKIPIF-- --FILE-- handle('/', function (Request $request, Response $response) { static $length; if (!isset($length)) { $length = strlen($request->getData()); } else { Assert::same(strlen($request->getData()), $length); } }); $server->start(); }); Coroutine::create(function () use ($server) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $server->port); for ($n = MAX_REQUESTS; $n--;) { $cli->get('/'); } $server->shutdown(); echo "DONE\n"; }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/bug_4519.phpt ================================================ --TEST-- swoole_http_server_coro: bug #4519 --SKIPIF-- --FILE-- initFreePorts(); $port = $pm->getFreePort(); $data = str_repeat('你好你好你好', 10000); $length = strlen($data); $pm->parentFunc = function ($pid) use ($pm, $data, $port) { run(function () use ($pm, $data, $port) { $client = new Client('127.0.0.1', $port); $client->setHeaders([ 'Content-type' => 'application/x-www-form-urlencoded', ]); $client->post('/api', ['test' => $data]); $client->close(); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm, $length, $port) { run(function () use ($pm, $length, $port) { $server = new Server('127.0.0.1', $port, false); $server->handle('/api', function (Request $request, Response $response) use ($length){ Assert::assert(sizeof($request->post) == 1 && strlen($request->post['test']) == $length); }); Swoole\Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/bug_no_handle.phpt ================================================ --TEST-- swoole_http_server_coro: bug no handle --SKIPIF-- --FILE-- start(); }); Coroutine::create(function () use ($server) { $cli = new Coroutine\Http\Client('127.0.0.1', $server->port); for ($n = MAX_REQUESTS; $n--;) { $cli->get('/'); Assert::same($cli->statusCode, Status::NOT_FOUND); } $cli->close(); if (defined('SWOOLE_USE_HTTP2')) { $cli = new Coroutine\Http2\Client('127.0.0.1', $server->port); $cli->connect(); for ($n = MAX_REQUESTS; $n--;) { $cli->send(new Http2\Request); $response = $cli->recv(); Assert::same($response->statusCode, Status::NOT_FOUND); } $cli->close(); } $server->shutdown(); echo "DONE\n"; }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/check_cookie_crlf.phpt ================================================ --TEST-- swoole_http_server_coro: check if the HTTP cookie contains CRLF --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->get('/rawcookie'); $headers = $client->getHeaders(); Assert::false(isset($headers['malicious-header'])); Assert::false(isset($headers['set-cookie'])); $client->get('/cookie'); $headers = $client->getHeaders(); Assert::false(isset($headers['malicious-header'])); Assert::true(isset($headers['set-cookie'])); $client->close(); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/rawcookie', function (Request $request, Response $response) { $value = "cn\r\nmalicious-header:injected\r\nContent-Length:27\r\n\r\n

malicious response body"; $response->rawcookie('lang', $value); $response->end('hello world'); }); $server->handle('/cookie', function (Request $request, Response $response) { $value = "cn\r\nmalicious-header:injected\r\nContent-Length:27\r\n\r\n

malicious response body"; $response->cookie('lang', $value); $response->end('hello world'); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Http\Response::rawcookie(): The value cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014" in %s DONE ================================================ FILE: tests/swoole_http_server_coro/check_http_header_crlf.phpt ================================================ --TEST-- swoole_http_server_coro: check if the HTTP header contains CRLF --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->get('/?r=AAA%0d%0amalicious-header:injected'); $headers = $client->getHeaders(); Assert::false(isset($headers['malicious-header'])); $client->close(); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function (Request $request, Response $response) { $response->header('Location', $request->get['r']); $response->status(302); $response->end('Redirecting...'); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Http\Response::end(): Header may not contain more than a single header, new line detected in %s DONE ================================================ FILE: tests/swoole_http_server_coro/chunked_pipeline_request.phpt ================================================ --TEST-- swoole_http_server_coro: chunked and pipeline request --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { chunked_request($pm); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { $response->end($request->rawContent() . EOF); }); Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_http_server_coro/close_socket.phpt ================================================ --TEST-- swoole_http_server_coro: close socket --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { for ($i = 0; $i < 2; $i++) { $cli = new Client('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->get('/')); Assert::assert(str_contains($cli->headers['server'], 'BWS') or str_contains($cli->headers['server'], 'bfe')); } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function (Request $request, Response $response) { $response->detach(); $socket = $response->socket; $headers = []; $checkHttpCode = false; $isHeaderSended = false; $httpCode = 200; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, 'https://www.baidu.com/'); curl_setopt($curl, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$headers, &$httpCode, &$checkHttpCode) { if (!$checkHttpCode) { $checkHttpCode = true; preg_match('/HTTP\/[0-9.]+\s(\d+)\s(.*)/', $header, $matches, PREG_OFFSET_CAPTURE, 0); if (!empty($matches)) { $httpCode = $matches[1][0]; return strlen($header); } } $content = trim($header); if (empty($content)) return strlen($header); list($key, $value) = explode(": ", $content); if (in_array(strtolower($key), ['content-length', 'transfer-encoding'])) return strlen($header); $headers[$key] = $value; return strlen($header); }); curl_setopt($curl, CURLOPT_WRITEFUNCTION, function ($curl, $str) use ($response, &$socket, &$headers, &$isHeaderSended, &$httpCode) { if (!$isHeaderSended) { $isHeaderSended = true; // $response->status($httpCode); if ($httpCode == 200) { $socket->send("HTTP/1.1 {$httpCode} OK\r\n"); } else { $socket->send("HTTP/1.1 {$httpCode} ERROR\r\n"); } foreach (array_merge([ 'Content-Type' => 'application/octet-stream', 'X-Accel-Buffering' => 'no', 'X-Server' => 'webserver/1.0' ], $headers) as $k => $v) { $socket->send("{$k}: {$v}\r\n"); } $socket->send("\r\n"); } return strlen($str); }); curl_exec($curl); curl_close($curl); return $socket->close(); }); $server->set([ 'http_compression' => false, 'http_parse_post' => false, 'http_parse_files' => false, ]); go(function () use ($server) { $server->start(); }); go(function () use ($server) { if (System::waitSignal(SIGTERM)) { $server->shutdown(); } }); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/compress_continue_frames.phpt ================================================ --TEST-- swoole_http_server_coro: compress continue frames --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'websocket_compression' => true ]); $ret = $client->upgrade('/'); Assert::assert($ret); $context = deflate_init(ZLIB_ENCODING_RAW); $client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set(['websocket_compression' => true]); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); $context = deflate_init(ZLIB_ENCODING_RAW); $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/compression_min_length.phpt ================================================ --TEST-- swoole_http_server_coro: compression_min_length --SKIPIF-- --FILE-- set(['compression_min_length' => 128,]); $server->handle('/test', function ($request, $response) { $response->end(str_repeat('A', $request->get['bytes'])); }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $server->start(); }); go(function () { $cli = new Client('127.0.0.1', TEST_PORT); $cli->setHeaders(['Accept-Encoding' => 'gzip', ]); $cli->get('/test?bytes=128'); Assert::eq($cli->getHeaders()['content-encoding'], 'gzip'); $cli = new Client('127.0.0.1', TEST_PORT); $cli->setHeaders(['Accept-Encoding' => 'gzip', ]); $cli->get('/test?bytes=127'); Assert::assert(!isset($cli->getHeaders()['content-encoding'])); file_get_contents('http://127.0.0.1:' . TEST_PORT . '/shutdown'); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/compression_types.phpt ================================================ --TEST-- swoole_http_server_coro: compression types --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { http_compression_types_test($pm); echo "DONE\n"; file_get_contents('http://127.0.0.1:' . $pm->getFreePort() . '/shutdown'); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set([ 'http_compression_types' => [ 'text/html', 'application/json' ], ]); $server->handle('/', function (Request $request, Response $response) { if ($request->server['request_uri'] == '/html') { $response->end(str_repeat('A', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/json') { $response->setHeader('Content-Type', 'application/json'); $response->end(str_repeat('B', $request->get['bytes'])); } elseif ($request->server['request_uri'] == '/raw') { $response->setHeader('Content-Type', 'text/raw'); $response->end(str_repeat('C', $request->get['bytes'])); } }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/continue_frames.phpt ================================================ --TEST-- swoole_http_server_coro: client continue frames - 1 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/continue_frames2.phpt ================================================ --TEST-- swoole_http_server_coro: client continue frames - 2 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s All fragments of a message, except for the initial frame, must use the continuation frame opcode(0). ================================================ FILE: tests/swoole_http_server_coro/continue_frames3.phpt ================================================ --TEST-- swoole_http_server_coro: client continue frames - 3 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); $frame = $client->recv(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); $frame = $response->recv(); Assert::true($frame->data == $data1 . $data2 . $data3); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %s A continuation frame cannot stand alone and MUST be preceded by an initial frame whose opcode indicates either text or binary data. ================================================ FILE: tests/swoole_http_server_coro/continue_frames4.phpt ================================================ --TEST-- swoole_http_server_coro: client continue frames - 4 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $results = []; $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $results[] = $client->recv(); Assert::true($results[0]->data == $data1 . $data2 . $data3); $client->push('222', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); $results[] = $client->recv(); Assert::true($results[1]->data == $data3 . $data2 . $data1); Assert::true($results[0]->data != $results[1]->data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $data1, $data2, $data3) { Co\run(function () use ($pm, $data1, $data2, $data3) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { $response->upgrade(); while ($frame = $response->recv()) { if ($frame->data == '111') { $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); } else { $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); } } }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/control_frame_compress.phpt ================================================ --TEST-- swoole_http_server_coro: control frame can not compress --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'websocket_compression' => true, 'open_websocket_close_frame' => true, ]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('A'); $frame = $client->recv(); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); Assert::true($frame->flags == SWOOLE_WEBSOCKET_FLAG_FIN); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set(['websocket_compression' => true]); $server->handle('/', function ($request, $response) { $response->upgrade(); $closeFrame = new CloseFrame(); $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $closeFrame->reason = 'hahahahaha'; $response->push($closeFrame, SWOOLE_WEBSOCKET_FLAG_RSV1 | SWOOLE_WEBSOCKET_FLAG_COMPRESS); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/control_frame_fragmented.phpt ================================================ --TEST-- swoole_http_server_coro: Control frames must not be fragmented --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'websocket_compression' => true, 'open_websocket_close_frame' => true, ]); $ret = $client->upgrade('/'); Assert::assert($ret); $client->push('A'); $frame = $client->recv(); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); Assert::true($frame->flags == SWOOLE_WEBSOCKET_FLAG_FIN); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set(['websocket_compression' => true]); $server->handle('/', function ($request, $response) { $response->upgrade(); $closeFrame = new CloseFrame(); $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $closeFrame->reason = 'hahahahaha'; $closeFrame->flags = 0; $response->push($closeFrame, 0); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/crash-bad-return-type.phpt ================================================ --TEST-- swoole_http_server_coro: crash - bad return type --SKIPIF-- --FILE-- '/dev/null']); go(function () use ($port) { $server = new Co\Http\Server("127.0.0.1", $port, true); $server->set([ 'open_tcp_nodelay' => true, 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Stop

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

Index

Stop

================================================ FILE: tests/swoole_http_server_coro/create_response.phpt ================================================ --TEST-- swoole_http_server_coro: create response --SKIPIF-- --FILE-- handle(function (Connection $conn) use ($server) { $req = Request::create(); while(true) { $data = $conn->recv(); if (strlen($data) != $req->parse($data) or $req->isCompleted()) { break; } } Assert::eq($req->get['value'], 1); Assert::eq($req->get['hello'], 'world'); $resp = Response::create($conn->exportSocket()); $resp->header('X-Server', 'swoole'); $resp->end(GREETER); $server->shutdown(); }); $server->start(); }); go(function () use ($port, $server) { $httpClient = new Co\Http\Client(HTTP_SERVER_HOST, $port, false); $httpClient->setMethod("POST"); $httpClient->setData("HELLO"); $ok = $httpClient->execute("/rawcookie?hello=world&value=1"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::eq($httpClient->getHeaders()['x-server'], 'swoole'); Assert::same($httpClient->getBody(), GREETER); $server->shutdown(); }); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/create_response_2.phpt ================================================ --TEST-- swoole_http_server_coro: create response [2] --SKIPIF-- --FILE-- handle(function (Connection $conn) use ($server) { $req = Request::create(); while(true) { $data = $conn->recv(); if (strlen($data) != $req->parse($data) or $req->isCompleted()) { break; } } Assert::eq($req->get['value'], 1); Assert::eq($req->get['hello'], 'world'); $resp = Response::create([$conn->exportSocket(), $req]); $resp->header('X-Server', 'swoole'); $resp->end(GREETER); $server->shutdown(); }); $server->start(); }); go(function () use ($port, $server) { $httpClient = new Co\Http\Client(HTTP_SERVER_HOST, $port, false); $httpClient->setMethod("POST"); $httpClient->setData("HELLO"); $ok = $httpClient->execute("/rawcookie?hello=world&value=1"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::eq($httpClient->getHeaders()['x-server'], 'swoole'); Assert::same($httpClient->getBody(), GREETER); $server->shutdown(); }); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/disconnect.phpt ================================================ --TEST-- swoole_http_server_coro: test disconnect function --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'open_websocket_close_frame' => true ]); $ret = $client->upgrade('/'); $client->push('Hello World'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); Assert::true($frame->reason == 'close it'); Assert::true($client->recv() == ''); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { $response->upgrade(); $response->recv(); Assert::true($response->disconnect(SWOOLE_WEBSOCKET_CLOSE_NORMAL, 'close it')); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/error_404.phpt ================================================ --TEST-- swoole_http_server_coro: 404 error --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { Assert::assert(httpGetStatusCode("http://127.0.0.1:{$pm->getFreePort()}/test") == 404); echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/stop?hello=1") . PHP_EOL; }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/hello', function ($request, $response) { $response->end("

hello

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

Stop

"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT--

Stop

================================================ FILE: tests/swoole_http_server_coro/error_413.phpt ================================================ --TEST-- swoole_http_server_coro: error 413 --SKIPIF-- --FILE-- set(['package_max_length' => 65536, ]); $server->handle('/', function ($request, $response) { $response->end(serialize($request->server)); }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $server->start(); }); go(function () { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', TEST_PORT, false); $cli->set(['timeout' => 5]); Assert::assert($cli->post('/world/index?b=455', ['value' => str_repeat('A', 128 * 1024 )])); Assert::assert($cli->getStatusCode() == 413); file_get_contents('http://127.0.0.1:' . TEST_PORT . '/shutdown'); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/form_data_1.phpt ================================================ --TEST-- swoole_http_server_coro: form data 1 --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { form_data_test($pm, [250]); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { $response->end(json_encode($request->post)); }); Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/form_data_2.phpt ================================================ --TEST-- swoole_http_server_coro: form data 2 --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { form_data_test($pm, [225]); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { $response->end(json_encode($request->post)); }); Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/graceful_shutdown.phpt ================================================ --TEST-- swoole_http_server_coro: graceful shutdown --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $errors = ''; Runtime::setHookFlags(SWOOLE_HOOK_ALL); Co\run(function () use ($pm, &$errors) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; go(function () use ($pm, &$errors) { try { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/sleep") . PHP_EOL; } catch (Throwable $e) { $errors .= $e->getMessage() . PHP_EOL; } }); go(function () use ($pm, &$errors) { usleep(5000); echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/shutdown") . PHP_EOL; try { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; } catch (Throwable $e) { $errors .= $e->getMessage() . PHP_EOL; echo "done\n"; } }); }); Assert::contains($errors, 'Connection reset by peer'); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->end("index"); }); $server->handle('/sleep', function ($request, $response) { Co::sleep(0.2); $response->end("sleep"); }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- index shutdown sleep done ================================================ FILE: tests/swoole_http_server_coro/handle.phpt ================================================ --TEST-- swoole_http_server_coro: handle --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test/index/") . PHP_EOL; echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/stop?hello=1") . PHP_EOL; }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Test

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

Stop

"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT--

Index

Test

Stop

================================================ FILE: tests/swoole_http_server_coro/http2.phpt ================================================ --TEST-- swoole_http_server_coro: http2 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->connect()); $streams = []; $request = new Request; for ($n = N; $n--;) { $request->data = $n; $streams[$client->send($request)] = $n; } for ($n = N; $n--;) { /** @var $response Response */ $response = $client->recv(); Assert::same($streams[$response->streamId], (int)$response->data); } echo "DONE\n"; $kill_request = new Request; $kill_request->path = '/stop'; $client->send($kill_request); $response = $client->recv(); Assert::assert($response instanceof Response); echo $response->data, "\n"; }); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Swoole\Coroutine\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->rawContent()); }); $server->handle('/stop', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { $response->end("STOP"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE STOP ================================================ FILE: tests/swoole_http_server_coro/http2_ssl.phpt ================================================ --TEST-- swoole_http_server_coro: http2 + SSL --SKIPIF-- --FILE-- set([ 'open_http2_protocol' => true, 'open_tcp_nodelay' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Stop

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

Index

Stop

================================================ FILE: tests/swoole_http_server_coro/https.phpt ================================================ --TEST-- swoole_http_server_coro: https --SKIPIF-- --FILE-- set(['open_tcp_nodelay' => true, 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Stop

"); $server->shutdown(); }); $server->start(); }); go(function () use ($port) { echo httpGetBody("https://127.0.0.1:{$port}/") . PHP_EOL; echo httpGetBody("https://127.0.0.1:{$port}/stop?hello=1") . PHP_EOL; }); Swoole\Event::wait(); ?> --EXPECT--

Index

Stop

================================================ FILE: tests/swoole_http_server_coro/ipv6.phpt ================================================ --TEST-- swoole_http_server_coro: ipv6 --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { echo file_get_contents("http://[::1]:{$pm->getFreePort()}/") . PHP_EOL; echo file_get_contents("http://[::1]:{$pm->getFreePort()}/stop?hello=1") . PHP_EOL; }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("::1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Stop

"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT--

Index

Stop

================================================ FILE: tests/swoole_http_server_coro/keepalive.phpt ================================================ --TEST-- swoole_http_server_coro: keepalive --SKIPIF-- --FILE-- handle('/', function ($request, $response) { $response->end(serialize($request->server)); }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $server->start(); }); go(function () { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', TEST_PORT, false); $cli->set(['timeout' => 5]); Assert::assert($cli->get('/hello?a=x3')); $data1 = unserialize($cli->body); Assert::assert($cli->get('/world/index?b=455')); $data2 = unserialize($cli->body); Assert::assert($data1['remote_port'] == $data2['remote_port']); file_get_contents('http://127.0.0.1:' . TEST_PORT . '/shutdown'); }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/open_frame.phpt ================================================ --TEST-- swoole_http_server_coro: handle frame by user --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $client->upgrade('/'); $client->push('Hello World'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); Assert::true($frame->code == SWOOLE_WEBSOCKET_CLOSE_NORMAL); Assert::true($frame->reason == "lalalala FUMEI"); $frame = $client->recv(); Assert::true($frame == ''); $client->disconnect(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $server->handle('/', function (Request $request, Response $response) { $response->upgrade(); $ping = new Frame(); $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; $response->push($ping); $pong = new Frame(); $pong->opcode = SWOOLE_WEBSOCKET_OPCODE_PONG; $response->push($pong); $close = new CloseFrame(); $close->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $close->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $close->reason = "lalalala FUMEI"; $response->push($close); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/ping.phpt ================================================ --TEST-- swoole_http_server_coro: test ping function --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['open_websocket_ping_frame' => true]); Assert::true($client->upgrade('/')); Assert::true($client->push('Hello World')); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); Assert::true($frame->data == 'Hello World'); Assert::true($client->ping()); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); Assert::true($frame->data == ''); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->upgrade(); $response->recv(); $response->ping('Hello World'); $response->ping(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/pipeline.phpt ================================================ --TEST-- swoole_http_server_coro: pipeline --SKIPIF-- --FILE-- handle('/', function (Request $request, Response $response) use ($server) { static $count = 0; $response->end("OK\n"); if (++$count === MAX_CONCURRENCY * MAX_REQUESTS) { echo "DONE\n"; $server->shutdown(); } }); $server->start(); }); for ($c = MAX_CONCURRENCY; $c--;) { Coroutine::sleep(0.001); Coroutine::create(function () use ($server) { $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (Assert::true($socket->connect('127.0.0.1', $server->port, -1))) { $ret = $socket->sendAll(str_repeat(REQUEST, MAX_REQUESTS)); Assert::same($ret, strlen(REQUEST) * MAX_REQUESTS); } else { throw new RuntimeException('Connect failed: ' . $socket->errMsg); } while (!empty($socket->recv())) { // pass } }); } }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/post_array.phpt ================================================ --TEST-- swoole_http_server_coro: post array data --SKIPIF-- --FILE-- handle('/post', function ($request, $response) { $response->end(json_encode(['form' => $request->post])); }); $server->handle('/stop', function ($request, $response) use ($server) { $response->end("

Stop

"); $server->shutdown(); }); $server->start(); }); go(function () use ($port) { $uri = 'http://127.0.0.1:' . $port; $data = []; for ($n = MAX_REQUESTS; $n--;) { $data[get_safe_random()] = get_safe_random(); } $body = httpGetBody($uri . '/post', ['method' => 'POST', 'data' => $data]); $form = json_decode($body, true)['form']; Assert::same($form, $data); echo httpGetBody($uri . "/stop?hello=1") . PHP_EOL; }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT--

Stop

DONE ================================================ FILE: tests/swoole_http_server_coro/random_port.phpt ================================================ --TEST-- swoole_http_server_coro: bind random port --SKIPIF-- --FILE-- port > 0 and $server->port < 65535); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/rawContent_get_big_data.phpt ================================================ --TEST-- swoole_http_server_coro: rawContent get big data --SKIPIF-- --FILE-- initRandomData(1, 64 * 1024); $pm->parentFunc = function () use ($pm) { run(function () use($pm) { httpRequest("http://127.0.0.1:{$pm->getFreePort()}/", [ 'method' => 'POST', 'data' => $pm->getRandomData(), ]); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { run(function () use($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function (Request $request, Response $response) use ($pm) { Assert::assert($request->rawContent() === $pm->getRandomData()); Assert::length($request->rawContent(), 64 * 1024); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/remote_addr.phpt ================================================ --TEST-- swoole_http_server_coro: remote_addr --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go( function () use ($pm) { $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); Assert::assert($client->get('/')); $data = $client->getBody(); Assert::assert($data); $json = json_decode($data); $info = $client->getsockname(); Assert::eq($json->remote_addr, $info['address']); Assert::eq($json->remote_port, $info['port']); httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/stop") . PHP_EOL; } ); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function (Request $request, $response) { $response->end(json_encode($request->server)); }); $server->handle('/stop', function ($request, $response) use ($server) { $response->end("

Stop

"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/restart.phpt ================================================ --TEST-- swoole_http_server_coro: graceful shutdown --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $n = 2; while ($n--) { echo "[$n]", httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; echo "[$n]", httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/shutdown") . PHP_EOL; usleep(150000); } }); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->end("index"); }); $server->handle('/shutdown', function ($request, $response) use ($server) { $response->end("shutdown"); $server->shutdown(); }); $pm->wakeup(); $n = 2; while ($n--) { $server->start(); usleep(100000); } }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- [1]index [1]shutdown [0]index [0]shutdown ================================================ FILE: tests/swoole_http_server_coro/reuse_port.phpt ================================================ --TEST-- swoole_http_server_coro: reuse port --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $ret = $cli->get('/hello'); if (!$ret) { echo "ERROR [3]\n"; return; } $result = unserialize($cli->getBody()); if (!$result) { echo "ERROR [4]\n"; return; } $pids[$result['wid']] = 1; }); $sch->start(); Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $atomic = new Atomic(); $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { $server = new Server('127.0.0.1', $pm->getFreePort(), false, true); $server->handle('/', function ($request, $response) { $response->end(serialize(['wid' => posix_getpid()])); }); if ($atomic->add() == 2) { $pm->wakeup(); } Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $server->start(); }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_http_server_coro/sendfile.phpt ================================================ --TEST-- swoole_http_server_coro: sendfile --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($i = MAX_REQUESTS; $i--;) { $send_file = get_safe_random(mt_rand(0, 65535 * 10)); file_put_contents('/tmp/sendfile.txt', $send_file); $recv_file = file_get_contents("http://127.0.0.1:{$pm->getFreePort()}/test.jpg"); Assert::same(md5($send_file), md5($recv_file)); } file_get_contents("http://127.0.0.1:{$pm->getFreePort()}/shutdown"); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function ($request, $response) { $response->end("

Index

"); }); $server->handle('/test.jpg', function ($request, $response) { $response->header('Content-Type', 'application/octet-stream'); $response->header('Content-Disposition', 'attachment; filename=recvfile.txt'); $response->sendfile('/tmp/sendfile.txt'); }); $server->handle('/shutdown', function ($request, $response) use ($server) { echo "shutdown\n"; $response->status(200); $response->end(); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- shutdown DONE ================================================ FILE: tests/swoole_http_server_coro/server_addr.phpt ================================================ --TEST-- swoole_http_server_coro: add server addr --SKIPIF-- --FILE-- initFreePorts(); $port = $pm->getFreePort(); $output = shell_exec('ip addr show'); preg_match_all('/inet (\d+\.\d+\.\d+\.\d+)\//', $output, $matches); $ips = $matches[1]; $pm->parentFunc = function ($pid) use ($pm, $port, $ips) { run(function () use ($pm, $port, $ips) { $client = new Client($ips[1], $port); $client->get('/'); $client->close(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm, $port, $ips) { run(function () use ($pm, $port, $ips) { $server = new Server('0.0.0.0', $port, false); $server->handle('/', function (Request $request, Response $response) use ($ips){ $server = $request->server; Assert::eq($server['server_addr'], $ips[1]); Assert::eq($server['remote_addr'], $ips[1]); Assert::true($server['server_port'] != $server['remote_port']); }); Swoole\Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/slow_client.phpt ================================================ --TEST-- swoole_http_server_coro: slow client --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); $client->connect("127.0.01", $pm->getFreePort()); $html = base64_encode(random_bytes(rand(1024, 65536))); $len = strlen($html); $data = "POST /index.html HTTP/1.1\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: $len\r\nX-Server: swoole\r\n\r\n$html"; $chunks = str_split($data, rand(5, 255)); foreach ($chunks as $out) { $client->send($out); usleep(100); } $data = $client->recv(); Assert::stringNotEmpty($data); Assert::true(swoole_string($data)->contains('HTTP/1.1 200 OK')); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { run(function () use($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function (Request $request, Response $response) use ($pm) { Assert::same($request->header['server'], 'nginx'); Assert::same($request->header['x-server'], 'swoole'); Assert::same($request->header['content-type'], 'text/html'); Assert::eq($request->header['content-length'], strlen($request->getContent())); $response->end("OK"); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server_coro/slow_large_post.phpt ================================================ --TEST-- swoole_http_server_coro: slow large post --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); $client->connect("127.0.0.1", $pm->getFreePort()); $post_data = KEY . '=' . urlencode(VALUE); $len = strlen($post_data); $data = "POST /index.html HTTP/1.1\r\nServer: nginx\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nContent-Length: $len\r\nX-Server: swoole\r\n\r\n$post_data"; $client->send(substr($data, 0, N1)); usleep(30000); $client->send(substr($data, N1, N2)); usleep(30000); $client->send(substr($data, N1 + N2)); $data = $client->recv(); Assert::stringNotEmpty($data); Assert::true(swoole_string($data)->contains('HTTP/1.1 200 OK')); $pm->kill(); echo "OK\n"; }; $pm->childFunc = function () use ($pm) { run(function () use($pm) { $server = new Server('127.0.0.1', $pm->getFreePort()); $server->handle('/', function (Request $request, Response $response) use ($pm) { Assert::same($request->header['server'], 'nginx'); Assert::same($request->header['x-server'], 'swoole'); Assert::same($request->header['content-type'], 'application/x-www-form-urlencoded'); Assert::eq($request->header['content-length'], strlen($request->getContent())); Assert::eq(VALUE, $request->post[KEY]); $response->end("OK"); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_http_server_coro/ssl_bad_client.phpt ================================================ --TEST-- swoole_http_server_coro: bad client --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $port = $pm->getFreePort(); $client = new Client(SWOOLE_SOCK_TCP); // 同步阻塞 if (!$client->connect('127.0.0.1', $port)) { exit("connect failed\n"); } $client->send('hello world'); while (true) { $data = $client->recv(); if (!$data) { break; } } echo httpGetBody("https://127.0.0.1:{$port}/stop?hello=1") . PHP_EOL; }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), true); $server->set([ 'open_tcp_nodelay' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $server->handle('/', function ($request, $response) { $response->end('

Index

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

Stop

'); $server->shutdown(); }); $server->start(); }); Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] WARNING Socket::ssl_accept(): bad SSL client[127.0.0.1:%d], reason=%d, error_string=%s

Stop

================================================ FILE: tests/swoole_http_server_coro/tcp_nodelay.phpt ================================================ --TEST-- swoole_http_server_coro: tcp nodelay --SKIPIF-- --FILE-- set(['open_tcp_nodelay' => true,]); $server->handle('/', function ($request, $response) { $response->end("

Index

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

Stop

"); $server->shutdown(); }); $server->start(); }); go(function () use ($port) { echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; echo httpGetBody("http://127.0.0.1:{$port}/stop?hello=1") . PHP_EOL; }); Swoole\Event::wait(); ?> --EXPECT--

Index

Stop

================================================ FILE: tests/swoole_http_server_coro/upload.phpt ================================================ --TEST-- swoole_http_server_coro: upload 01 --SKIPIF-- --FILE-- 0, 'trace_flags' => SWOOLE_TRACE_HTTP]); $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $uri = "http://127.0.0.1:{$pm->getFreePort()}"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $uri.'/upload'); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); //设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); $file = TEST_IMAGE; $post_data = array('test' => str_repeat('a', 80)); if (function_exists("curl_file_create")) { $cfile = curl_file_create($file); $post_data['file'] = $cfile; } else { $post_data['file'] = '@' . $file; } curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); Assert::assert(!empty($res)); Assert::same($res, md5_file($file)); curl_close($ch); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/upload', function ($request, $response) use ($server) { $response->end(md5_file($request->files['file']['tmp_name'])); $server->shutdown(); }); $server->start(); }); $pm->wakeup(); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/websocket.phpt ================================================ --TEST-- swoole_http_server_coro: websocket greeter and reply twice --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm, &$count) { global $count; $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $data = sha1(get_safe_random(mt_rand(1, 1024))); for ($n = MAX_REQUESTS; $n--;) { $cli->push($data); $ret = $cli->recv(); Assert::same($ret->data, "Hello {$data}!"); $ret = $cli->recv(); Assert::same($ret->data, "How are you, {$data}?"); Assert::same($cli->cookies['test-file'], __FILE__); Assert::same($cli->headers['x-swoole'], 'hello'); $count++; } }); } Swoole\Event::wait(); Assert::same($count, (MAX_CONCURRENCY * MAX_REQUESTS)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/websocket', function ($request, $ws) { $ws->header('x-swoole', 'hello'); $ws->cookie('test-file', __FILE__); $ws->upgrade(); while (true) { $frame = $ws->recv(); if ($frame === false) { echo "error : " . swoole_last_error() . "\n"; break; } else if ($frame == '') { break; } else { Assert::greaterThan($frame->fd, 0); $ws->push("Hello {$frame->data}!"); $ws->push("How are you, {$frame->data}?"); } } }); $server->handle('/shutdown', function ($request, $response) use ($server) { echo "shutdown\n"; $response->status(200); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/websocket_close.phpt ================================================ --TEST-- swoole_http_server_coro: close websocket connection --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { go(function () use ($pm) { global $count; $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $data = sha1(get_safe_random(mt_rand(0, 1024))); $cli->push($data); $ret = $cli->recv(); Assert::same($ret->data, "Hello {$data}!"); $s = microtime(true); // An empty string was received, indicating that the connection has been closed by the peer $ret = $cli->recv(); Assert::lessThan(microtime(true) - $s, 0.002); Assert::same($ret, ""); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/websocket', function ($request, $ws) { $ws->upgrade(); $frame = $ws->recv(); if ($frame === false) { echo "error : " . swoole_last_error() . "\n"; } else if ($frame === '' or $frame->data === '') { echo "close\n"; } else { Assert::greaterThan($frame->fd, 0); $ws->push("Hello {$frame->data}!"); $ws->close(); } System::sleep(0.5); }); $server->handle('/shutdown', function ($request, $response) use ($server) { echo "shutdown\n"; $response->status(200); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/websocket_compression.phpt ================================================ --TEST-- swoole_http_server_coro: websocket compression --SKIPIF-- --FILE-- set([ 'websocket_compression' => true, ]); $server->handle('/', function (Request $request, Response $ws) { if (($request->header['upgrade'] ?? '') === 'websocket') { if ($ws->upgrade()) { while ($frame = $ws->recv()) { $ws->push($frame); } } } else { $ws->status(400); $ws->end(); } }); Coroutine::create(function () use ($server) { $cli = new Client('127.0.0.1', $server->port); $cli->set([ 'timeout' => 5, 'websocket_compression' => true ]); $ret = $cli->upgrade('/'); if (!Assert::assert($ret)) { return; } for ($n = MAX_REQUESTS; $n--;) { $data = get_safe_random(); $cli->push( $data, SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN | SWOOLE_WEBSOCKET_FLAG_COMPRESS ); $frame = $cli->recv(); if (!Assert::same($frame->data, $data)) { return; } if (!Assert::eq($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS, defined('SWOOLE_HAVE_ZLIB'))) { return; } } $server->shutdown(); echo "DONE\n"; }); $server->start(); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/websocket_mixed.phpt ================================================ --TEST-- swoole_http_server_coro: bad request --SKIPIF-- --FILE-- handle('/', function (Request $request, Response $websocket) use ($server) { $websocket->upgrade(); $frame = $websocket->recv(); if ($frame) { $websocket->push($frame); } }); $server->start(); }); Coroutine::sleep(0.001); Coroutine::create(function () use ($server) { $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (Assert::true($socket->connect('127.0.0.1', $server->port, -1))) { $ret = $socket->sendAll(REQUEST); Assert::same($ret, strlen(REQUEST)); Assert::contains($socket->recv(), 'Hello Swoole'); $server->shutdown(); echo "DONE\n"; } }); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_http_server_coro/websocket_ping.phpt ================================================ --TEST-- swoole_http_server_coro: websocket ping --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $cli->push('Swoole'); $ret = $cli->recv(); Assert::same($ret->opcode, WEBSOCKET_OPCODE_PING); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/websocket', function ($request, $ws) { $ws->upgrade(); while (true) { $frame = $ws->recv(); if ($frame === false) { echo "error : " . swoole_last_error() . "\n"; break; } else if ($frame === '') { break; } else { usleep(10000); $ws->ping(); } } }); $server->start(); $pm->wakeup(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_http_server_coro/websocket_ping_pong.phpt ================================================ --TEST-- swoole_http_server_coro: websocket ping pong --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $cli->push('Swoole'); $ret = $cli->recv(); Assert::same($ret->data, "How are you, Swoole?"); $ret = $cli->recv(); Assert::same($ret->opcode, WEBSOCKET_OPCODE_PING); $pingFrame = new Frame; $pingFrame->opcode = WEBSOCKET_OPCODE_PING; // 发送 PING $cli->push($pingFrame); $ret = $cli->recv(); Assert::same($ret->opcode, WEBSOCKET_OPCODE_PONG); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $server->handle('/websocket', function ($request, $ws) { $ws->upgrade(); while (true) { $frame = $ws->recv(); if ($frame === false) { echo "error : " . swoole_last_error() . "\n"; break; } else if ($frame === '') { break; } else { if ($frame->opcode === 9) { $pFrame = new Frame; $pFrame->opcode = WEBSOCKET_OPCODE_PONG; $ws->push($pFrame); } else { $ws->push("How are you, {$frame->data}?"); $pFrame = new Frame; // 发送 PING $pFrame->opcode = WEBSOCKET_OPCODE_PING; $ws->push($pFrame); } } } }); $server->start(); $pm->wakeup(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_iouring/mix.phpt ================================================ --TEST-- swoole_iouring: support io_uring --SKIPIF-- --FILE-- 32, 'iouring_entries' => 30000, ]; if (defined('SWOOLE_IOURING_SQPOLL')) { $setting['iouring_flag'] = SWOOLE_IOURING_SQPOLL; } swoole_async_set($setting); $results = []; for ($i = 1; $i <= 10000; $i++) { $results[$i] = random_bytes(rand(8192, 8192 * 3)); } run(function() use ($results) { $filesize = 1048576; $content = random_bytes($filesize); $fileName = '/tmp/test_file'; Assert::eq(file_put_contents($fileName, $content), 1048576); var_dump(stat($fileName)); for ($i = 0; $i < 100; $i++) { Assert::eq(filesize($fileName), 1048576); Assert::eq(file_get_contents($fileName), $content); } unlink($fileName); Assert::true(!file_exists($fileName)); $stream = fopen($fileName, 'w'); fwrite($stream, $content); if (PHP_VERSION_ID >= 80100) { Assert::true(fdatasync($stream)); Assert::true(fsync($stream)); } Assert::eq(file_get_contents($fileName), $content); var_dump(fstat($stream)); fclose($stream); unlink($fileName); file_put_contents($fileName, $content); rename($fileName, $fileName.'aaa'); Assert::true(!file_exists($fileName)); Assert::true(file_exists($fileName.'aaa')); unlink($fileName.'aaa'); $directory = '/tmp/a/b/c/d/e/f'; mkdir($directory, 0755, true); Assert::true(is_dir($directory)); rmdir($directory); Assert::true(!is_dir($directory)); $waitGroup = new WaitGroup(); for ($i = 1; $i <= 10000; $i++) { go(function() use ($waitGroup, $i, $results){ $waitGroup->add(); $filename = '/tmp/file'.$i; file_put_contents($filename, $results[$i]); Assert::true($results[$i] == file_get_contents($filename)); file_put_contents($filename, $results[$i], FILE_APPEND); file_put_contents($filename, $results[$i], FILE_APPEND); Assert::true(strlen($results[$i]) * 3 == strlen(file_get_contents($filename))); $stream = fopen($filename, 'r+'); $size = rand(1, filesize($filename)); Assert::true(ftruncate($stream, $size)); fclose($stream); Assert::true($size == strlen(file_get_contents($filename))); $waitGroup->done(); }); } $waitGroup->wait(); echo 'SUCCESS'; }); ?> --EXPECTF-- array(26) { [0]=> int(%d) [1]=> int(%d) [2]=> int(%d) [3]=> int(%d) [4]=> int(%d) [5]=> int(%d) [6]=> int(%d) [7]=> int(%d) [8]=> int(%d) [9]=> int(%d) [10]=> int(%d) [11]=> int(%d) [12]=> int(%d) ["dev"]=> int(%d) ["ino"]=> int(%d) ["mode"]=> int(%d) ["nlink"]=> int(%d) ["uid"]=> int(%d) ["gid"]=> int(%d) ["rdev"]=> int(%d) ["size"]=> int(%d) ["atime"]=> int(%d) ["mtime"]=> int(%d) ["ctime"]=> int(%d) ["blksize"]=> int(%d) ["blocks"]=> int(%d) } array(26) { [0]=> int(%d) [1]=> int(%d) [2]=> int(%d) [3]=> int(%d) [4]=> int(%d) [5]=> int(%d) [6]=> int(%d) [7]=> int(%d) [8]=> int(%d) [9]=> int(%d) [10]=> int(%d) [11]=> int(%d) [12]=> int(%d) ["dev"]=> int(%d) ["ino"]=> int(%d) ["mode"]=> int(%d) ["nlink"]=> int(%d) ["uid"]=> int(%d) ["gid"]=> int(%d) ["rdev"]=> int(%d) ["size"]=> int(%d) ["atime"]=> int(%d) ["mtime"]=> int(%d) ["ctime"]=> int(%d) ["blksize"]=> int(%d) ["blocks"]=> int(%d) } SUCCESS ================================================ FILE: tests/swoole_iouring/setting.phpt ================================================ --TEST-- swoole_iouring: iouring setting test --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'iouring_flag' => SWOOLE_IOURING_SQPOLL, 'iouring_entries' => 4096, 'iouring_workers' => 16 ]); $http->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { $response->status(200, "status"); $response->end("Hello World"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_library/array_object/base.phpt ================================================ --TEST-- swoole_library/array_object: base --SKIPIF-- --FILE-- 'dua']; $array = _array(); Assert::true($array->isEmpty()); $array->__construct($data); Assert::false($array->isEmpty()); Assert::same($array->__toArray(), $data); Assert::same($array[0], $data[0]); Assert::isIterable($array); Assert::eq($array, _array()->unserialize($array->serialize())); $array->push('OK'); $array->pushBack($array->pop()); echo $array->popBack() . PHP_EOL; Assert::same($array->count(), count($data)); Assert::true($array->clear()->isEmpty()); Assert::isEmpty($array->__toArray()); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_library/database/mysqli.phpt ================================================ --TEST-- swoole_library/database: pdo --SKIPIF-- --FILE-- --EXPECTF-- Use %fs for %d queries ================================================ FILE: tests/swoole_library/database/pdo.phpt ================================================ --TEST-- swoole_library/database: pdo --SKIPIF-- --FILE-- --EXPECTF-- Use %fs for %d queries ================================================ FILE: tests/swoole_library/database/redis.phpt ================================================ --TEST-- swoole_library/database: pdo --SKIPIF-- --FILE-- --EXPECTF-- Use %fs for %d queries ================================================ FILE: tests/swoole_library/exec/exec/1.phpt ================================================ --TEST-- swoole_library/exec/exec: Test exec --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_library/exec/exec/2.phpt ================================================ --TEST-- swoole_library/exec/exec: Fix $output result inconsistency --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_library/exec/shell_exec/1.phpt ================================================ --TEST-- swoole_library/exec/shell_exec: shell_exec --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_library/multibyte_string_object/base.phpt ================================================ --TEST-- swoole_library/multibyte_string_object: base --SKIPIF-- --FILE-- substr(0)); var_dump((string) $str->substr(2, 2)); var_dump($str->contains('中国')); var_dump($str->contains('美国')); var_dump($str->startsWith('我')); var_dump($str->endsWith('不是')); var_dump($str->length()); ?> --EXPECT-- string(15) "我是中国人" string(6) "中国" bool(true) bool(false) bool(true) bool(false) int(5) ================================================ FILE: tests/swoole_library/name_resolver/1.phpt ================================================ --TEST-- swoole_library/name_resolver: resolve --SKIPIF-- --FILE-- [$ns]]); $html = base64_encode(random_bytes(rand(2048, 65536 * 2))); $pm = new SwooleTest\ProcessManager; $pm->initFreePorts(PORT_N); $pm->parentFunc = function ($pid) use ($pm, $ns, $html) { Coroutine::set(['name_resolver' => [$ns]]); run(function () use ($html) { swoole_loop_n(REQ_N, function () use ($html) { $client = new Client(SERVICE_NAME); $client->set(['max_retries' => PORT_N]); $r = $client->get('/'); Assert::true($r); Assert::eq($client->getBody(), $html); }); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $ns, $html) { $serv = new Server('127.0.0.1', $pm->getFreePort(0), SERVER_MODE_RANDOM); $serv->addListener('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', ]); $serv->on("workerStart", function ($serv, $workerId) use ($pm, $ns) { if ($workerId == 0) { swoole_loop_n(PORT_N, function ($i) use ($pm, $ns) { $ns->join(SERVICE_NAME, '127.0.0.1', $pm->getFreePort($i)); }); $pm->wakeup(); } }); $serv->on('request', function ($req, $resp) use ($pm, $html) { if ($req->server['server_port'] == $pm->getFreePort(1)) { $resp->status(503); $resp->end(); return; } $resp->end($html); }); $serv->on('beforeShutdown', function ($serv) use ($pm, $ns) { swoole_loop_n(PORT_N, function ($i) use ($pm, $ns) { $ns->leave(SERVICE_NAME, '127.0.0.1', $pm->getFreePort($i)); }); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_library/name_resolver/lookup.phpt ================================================ --TEST-- swoole_library/name_resolver: lookup --SKIPIF-- --FILE-- [$ns]]); const N = 4; run(function () use ($ns) { $test_name = 'test_resolver_1'; $nodes = []; swoole_loop_n(N, function () use (&$nodes, $test_name, $ns) { $node = ['port' => rand(1, 9999), 'ip' => '192.168.1.' . rand(1, 255)]; $nodes[] = $node; $ns->join($test_name, $node['ip'], $node['port']); }); $ctx = new Swoole\NameResolver\Context(AF_INET, true); swoole_loop_n(N * 2, function ($i) use (&$nodes, $test_name, $ns, $ctx) { $rs = swoole_name_resolver_lookup($test_name, $ctx); Assert::notEmpty($rs); [$ip, $port] = explode(':', $rs); $node = ['ip' => $ip, 'port' => $port]; Assert::true(in_array($node, $nodes)); }); swoole_loop_n(N, function ($i) use (&$nodes, $test_name, $ns) { $ns->leave($test_name, $nodes[$i]['ip'], $nodes[$i]['port']); }); }); echo "DONE\n"; ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_library/string_object/base.phpt ================================================ --TEST-- swoole_library/string_object: base --SKIPIF-- --FILE-- rtrim(); $array = $string->split('.'); Assert::same($array->count(), 3); Assert::same((string)$string->substr(_string($array[0])->length() + 1), 'swoole.com'); Assert::same((string)$string->upper(), 'WWW.SWOOLE.COM'); echo $string->upper()->substr(-8, 1) . 'K' . PHP_EOL; Assert::same((string)$string, 'www.swoole.com'); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_library/wait_group/normal.phpt ================================================ --TEST-- swoole_library/wait_group: base --SKIPIF-- --FILE-- add(); go(function() use ($wg, $i, &$ret) { co::sleep(0.1); $ret[$i] = $i; $wg->done(); }); } $wg->wait(); Assert::assert(count($ret) == 10); }); ?> --EXPECTF-- ================================================ FILE: tests/swoole_library/wait_group/timeout.phpt ================================================ --TEST-- swoole_library/wait_group: base --SKIPIF-- --FILE-- add(); go(function() use ($wg, $i, &$ret) { $time = rand(5,15)/10; co::sleep($time); $ret[$i] = $time; $wg->done(); }); } $std_time = 1; $wg->wait($std_time); $end_time = microtime(1); $used_time = $end_time - $start_time; echo "all done, use time $used_time\n"; Assert::assert(abs($std_time - $used_time) < 0.5); }); ?> --EXPECTF-- all done, use time %s ================================================ FILE: tests/swoole_lock/bug_5984.phpt ================================================ --TEST-- swoole_lock: Github Bug #5984 --SKIPIF-- --FILE-- SWOOLE_HOOK_ALL & ~SWOOLE_HOOK_CURL]); $fileEx = '/tmp/test_ex.txt'; file_put_contents($fileEx, '0'); $lockEx = file_get_contents($fileEx); Co\run(function () use ($fileEx) { for ($i = 0; $i < 50; $i++) { go(function () use ($i, $fileEx) { $fp = fopen($fileEx, 'r+'); flock($fp, LOCK_EX); $val = (int) file_get_contents($fileEx); usleep(100); file_put_contents($fileEx, (string) ($val + 1)); flock($fp, LOCK_UN); fclose($fp); }); } }); $val = (int) file_get_contents($fileEx); echo $val === 50 ? "✓ 通过 (计数=$val)\n" : "✗ 失败 (计数=$val, 预期50)\n"; ?> --EXPECT-- ✓ 通过 (计数=50) ================================================ FILE: tests/swoole_lock/lock_nb.phpt ================================================ --TEST-- swoole_lock: lock nb --SKIPIF-- --FILE-- lock()); $ret = $lock->lock(LOCK_EX | LOCK_NB); Assert::false($ret); $lock->unlock(); Assert::true($lock->lock(LOCK_EX | LOCK_NB)); ?> --EXPECT-- ================================================ FILE: tests/swoole_lock/lock_read.phpt ================================================ --TEST-- swoole_lock: lock read --SKIPIF-- --FILE-- lock(LOCK_SH); usleep(200_000); $lock->unlock(); }); $process1->start(); $process2 = new Process(function ($p) use ($lock) { $lock->lock(LOCK_SH); usleep(200_000); $lock->unlock(); }); $process2->start(); Process::wait(); Process::wait(); // Using shared locks, two processes will get locks at the same time and execute them concurrently Assert::lessThan(microtime(true) - $begin, 0.35); ?> --EXPECT-- ================================================ FILE: tests/swoole_lock/lock_timeout.phpt ================================================ --TEST-- swoole_lock: lock timeout --SKIPIF-- --FILE-- lock()); $start = microtime(true); $ret = $lock->lock(LOCK_EX, 0.2); Assert::false($ret); $end = microtime(true); Assert::eq($lock->errCode, SOCKET_ETIMEDOUT); Assert::greaterThanEq($end - $start, 0.2); ?> --EXPECT-- bool(true) ================================================ FILE: tests/swoole_lock/mutex.phpt ================================================ --TEST-- swoole_lock: mutex --SKIPIF-- --FILE-- lock(); $process1 = new Process(function ($p) use ($lock, $fp) { fwrite($fp, "[Child 1] Wait Lock\n"); $lock->lock(); usleep(10); fwrite($fp, "[Child 1] Get Lock\n"); $lock->unlock(); fwrite($fp, "[Child 1] exit\n"); }); $process1->start(); $process2 = new Process(function ($p) use ($lock, $fp) { fwrite($fp, "[Child 2] Sleep\n"); sleep(1); fwrite($fp, "[Child 2] Release Lock\n"); $lock->unlock(); fwrite($fp, "[Child 2] exit\n"); }); $process2->start(); Process::wait(); Process::wait(); ?> --EXPECTF-- [Master] Create Lock [Child 1] Wait Lock [Child 2] Sleep [Child 2] Release Lock [Child 2] exit [Child 1] Get Lock [Child 1] exit ================================================ FILE: tests/swoole_lock/spinlock_timeout.phpt ================================================ --TEST-- swoole_lock: lock timeout --SKIPIF-- --FILE-- lock()); $start = microtime(true); $ret = $lock->lock(LOCK_EX, 0.2); Assert::false($ret); $end = microtime(true); Assert::eq($lock->errCode, SOCKET_ETIMEDOUT); Assert::greaterThanEq($end - $start, 0.2); ?> --EXPECT-- bool(true) ================================================ FILE: tests/swoole_pdo_firebird/base.phpt ================================================ --TEST-- swoole_pdo_firebird: test hook pdo_firebird --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_FIREBIRD]); run(static function (): void { $db = PdoFirebirdTest::create(); $db->exec('CREATE TABLE test_table (id INTEGER PRIMARY KEY, name VARCHAR(50))'); $db->exec("INSERT INTO test_table VALUES (1, 'Firebird Test')"); $stmt = $db->query('SELECT * FROM test_table'); $result = $stmt->fetch(PDO::FETCH_ASSOC); $stmt = null; var_dump($result); $db->exec('DROP TABLE test_table'); }); echo "DONE\n"; ?> --EXPECTF-- array(2) { ["ID"]=> int(1) ["NAME"]=> string(13) "Firebird Test" } DONE ================================================ FILE: tests/swoole_pdo_firebird/pdo_firebird.inc ================================================ getMessage()); } } public static function create(): PDO { return new PDO(FIREBIRD_DSN, FIREBIRD_USER, FIREBIRD_PASSWORD); } } ================================================ FILE: tests/swoole_pdo_firebird/query.phpt ================================================ --TEST-- swoole_pdo_firebird: test query --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_FIREBIRD]); Co\run(static function (): void { $db = PdoFirebirdTest::create(); // 准备测试表 try { $db->exec('DROP TABLE concurrent_test'); try { $db->exec('COMMIT'); } catch (PDOException $e) { // 忽略COMMIT失败的错误 } } catch (PDOException $e) { // 表不存在时的错误可以忽略 } // 创建测试表 $db->exec('CREATE TABLE concurrent_test (id INTEGER PRIMARY KEY, name VARCHAR(100), age INTEGER)'); $stmt = $db->prepare('INSERT INTO concurrent_test (id, name, age) values (?, ?, ?)'); $list = []; for ($i = 0; $i < N; $i++) { $id = $i + 1; $name = base64_encode(random_bytes(8)); $age = random_int(18, 35); $stmt->bindValue(1, $id); $stmt->bindValue(2, $name); $stmt->bindValue(3, $age); $stmt->execute(); $list[] = [ 'id' => $id, 'name' => $name, 'age' => $age ]; } // 创建通道用于同步 $channel = new Co\Channel(N); foreach ($list as $rs) { Co\go(function () use ($rs, $channel) { $db = PdoFirebirdTest::create(); try { $statement = $db->query('SELECT * FROM concurrent_test WHERE id = ' . $rs['id'] . ' ROWS 1'); $result = $statement->fetch(PDO::FETCH_ASSOC); Assert::eq($result['ID'], $rs['id']); Assert::eq($result['NAME'], $rs['name']); Assert::eq($result['AGE'], $rs['age']); } catch (PDOException $e) { echo "Error in coroutine: " . $e->getMessage() . "\n"; } // 通知主协程当前协程已完成 $channel->push(true); }); } // 等待所有协程完成 for ($i = 0; $i < N; $i++) { $channel->pop(); } // 所有协程完成后再清理测试表 try { $db->exec('DROP TABLE concurrent_test'); } catch (PDOException $e) { // 忽略清理错误 } }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_firebird/sleep.phpt ================================================ --TEST-- swoole_pdo_firebird: test hook firebird sleep --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_FIREBIRD]); Co\run(static function (): void { $sleep_count = 0; Co\go(function () use (&$sleep_count) { $n = N; while ($n--) { Co::sleep(0.002); $sleep_count++; } }); $db = PdoFirebirdTest::create(); $iterations = 50; for ($i = 0; $i < $iterations; $i++) { $statement = $db->query('SELECT COUNT(*) FROM RDB$RELATIONS'); $statement->fetch(); } Assert::greaterThanEq($sleep_count, 10); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_firebird/transaction.phpt ================================================ --TEST-- swoole_pdo_firebird: test transaction --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_FIREBIRD]); run(static function (): void { $db = PdoFirebirdTest::create(); // 先尝试删除可能存在的表(忽略错误) try { $db->exec('DROP TABLE transaction_test'); // 使用try-catch包围COMMIT try { $db->exec('COMMIT'); } catch (PDOException $e) { // 忽略COMMIT失败的错误 } } catch (PDOException $e) { // 表不存在时的错误可以忽略 } // 创建测试表 - 将value改为test_value以避免保留关键字冲突 $db->exec('CREATE TABLE transaction_test (id INTEGER PRIMARY KEY, test_value VARCHAR(50))'); try { // 开始事务 $db->beginTransaction(); // 插入数据 - 更新列名 $db->exec("INSERT INTO transaction_test VALUES (1, 'Value 1')"); $db->exec("INSERT INTO transaction_test VALUES (2, 'Value 2')"); // 提交事务 $db->commit(); // 验证数据已插入 $stmt = $db->query('SELECT COUNT(*) FROM transaction_test'); $count = $stmt->fetchColumn(); echo "Count after commit: ", $count, "\n"; // 再次开始事务 $db->beginTransaction(); $db->exec("INSERT INTO transaction_test VALUES (3, 'Value 3')"); // 回滚事务 $db->rollback(); // 验证回滚后的数据 $stmt = $db->query('SELECT COUNT(*) FROM transaction_test'); $count = $stmt->fetchColumn(); echo "Count after rollback: ", $count, "\n"; } catch (PDOException $e) { echo "Error: ", $e->getMessage(), "\n"; } // 清理测试表 try { $db->exec('DROP TABLE transaction_test'); } catch (PDOException $e) { // 如果删除失败,尝试使用更安全的方式处理事务 try { // 首先尝试回滚任何活动事务 $db->rollBack(); } catch (PDOException $e) { // 忽略回滚失败的错误 } // 再次尝试删除表 try { $db->exec('DROP TABLE transaction_test'); } catch (PDOException $e) { // 忽略第二次删除失败的错误 } } }); echo "DONE\n"; ?> --EXPECT-- Count after commit: 2 Count after rollback: 2 DONE ================================================ FILE: tests/swoole_pdo_odbc/base.phpt ================================================ --TEST-- swoole_pdo_odbc: test hook pdo_odbc --SKIPIF-- --FILE-- prepare('show tables'); $statement->execute(); Assert::greaterThan(count($statement->fetchAll(PDO::FETCH_COLUMN)), 1); }); Co\go(function () { $pdo = new PDO(ODBC_DSN); $statement = $pdo->prepare('show tables'); $statement->execute(); Assert::greaterThan(count($statement->fetchAll(PDO::FETCH_COLUMN)), 1); }); }); echo "DONE\n"; ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_pdo_odbc/blocking.phpt ================================================ --TEST-- swoole_pdo_odbc: test hook pgsql --SKIPIF-- --FILE-- prepare('SELECT sleep(1) ss'); $statement->execute(); Assert::eq($sleep_count, 0); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_odbc/query.phpt ================================================ --TEST-- swoole_pdo_odbc: test query --SKIPIF-- --FILE-- prepare('INSERT INTO ckl (name, domain, path) values (?, ?, ?)'); $list = []; for ($i = 0; $i < N; $i++) { $row = [ 'name' => base64_encode(random_bytes(8)), 'domain' => 'domain-' . random_int(10000, 99999), 'path' => '/' . uniqid() . '/' . $i, ]; $list[] = $row; $stmt->bindValue(1, $row['name']); $stmt->bindValue(2, $row['domain']); $stmt->bindValue(3, $row['path']); $stmt->execute(); } foreach ($list as $rs) { Co\go(function () use ($rs) { $pdo = new PDO(ODBC_DSN); $statement = $pdo->query('select name, domain, path from ckl where path = "' . $rs['path'] . '" limit 1'); Assert::eq($statement->fetch(PDO::FETCH_ASSOC), $rs); }); } }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_odbc/race.phpt ================================================ --TEST-- swoole_pdo_odbc: race --SKIPIF-- --FILE-- prepare('SELECT sleep(1) ss'); try { $statement->execute(); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); } catch (\PDOException $e) { $msg[] = $e->getMessage(); } }); } Assert::count($msg, 0); }); Assert::greaterThanEq(microtime(true) - $begin, 2); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_odbc/server_info.phpt ================================================ --TEST-- swoole_pdo_odbc: test hook pgsql --SKIPIF-- --FILE-- getAttribute(PDO::ATTR_SERVER_INFO); Assert::eq(strtolower($info), 'mysql'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_odbc/sleep.phpt ================================================ --TEST-- swoole_pdo_odbc: test hook pgsql --SKIPIF-- --FILE-- prepare('SELECT sleep(1) ss'); $statement->execute(); Assert::eq($sleep_count, N); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_odbc/transaction.phpt ================================================ --TEST-- swoole_pdo_odbc: test query --SKIPIF-- --FILE-- prepare('INSERT INTO ckl (name, domain, path) values (?, ?, ?)'); $row = [ 'name' => base64_encode(random_bytes(8)), 'domain' => 'domain-' . random_int(10000, 99999), 'path' => '/' . uniqid() . '/' . 0, ]; $stmt->bindValue(1, $row['name']); $stmt->bindValue(2, $row['domain']); $stmt->bindValue(3, $row['path']); $stmt->execute(); }; $countTable = function ($pdo) { return $pdo->query('select count(*) c from ckl')->fetch(PDO::FETCH_ASSOC)['c']; }; $insertToTable($pdo); var_dump('insert'); $c1 = $countTable($pdo); $pdo->beginTransaction(); $insertToTable($pdo); $pdo->rollBack(); var_dump('rollback'); Assert::eq($countTable($pdo), $c1); $pdo->beginTransaction(); $insertToTable($pdo); $pdo->commit(); var_dump('commit'); Assert::eq($countTable($pdo), $c1 + 1); }); echo "Done\n"; ?> --EXPECTF-- string(6) "insert" string(8) "rollback" string(6) "commit" Done ================================================ FILE: tests/swoole_pdo_oracle/bug41996.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI Bug #41996 (Problem accessing Oracle ROWID) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $stmt = $db->prepare('SELECT rowid FROM dual'); $stmt->execute(); $row = $stmt->fetch(); var_dump(strlen($row[0]) > 0); }); ?> --EXPECT-- bool(true) ================================================ FILE: tests/swoole_pdo_oracle/bug44301.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI Bug #44301 (Segfault when an exception is thrown on persistent connections) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_PERSISTENT, true); try { $stmt = $db->prepare('SELECT * FROM no_table'); $stmt->execute(); } catch (PDOException $e) { print $e->getMessage(); } $db = null; }); ?> --EXPECTF-- SQLSTATE[HY000]: General error: 942 OCIStmtExecute: ORA-00942: table or view %Sdoes not exist (%soci_statement.c:%d) ================================================ FILE: tests/swoole_pdo_oracle/bug46274.phpt ================================================ --TEST-- swoole_pdo_oracle:ATTR_STRINGIFY_FETCHES and blob) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); try { $db->exec("DROP TABLE test_one_blob"); } catch (Exception $e) { } $db->beginTransaction(); $db->query('CREATE TABLE test_one_blob (id INT NOT NULL, blob1 BLOB)'); $stmt = $db->prepare("INSERT INTO test_one_blob (id, blob1) VALUES (:id, EMPTY_BLOB()) RETURNING blob1 INTO :foo"); $data = 'foo'; $blob = fopen('php://memory', 'a'); fwrite($blob, $data); rewind($blob); $id = 1; $stmt->bindparam(':id', $id); $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); $stmt->execute(); $data = ''; $blob = fopen('php://memory', 'a'); fwrite($blob, $data); rewind($blob); $id = 1; $stmt->bindparam(':id', $id); $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); $stmt->execute(); $res = $db->query("SELECT blob1 from test_one_blob"); // Resource var_dump($res->fetch()); // Empty string var_dump($res->fetch()); $db->exec("DROP TABLE test_one_blob"); }); ?> --EXPECT-- array(2) { ["blob1"]=> string(3) "foo" [0]=> string(3) "foo" } array(2) { ["blob1"]=> string(0) "" [0]=> string(0) "" } ================================================ FILE: tests/swoole_pdo_oracle/bug46274_2.phpt ================================================ --TEST-- swoole_pdo_oracle:ATTR_STRINGIFY_FETCHES and blob) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); try { $db->exec("DROP TABLE test_one_blob"); } catch (Exception $e) { } $db->beginTransaction(); $db->query('CREATE TABLE test_one_blob (id INT NOT NULL, blob1 BLOB)'); $stmt = $db->prepare("INSERT INTO test_one_blob (id, blob1) VALUES (:id, EMPTY_BLOB()) RETURNING blob1 INTO :foo"); $data = 'foo'; $blob = fopen('php://memory', 'a'); fwrite($blob, $data); rewind($blob); $id = 1; $stmt->bindparam(':id', $id); $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); $stmt->execute(); $data = ''; $blob = fopen('php://memory', 'a'); fwrite($blob, $data); rewind($blob); $id = 1; $stmt->bindparam(':id', $id); $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); $stmt->execute(); $res = $db->query("SELECT blob1 from test_one_blob"); // Resource var_dump($row = $res->fetch()); var_dump(fread($row[0], 1024)); fclose($row[0]); // Empty string var_dump($row = $res->fetch()); var_dump(fread($row[0], 1024)); fclose($row[0]); $db->exec("DROP TABLE test_one_blob"); }); ?> --EXPECTF-- array(2) { ["blob1"]=> resource(%d) of type (stream) [0]=> resource(%d) of type (stream) } string(3) "foo" array(2) { ["blob1"]=> resource(%d) of type (stream) [0]=> resource(%d) of type (stream) } string(0) "" ================================================ FILE: tests/swoole_pdo_oracle/bug54379.phpt ================================================ --TEST-- swoole_pdo_oracle: UTF-8 output gets truncated) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { $db->exec("DROP TABLE test"); } catch (Exception $e) { } $db->exec("CREATE TABLE test (col1 NVARCHAR2(20))"); $db->exec("INSERT INTO test VALUES('12345678901234567890')"); $db->exec("INSERT INTO test VALUES('あいうえおかきくけこさしすせそたちつてと')"); $stmt = $db->prepare("SELECT * FROM test"); $stmt->execute(); var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); $db->exec("DROP TABLE test"); }); ?> --EXPECT-- array(2) { [0]=> array(1) { ["col1"]=> string(20) "12345678901234567890" } [1]=> array(1) { ["col1"]=> string(60) "あいうえおかきくけこさしすせそたちつてと" } } ================================================ FILE: tests/swoole_pdo_oracle/bug57702.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI Bug #57702 (Multi-row BLOB fetches) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); // Note the PDO test setup sets PDO::ATTR_STRINGIFY_FETCHES to true // (and sets PDO::ATTR_CASE to PDO::CASE_LOWER) $query = "begin execute immediate 'drop table bug57702'; exception when others then if sqlcode <> -942 then raise; end if; end;"; $stmt = $db->prepare($query); $stmt->execute(); $query = "create table bug57702 (id number, data1 blob, data2 blob)"; $stmt = $db->prepare($query); $stmt->execute(); function do_insert($db, $id, $data1, $data2) { $db->beginTransaction(); $stmt = $db->prepare("insert into bug57702 (id, data1, data2) values (:id, empty_blob(), empty_blob()) returning data1, data2 into :blob1, :blob2"); $stmt->bindParam(':id', $id); $stmt->bindParam(':blob1', $blob1, PDO::PARAM_LOB); $stmt->bindParam(':blob2', $blob2, PDO::PARAM_LOB); $blob1 = null; $blob2 = null; $stmt->execute(); fwrite($blob1, $data1); fclose($blob1); fwrite($blob2, $data2); fclose($blob2); $db->commit(); } do_insert($db, 1, "row 1 col 1", "row 1 col 2"); do_insert($db, 2, "row 2 col 1", "row 2 col 2"); //////////////////// echo "First Query\n"; // Fetch it back $stmt = $db->prepare('select data1, data2 from bug57702 order by id'); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); var_dump($row['data1']); var_dump($row['data2']); $row = $stmt->fetch(PDO::FETCH_ASSOC); var_dump($row['data1']); var_dump($row['data2']); //////////////////// echo "\nSecond Query\n"; foreach($db->query("select data1 as d1, data2 as d2 from bug57702 order by id") as $row) { var_dump($row['d1']); var_dump($row['d2']); } //////////////////// echo "\nThird Query\n"; $stmt = $db->prepare('select data1 as d3_1, data2 as d3_2 from bug57702 order by id'); $rs = $stmt->execute(); $stmt->bindColumn('d3_1' , $clob1, PDO::PARAM_LOB); $stmt->bindColumn('d3_2' , $clob2, PDO::PARAM_LOB); while ($stmt->fetch(PDO::FETCH_BOUND)) { var_dump($clob1); var_dump($clob2); } //////////////////// echo "\nFourth Query\n"; $a = array(); $i = 0; foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { $a[$i][0] = $row['d4_1']; $a[$i][1] = $row['d4_2']; $i++; } for ($i = 0; $i < count($a); $i++) { var_dump($a[$i][0]); var_dump($a[$i][1]); } //////////////////// echo "\nFifth Query\n"; $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams // Since each column only has one lob descriptor, the last row is // shown twice because the lob descriptor for each column is reused in // the stream $a = array(); $i = 0; foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { $a[$i][0] = $row['d4_1']; $a[$i][1] = $row['d4_2']; $i++; } for ($i = 0; $i < count($a); $i++) { var_dump(stream_get_contents($a[$i][0])); var_dump(stream_get_contents($a[$i][1])); } //////////////////// echo "\nSixth Query\n"; $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams $a = array(); $i = 0; foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { $a[$i][0] = $row['d4_1']; $a[$i][1] = $row['d4_2']; var_dump(stream_get_contents($a[$i][0])); var_dump(stream_get_contents($a[$i][1])); $i++; } // Cleanup $query = "drop table bug57702"; $stmt = $db->prepare($query); $stmt->execute(); print "done\n"; }); ?> --EXPECTF-- First Query resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) Second Query resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) Third Query resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) Fourth Query resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) resource(%d) of type (stream) Fifth Query string(11) "row 2 col 1" string(11) "row 2 col 2" string(11) "row 2 col 1" string(11) "row 2 col 2" Sixth Query string(11) "row 1 col 1" string(11) "row 1 col 2" string(11) "row 2 col 1" string(11) "row 2 col 2" done ================================================ FILE: tests/swoole_pdo_oracle/bug60994.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 characters) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); $dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB, data2 NCLOB)'); $id = null; $insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data, data2) VALUES (:id, :data, :data2)'); $insert->bindParam(':id', $id, \PDO::PARAM_STR); $select = $dbh->prepare("SELECT data, data2 FROM pdo_oci_bug60994 WHERE id = :id"); echo PHP_EOL, 'Test 1: j', PHP_EOL; $string1 = 'abc' . str_repeat('j', 8187) . 'xyz'; // 8193 chars total works fine here (even 1 million works fine, subject to memory_limit) $id = 1; $insert->bindParam(':data', $string1, \PDO::PARAM_STR, strlen($string1)); // length in bytes $insert->bindParam(':data2', $string1, \PDO::PARAM_STR, strlen($string1)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); $row = $select->fetch(); $stream1 = stream_get_contents($row['DATA']); $start1 = mb_substr($stream1, 0, 10); $ending1 = mb_substr($stream1, -10); echo 'size of string1 is ', strlen($string1), ' bytes, ', mb_strlen($string1), ' chars.', PHP_EOL; echo 'size of stream1 is ', strlen($stream1), ' bytes, ', mb_strlen($stream1), ' chars.', PHP_EOL; echo 'beg of stream1 is ', $start1, PHP_EOL; echo 'end of stream1 is ', $ending1, PHP_EOL; if ($string1 != $stream1 || $stream1 != stream_get_contents($row['DATA2'])) { echo 'Expected nclob value to match clob value for stream1', PHP_EOL; } echo PHP_EOL, 'Test 2: £', PHP_EOL; $string2 = 'abc' . str_repeat('£', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 2; $insert->bindParam(':data', $string2, \PDO::PARAM_STR, strlen($string2)); // length in bytes $insert->bindParam(':data2', $string2, \PDO::PARAM_STR, strlen($string2)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); $row = $select->fetch(); $stream2 = stream_get_contents($row['DATA']); $start2 = mb_substr($stream2, 0, 10); $ending2 = mb_substr($stream2, -10); echo 'size of string2 is ', strlen($string2), ' bytes, ', mb_strlen($string2), ' chars.', PHP_EOL; echo 'size of stream2 is ', strlen($stream2), ' bytes, ', mb_strlen($stream2), ' chars.', PHP_EOL; echo 'beg of stream2 is ', $start2, PHP_EOL; echo 'end of stream2 is ', $ending2, PHP_EOL; if ($string2 != $stream2 || $stream2 != stream_get_contents($row['DATA2'])) { echo 'Expected nclob value to match clob value for stream2', PHP_EOL; } echo PHP_EOL, 'Test 3: Җ', PHP_EOL; $string3 = 'abc' . str_repeat('Җ', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 3; $insert->bindParam(':data', $string3, \PDO::PARAM_STR, strlen($string3)); // length in bytes $insert->bindParam(':data2', $string3, \PDO::PARAM_STR, strlen($string3)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); $row = $select->fetch(); $stream3 = stream_get_contents($row['DATA']); $start3 = mb_substr($stream3, 0, 10); $ending3 = mb_substr($stream3, -10); echo 'size of string3 is ', strlen($string3), ' bytes, ', mb_strlen($string3), ' chars.', PHP_EOL; echo 'size of stream3 is ', strlen($stream3), ' bytes, ', mb_strlen($stream3), ' chars.', PHP_EOL; echo 'beg of stream3 is ', $start3, PHP_EOL; echo 'end of stream3 is ', $ending3, PHP_EOL; if ($string3 != $stream3 || $stream3 != stream_get_contents($row['DATA2'])) { echo 'Expected nclob value to match clob value for stream3', PHP_EOL; } echo PHP_EOL, 'Test 4: の', PHP_EOL; $string4 = 'abc' . str_repeat('の', 8187) . 'xyz'; // 8193 chars total is when it breaks $id = 4; $insert->bindParam(':data', $string4, \PDO::PARAM_STR, strlen($string4)); // length in bytes $insert->bindParam(':data2', $string4, \PDO::PARAM_STR, strlen($string4)); $insert->execute(); $select->bindParam(':id', $id, \PDO::PARAM_STR); $select->execute(); $row = $select->fetch(); $stream4 = stream_get_contents($row['DATA']); $start4 = mb_substr($stream4, 0, 10); $ending4 = mb_substr($stream4, -10); echo 'size of string4 is ', strlen($string4), ' bytes, ', mb_strlen($string4), ' chars.', PHP_EOL; echo 'size of stream4 is ', strlen($stream4), ' bytes, ', mb_strlen($stream4), ' chars.', PHP_EOL; echo 'beg of stream4 is ', $start4, PHP_EOL; echo 'end of stream4 is ', $ending4, PHP_EOL; if ($string4 != $stream4 || $stream4 != stream_get_contents($row['DATA2'])) { echo 'Expected nclob value to match clob value for stream4', PHP_EOL; } $dbh->exec('DROP TABLE pdo_oci_bug60994'); }); ?> --EXPECT-- Test 1: j size of string1 is 8193 bytes, 8193 chars. size of stream1 is 8193 bytes, 8193 chars. beg of stream1 is abcjjjjjjj end of stream1 is jjjjjjjxyz Test 2: £ size of string2 is 16380 bytes, 8193 chars. size of stream2 is 16380 bytes, 8193 chars. beg of stream2 is abc£££££££ end of stream2 is £££££££xyz Test 3: Җ size of string3 is 16380 bytes, 8193 chars. size of stream3 is 16380 bytes, 8193 chars. beg of stream3 is abcҖҖҖҖҖҖҖ end of stream3 is ҖҖҖҖҖҖҖxyz Test 4: の size of string4 is 24567 bytes, 8193 chars. size of stream4 is 24567 bytes, 8193 chars. beg of stream4 is abcののののののの end of stream4 is のののののののxyz ================================================ FILE: tests/swoole_pdo_oracle/bug_33707.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI Bug #33707 (Errors in select statements not reported) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $rs = $db->query('select blah from a_table_that_does_not_exist'); var_dump($rs); var_dump($db->errorInfo()); }); ?> --EXPECTF-- bool(false) array(3) { [0]=> string(5) "HY000" [1]=> int(942) [2]=> string(%d) "OCIStmtExecute: ORA-00942: table or view %Sdoes not exist (%s:%d)" } ================================================ FILE: tests/swoole_pdo_oracle/checkliveness.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI checkliveness (code coverage) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { try { $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD, array(PDO::ATTR_PERSISTENT => true)); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); exit; } // This triggers the call to check liveness try { $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD, array(PDO::ATTR_PERSISTENT => true)); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(); exit; } $db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); try { $stmt = $db->prepare('SELECT * FROM dual'); $stmt->execute(); $row = $stmt->fetch(); var_dump($row); } catch (PDOException $e) { print $e->getMessage(); } $db = null; }); ?> --EXPECT-- array(2) { ["DUMMY"]=> string(1) "X" [0]=> string(1) "X" } ================================================ FILE: tests/swoole_pdo_oracle/coroutint.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI coroutine --SKIPIF-- --FILE-- SWOOLE_HOOK_ALL]); run(function() { $db = PdoOracleTest::create(); $db->exec("create table test (id int)"); for($i = 0; $i < 10; $i++) { go(function () use($db, $i){ $stmt = $db->prepare("insert into test values (?)"); $stmt->execute([$i]); $stmt = $db->prepare("select id from test where id = ?"); $stmt->execute([$i]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); }); } sleep(1); $db->exec("drop table test"); }); ?> --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_oracle/oci_success_with_info.phpt ================================================ --TEST-- swoole_pdo_oracle: Handling OCI_SUCCESS_WITH_INFO --SKIPIF-- --FILE-- exec(<<<'SQL' BEGIN EXECUTE IMMEDIATE 'DROP PROFILE BUG77120_PROFILE CASCADE'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -2380 THEN RAISE; END IF; END; SQL ); } function dropUser(PDO $conn): void { $conn->exec(<<<'SQL' BEGIN EXECUTE IMMEDIATE 'DROP USER BUG77120_USER CASCADE'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -1918 THEN RAISE; END IF; END; SQL ); } function triggerCompilationError(PDO $conn): void { $conn->exec(<<<'SQL' CREATE OR REPLACE FUNCTION BUG77120(INT A) RETURN INT AS BEGIN RETURN 0; END; SQL ); } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); run(function() { $conn = connectAsAdmin(); dropUser($conn); dropProfile($conn); $password = bin2hex(random_bytes(8)); $conn->exec('CREATE PROFILE BUG77120_PROFILE LIMIT PASSWORD_LIFE_TIME 1/86400 PASSWORD_GRACE_TIME 1'); $conn->exec('CREATE USER BUG77120_USER IDENTIFIED BY "' . $password . '" PROFILE BUG77120_PROFILE'); $conn->exec('GRANT CREATE SESSION TO BUG77120_USER'); // let the password expire sleep(3); // 2 seconds is causing random test failures $conn = connectAsUser('BUG77120_USER', $password); var_dump($conn->errorInfo()); $conn = connectAsAdmin(); dropUser($conn); dropProfile($conn); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); triggerCompilationError($conn); var_dump($conn->errorInfo()); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); triggerCompilationError($conn); var_dump($conn->errorInfo()); }); ?> --EXPECTF-- array(3) { [0]=> string(5) "HY000" [1]=> int(28002) [2]=> string(%d) "OCISessionBegin: OCI_SUCCESS_WITH_INFO: ORA-28002: %s (%s:%d)" } array(3) { [0]=> string(5) "HY000" [1]=> int(24344) [2]=> string(%d) "OCIStmtExecute: OCI_SUCCESS_WITH_INFO: ORA-24344: %s (%s:%d)" } array(3) { [0]=> string(5) "HY000" [1]=> int(24344) [2]=> string(%d) "OCIStmtExecute: OCI_SUCCESS_WITH_INFO: ORA-24344: %s (%s:%d)" } ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_action.phpt ================================================ --TEST-- swoole_pdo_oracle: Setting session action --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $query = 'select action from v$session where sid = sys_context(\'USERENV\', \'SID\')'; $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'ACTION NOT SET: '; var_dump($row['action']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, "some action")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'ACTION SET: '; var_dump($row['action']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, "something else!")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'ACTION RESET: '; var_dump($row['action']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, null)); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'ACTION NULLED: '; var_dump($row['action']); echo "Done\n"; }); ?> --EXPECT-- ACTION NOT SET: NULL bool(true) ACTION SET: string(11) "some action" bool(true) ACTION RESET: string(15) "something else!" bool(true) ACTION NULLED: NULL Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_1.phpt ================================================ --TEST-- swoole_pdo_oracle: Basic autocommit functionality --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $dbh->exec("drop table pdo_ac_tab"); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); print "PDO::ATTR_AUTOCOMMIT: Default: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); echo "Change setting to false - "; $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); echo "Change setting back to true - "; $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, true); print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); // Use 2nd connection to check that autocommit does commit echo "Insert data\n"; $dbh->exec("create table pdo_ac_tab (col1 varchar2(20))"); $dbh->exec("insert into pdo_ac_tab (col1) values ('some data')"); $dbh2 = PdoOracleTest::create(); echo "Second connection should be able to see committed data\n"; $s = $dbh2->prepare("select col1 from pdo_ac_tab"); $s->execute(); while ($r = $s->fetch()) { echo "Data is: " . $r[0] . "\n"; } $dbh->exec("drop table pdo_ac_tab"); echo "Done\n"; }); ?> --EXPECT-- PDO::ATTR_AUTOCOMMIT: Default: bool(true) Change setting to false - PDO::ATTR_AUTOCOMMIT: bool(false) Change setting back to true - PDO::ATTR_AUTOCOMMIT: bool(true) Insert data Second connection should be able to see committed data Data is: some data Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_2.phpt ================================================ --TEST-- swoole_pdo_oracle: beginTransaction and native transactions --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $dbh->exec("drop table pdo_ac_tab"); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->exec("create table pdo_ac_tab (col1 varchar2(25))"); echo "Test 1 Check beginTransaction insertion\n"; $dbh->beginTransaction(); try { $dbh->exec("insert into pdo_ac_tab (col1) values ('data 1')"); $dbh->exec("insert into pdo_ac_tab (col1) values ('data 2')"); $dbh->commit(); } catch (PDOException $e) { echo "Caught unexpected exception at line " . __LINE__ . "\n"; echo $e->getMessage() . "\n"; $dbh->rollback(); } echo "Test 2 Cause an exception and test beginTransaction rollback\n"; $dbh->beginTransaction(); try { $dbh->exec("insert into pdo_ac_tab (col1) values ('not committed #1')"); $dbh->exec("insert into pdo_ac_tab (col1) values ('data that is too long to fit and will barf')"); $dbh->commit(); } catch (PDOException $e) { echo "Caught expected exception at line " . __LINE__ . "\n"; echo $e->getMessage() . "\n"; $dbh->rollback(); } echo "Test 3 Setting ATTR_AUTOCOMMIT to true will commit and end the transaction\n"; $dbh->exec("insert into pdo_ac_tab (col1) values ('data 3')"); $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, true); print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); try { $dbh->rollback(); } catch (PDOException $e) { echo "Caught expected exception at line " . __LINE__ . "\n"; echo $e->getMessage() . "\n"; } echo "Test 4 Setting ATTR_AUTOCOMMIT to false will commit and end the transaction\n"; $dbh->beginTransaction(); $dbh->exec("insert into pdo_ac_tab (col1) values ('data 4')"); $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); try { $dbh->rollback(); } catch (PDOException $e) { echo "Caught expected exception at line " . __LINE__ . "\n"; echo $e->getMessage() . "\n"; } echo "Test 5 Handle transactions ourselves\n"; print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); $dbh->exec("insert into pdo_ac_tab (col1) values ('not committed #2')"); $dbh->exec("rollback"); $dbh->exec("insert into pdo_ac_tab (col1) values ('data 5')"); $dbh->exec("insert into pdo_ac_tab (col1) values ('data 6')"); $dbh->exec("commit"); // Open new connection to really verify what was inserted $dbh2 = PdoOracleTest::create(); echo "Query Results are:\n"; $s = $dbh2->prepare("select col1 from pdo_ac_tab"); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Done\n"; }); ?> --EXPECTF-- Test 1 Check beginTransaction insertion Test 2 Cause an exception and test beginTransaction rollback Caught expected exception at line %d SQLSTATE[HY000]: General error: 12899 OCIStmtExecute: ORA-12899: %s %s Test 3 Setting ATTR_AUTOCOMMIT to true will commit and end the transaction PDO::ATTR_AUTOCOMMIT: bool(true) Caught expected exception at line %d There is no active transaction Test 4 Setting ATTR_AUTOCOMMIT to false will commit and end the transaction PDO::ATTR_AUTOCOMMIT: bool(false) Caught expected exception at line %d There is no active transaction Test 5 Handle transactions ourselves PDO::ATTR_AUTOCOMMIT: bool(false) Query Results are: data 1 data 2 data 3 data 4 data 5 data 6 Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_3.phpt ================================================ --TEST-- swoole_pdo_oracle: closing a connection in non-autocommit mode commits data --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); // Check connection can be created with AUTOCOMMIT off $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); $dbh->exec("drop table pdo_ac_tab"); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); print "PDO::ATTR_AUTOCOMMIT: "; var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); echo "Insert data\n"; $dbh->exec("create table pdo_ac_tab (col1 varchar2(20))"); $dbh->exec("insert into pdo_ac_tab (col1) values ('some data')"); $dbh = null; // close first connection echo "Second connection should be able to see committed data\n"; $dbh2 = PdoOracleTest::create(); $s = $dbh2->prepare("select col1 from pdo_ac_tab"); $s->execute(); while ($r = $s->fetch()) { echo "Data is: " . $r[0] . "\n"; } $dbh2->exec("drop table pdo_ac_tab"); echo "Done\n"; }); ?> --EXPECT-- PDO::ATTR_AUTOCOMMIT: bool(false) Insert data Second connection should be able to see committed data Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_call_timeout.phpt ================================================ --TEST-- swoole_pdo_oracle: Setting and using call timeout --SKIPIF-- getAttribute(PDO::ATTR_CLIENT_VERSION), $matches); if (!(isset($matches[0]) && $matches[0] >= 18)) { die("skip works only with Oracle 18c or greater version of Oracle client libraries"); } ?> --FILE-- prepare("begin dbms_lock.sleep(:t); end;"); if (!$stmt) { $error = $dbh->errorInfo(); echo "Prepare error was ", $error[2], "\n"; return; } $stmt->bindParam(":t", $t, PDO::PARAM_INT); $r = $stmt->execute(); if ($r) { echo "Execute succeeded\n"; } else { $error = $dbh->errorInfo(); echo "Execute error was ", $error[2], "\n"; } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); echo "Test 1\n"; $dbh->setAttribute(PDO::OCI_ATTR_CALL_TIMEOUT, 4000); // milliseconds echo "call timeout:\n"; var_dump($dbh->getAttribute(PDO::OCI_ATTR_CALL_TIMEOUT)); $r = mysleep($dbh, 8); // seconds }); ?> ===DONE=== --EXPECTF-- Test 1 call timeout: int(4000) Execute error was OCIStmtExecute: ORA-%r(03136|03156)%r: %s (%s:%d) ===DONE=== ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_case.phpt ================================================ --TEST-- swoole_pdo_oracle: Column Case --SKIPIF-- --FILE-- getAttribute(PDO::ATTR_CASE)); $s = $dbh->prepare("select dummy from dual"); $s->execute(); while ($r = $s->fetch(PDO::FETCH_ASSOC)) { var_dump($r); } } function do_query2($dbh, $mode) { echo "Mode desired is $mode\n"; $s = $dbh->prepare("select dummy from dual", array(PDO::ATTR_CASE, $mode)); $s->execute(); while ($r = $s->fetch(PDO::FETCH_ASSOC)) { var_dump($r); } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "Test 1 - Force column names to lower case\n"; $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); do_query1($dbh); echo "Test 2 - Leave column names as returned by the database driver\n"; $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); do_query1($dbh); echo "Test 3 - Force column names to upper case\n"; $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); do_query1($dbh); echo "Test 4 - Setting on statement has no effect. Attempt lower case but get upper\n"; $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); // reset do_query2($dbh, PDO::CASE_LOWER); echo "Done\n"; }); ?> --EXPECT-- Test 1 - Force column names to lower case int(2) array(1) { ["dummy"]=> string(1) "X" } Test 2 - Leave column names as returned by the database driver int(0) array(1) { ["DUMMY"]=> string(1) "X" } Test 3 - Force column names to upper case int(1) array(1) { ["DUMMY"]=> string(1) "X" } Test 4 - Setting on statement has no effect. Attempt lower case but get upper Mode desired is 2 array(1) { ["DUMMY"]=> string(1) "X" } Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_client.phpt ================================================ --TEST-- swoole_pdo_oracle: Client version --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); echo "ATTR_CLIENT_VERSION: "; $cv = $dbh->getAttribute(PDO::ATTR_CLIENT_VERSION); var_dump($cv); $s = explode(".", $cv); if (count($s) > 1 && (($s[0] == 10 && $s[1] >= 2) || $s[0] >= 11)) { if (count($s) != 5) { echo "Wrong number of values in array\nVersion was: "; var_dump($cv); } else { echo "Version OK, so far as can be portably checked\n"; } } else { if (count($s) != 2) { echo "Wrong number of values in array\nVersion was: "; var_dump($cv); } else { echo "Version OK, so far as can be portably checked\n"; } } echo "Done\n"; }); ?> --EXPECTF-- ATTR_CLIENT_VERSION: string(%d) "%d.%s" Version OK, so far as can be portably checked Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_client_identifier.phpt ================================================ --TEST-- swoole_pdo_oracle: Setting session client identifier --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $query = 'select client_identifier from v$session where sid = sys_context(\'USERENV\', \'SID\')'; $dbh = PdoOracleTest::create(); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_IDENTIFIER NOT SET: '; var_dump($row['client_identifier']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, "some client identifier")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_IDENTIFIER SET: '; var_dump($row['client_identifier']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, "something else!")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_IDENTIFIER RESET: '; var_dump($row['client_identifier']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, null)); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_IDENTIFIER NULLED: '; var_dump($row['client_identifier']); echo "Done\n"; }); ?> --EXPECT-- CLIENT_IDENTIFIER NOT SET: NULL bool(true) CLIENT_IDENTIFIER SET: string(22) "some client identifier" bool(true) CLIENT_IDENTIFIER RESET: string(15) "something else!" bool(true) CLIENT_IDENTIFIER NULLED: NULL Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_client_info.phpt ================================================ --TEST-- swoole_pdo_oracle: Setting session client info --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $query = 'select client_info from v$session where sid = sys_context(\'USERENV\', \'SID\')'; $dbh = PdoOracleTest::create(); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_INFO NOT SET: '; var_dump($row['client_info']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, "some client info")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_INFO SET: '; var_dump($row['client_info']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, "something else!")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_INFO RESET: '; var_dump($row['client_info']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, null)); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'CLIENT_INFO NULLED: '; var_dump($row['client_info']); echo "Done\n"; }); ?> --EXPECT-- CLIENT_INFO NOT SET: NULL bool(true) CLIENT_INFO SET: string(16) "some client info" bool(true) CLIENT_INFO RESET: string(15) "something else!" bool(true) CLIENT_INFO NULLED: NULL Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_drivername.phpt ================================================ --TEST-- swoole_pdo_oracle: verify driver name --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); var_dump($dbh->getAttribute(PDO::ATTR_DRIVER_NAME)); echo "Done\n"; }); ?> --EXPECT-- string(3) "oci" Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_module.phpt ================================================ --TEST-- swoole_pdo_oracle: Setting session module --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $query = 'select module from v$session where sid = sys_context(\'USERENV\', \'SID\')'; $dbh = PdoOracleTest::create(); var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, "some module")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'MODULE SET: '; var_dump($row['module']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, "something else!")); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'MODULE RESET: '; var_dump($row['module']); var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, null)); $stmt = $dbh->query($query); $row = $stmt->fetch(); echo 'MODULE NULLED: '; var_dump($row['module']); echo "Done\n"; }); ?> --EXPECT-- bool(true) MODULE SET: string(11) "some module" bool(true) MODULE RESET: string(15) "something else!" bool(true) MODULE NULLED: NULL Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_nulls_1.phpt ================================================ --TEST-- swoole_pdo_oracle: Oracle Nulls --SKIPIF-- --FILE-- getAttribute(PDO::ATTR_ORACLE_NULLS)); $s = $dbh->prepare("select '' as myempty, null as mynull from dual"); $s->execute(); while ($r = $s->fetch()) { var_dump($r[0]); var_dump($r[1]); } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); print "PDO::ATTR_ORACLE_NULLS: Default: "; do_query($dbh); print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_NATURAL: "; $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); // No conversion. do_query($dbh); print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_EMPTY_STRING: "; $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING); // Empty string is converted to NULL. do_query($dbh); print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_TO_STRING: "; $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING); // NULL is converted to an empty string. do_query($dbh); echo "Done\n"; }); ?> --EXPECT-- PDO::ATTR_ORACLE_NULLS: Default: int(0) NULL NULL PDO::ATTR_ORACLE_NULLS: PDO::NULL_NATURAL: int(0) NULL NULL PDO::ATTR_ORACLE_NULLS: PDO::NULL_EMPTY_STRING: int(1) NULL NULL PDO::ATTR_ORACLE_NULLS: PDO::NULL_TO_STRING: int(2) string(0) "" string(0) "" Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_1.phpt ================================================ --TEST-- swoole_pdo_oracle: Set prefetch on connection --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); echo "Test connect\n"; $dbh->setAttribute(PDO::ATTR_PREFETCH, 101); echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; // Verify can fetch $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Test set 102\n"; $dbh->setAttribute(PDO::ATTR_PREFETCH, 102); echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; // Verify can fetch $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Test set -1: (Uses 0)\n"; $dbh->setAttribute(PDO::ATTR_PREFETCH, -1); echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; // Verify can fetch $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Test set PHP_INT_MAX: (Uses default)\n"; $dbh->setAttribute(PDO::ATTR_PREFETCH, PHP_INT_MAX); echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; // Verify can fetch $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Done\n"; }); ?> --EXPECT-- Test connect 101 X Test set 102 102 X Test set -1: (Uses 0) 0 X Test set PHP_INT_MAX: (Uses default) 100 X Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_2.phpt ================================================ --TEST-- swoole_pdo_oracle: prefetch on statements --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $s = $dbh->prepare("select '' as myempty, null as mynull from dual", array(PDO::ATTR_PREFETCH => 101)); echo "Test 1: Can't set prefetch after prepare\n"; var_dump($s->setAttribute(PDO::ATTR_PREFETCH, 102)); // Verify can fetch $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Test 2: Turn off prefetching\n"; $s = $dbh->prepare("select '' as myempty, null as mynull from dual", array(PDO::ATTR_PREFETCH => 0)); $s = $dbh->prepare("select dummy from dual" ); $s->execute(); while ($r = $s->fetch()) { echo $r[0] . "\n"; } echo "Done\n"; }); ?> --EXPECTF-- Test 1: Can't set prefetch after prepare Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes in %s:%d Stack trace: #0 %s(%d): PDOStatement->setAttribute(1, 102) %A thrown in %s on line %d ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_attr_server.phpt ================================================ --TEST-- swoole_pdo_oracle: Server version and info --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); echo "Test 1\n"; echo "ATTR_SERVER_VERSION: "; var_dump($dbh->getAttribute(PDO::ATTR_SERVER_VERSION)); echo "Test 2\n"; echo "ATTR_SERVER_INFO\n"; $si = $dbh->getAttribute(PDO::ATTR_SERVER_INFO); $pos = strpos($si, "Oracle"); if ($pos === 0) { echo "Found 'Oracle' at position $pos as expected\n"; } else { echo "Unexpected result. Server info was:\n"; var_dump($si); } echo "Done\n"; }); ?> --EXPECTF-- Test 1 ATTR_SERVER_VERSION: string(%d) "%d.%d.%d.%d.%d" Test 2 ATTR_SERVER_INFO Found 'Oracle' at position 0 as expected Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_class_constants.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI specific class constants --SKIPIF-- --FILE-- true, 'OCI_ATTR_ACTION' => true, 'OCI_ATTR_CLIENT_IDENTIFIER' => true, 'OCI_ATTR_MODULE' => true, 'OCI_ATTR_CALL_TIMEOUT' => true, ]; $ref = new ReflectionClass('PDO'); $constants = $ref->getConstants(); $values = []; foreach ($constants as $name => $value) { if (substr($name, 0, 8) == 'OCI_ATTR') { if (!isset($values[$value])) { $values[$value] = [$name]; } else { $values[$value][] = $name; } if (isset($expected[$name])) { unset($expected[$name]); unset($constants[$name]); } } else { unset($constants[$name]); } } if (!empty($constants)) { printf("[001] Dumping list of unexpected constants\n"); var_dump($constants); } if (!empty($expected)) { printf("[002] Dumping list of missing constants\n"); var_dump($expected); } if (!empty($values)) { foreach ($values as $value => $constants) { if (count($constants) > 1) { printf("[003] Several constants share the same value '%s'\n", $value); var_dump($constants); } } } print "done!"; ?> --EXPECT-- done! ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_debugdumpparams.phpt ================================================ --TEST-- swoole_pdo_oracle:debugDumpParams() truncates query) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); $stmt = $db->query(" SELECT ' Dumps the information contained by a prepared statement directly on the output. It will provide the SQL query in use, the number of parameters used (Params), the list of parameters, with their name, type (paramtype) as an integer, their key name or position, and the position in the query (if this is supported by the PDO driver, otherwise, it will be -1). This is a debug function, which dump directly the data on the normal output. Tip: As with anything that outputs its result directly to the browser, the output-control functions can be used to capture the output of this function, and save it in a string (for example). This will only dumps the parameters in the statement at the moment of the dump. Extra parameters are not stored in the statement, and not displayed. ' FROM DUAL "); var_dump($stmt->debugDumpParams()); }); ?> --EXPECTF-- SQL: [%d] SELECT ' Dumps the information contained by a prepared statement directly on the output. It will provide the SQL query in use, the number of parameters used (Params), the list of parameters, with their name, type (paramtype) as an integer, their key name or position, and the position in the query (if this is supported by the PDO driver, otherwise, it will be -1). This is a debug function, which dump directly the data on the normal output. Tip: As with anything that outputs its result directly to the browser, the output-control functions can be used to capture the output of this function, and save it in a string (for example). This will only dumps the parameters in the statement at the moment of the dump. Extra parameters are not stored in the statement, and not displayed. ' FROM DUAL Params: 0 NULL ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_fread_1.phpt ================================================ --TEST-- swoole_pdo_oracle: check fread() EOF --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Initialization $stmtarray = array( "begin execute immediate 'drop table pdo_oci_fread_tab'; exception when others then null; end;", "create table pdo_oci_fread_tab (id number, data clob)", "declare lob1 clob := 'abc' || lpad('j',4020,'j') || 'xyz'; begin insert into pdo_oci_fread_tab (id,data) values (1, lob1); end;" ); foreach ($stmtarray as $stmt) { $dbh->exec($stmt); } echo "Test 1\n"; $s = $dbh->query("select data from pdo_oci_fread_tab where id = 1"); $r = $s->fetch(); $sh = $r['data']; while (1) { $buffer = fread($sh,1024); if (!$buffer) { break; } echo '*'.$buffer.'*'; } echo "\n"; fclose($sh); // Clean up $stmtarray = array( "drop table pdo_oci_fread_tab" ); foreach ($stmtarray as $stmt) { $dbh->exec($stmt); } }); ?> --EXPECT-- Test 1 *abcjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjxyz* ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_phpinfo.phpt ================================================ --TEST-- swoole_pdo_oracle: phpinfo() output --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); ob_start(); phpinfo(); $tmp = ob_get_contents(); ob_end_clean(); $reg = 'coroutine_oracle => enabled'; if (!preg_match("/$reg/", $tmp)) { printf("[001] Cannot find OCI PDO driver line in phpinfo() output\n"); } print "done!"; }); ?> --EXPECT-- done! ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_quote1.phpt ================================================ --TEST-- swoole_pdo_oracle: Test PDO->quote() for PDO_OCI --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $db->query("create table poq_tab (t varchar2(100))"); $stmt = $db->prepare('select * from poq_tab'); // The intent is that the fetched data be identical to the unquoted string. // Remember!: use bind variables instead of PDO->quote() $a = array("", "a", "ab", "abc", "ab'cd", "a\b\n", "'", "''", "a'", "'z", "a''b", '"'); foreach ($a as $u) { $q = $db->quote($u); echo "Unquoted : "; var_dump($u); echo "Quoted : "; var_dump($q); $db->exec("delete from poq_tab"); $db->query("insert into poq_tab (t) values($q)"); $stmt->execute(); var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); } echo "Done\n"; $db->exec("drop table poq_tab"); }); ?> --EXPECT-- Unquoted : string(0) "" Quoted : string(2) "''" array(1) { [0]=> array(1) { ["t"]=> NULL } } Unquoted : string(1) "a" Quoted : string(3) "'a'" array(1) { [0]=> array(1) { ["t"]=> string(1) "a" } } Unquoted : string(2) "ab" Quoted : string(4) "'ab'" array(1) { [0]=> array(1) { ["t"]=> string(2) "ab" } } Unquoted : string(3) "abc" Quoted : string(5) "'abc'" array(1) { [0]=> array(1) { ["t"]=> string(3) "abc" } } Unquoted : string(5) "ab'cd" Quoted : string(8) "'ab''cd'" array(1) { [0]=> array(1) { ["t"]=> string(5) "ab'cd" } } Unquoted : string(4) "a\b " Quoted : string(6) "'a\b '" array(1) { [0]=> array(1) { ["t"]=> string(4) "a\b " } } Unquoted : string(1) "'" Quoted : string(4) "''''" array(1) { [0]=> array(1) { ["t"]=> string(1) "'" } } Unquoted : string(2) "''" Quoted : string(6) "''''''" array(1) { [0]=> array(1) { ["t"]=> string(2) "''" } } Unquoted : string(2) "a'" Quoted : string(5) "'a'''" array(1) { [0]=> array(1) { ["t"]=> string(2) "a'" } } Unquoted : string(2) "'z" Quoted : string(5) "'''z'" array(1) { [0]=> array(1) { ["t"]=> string(2) "'z" } } Unquoted : string(4) "a''b" Quoted : string(8) "'a''''b'" array(1) { [0]=> array(1) { ["t"]=> string(4) "a''b" } } Unquoted : string(1) """ Quoted : string(3) "'"'" array(1) { [0]=> array(1) { ["t"]=> string(1) """ } } Done ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_stmt_getcolumnmeta.phpt ================================================ --TEST-- swoole_pdo_oracle: PDOStatement->getColumnMeta --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { try { $db = PdoOracleTest::create(); $db->exec(<<exec("CREATE TABLE test(id INT)"); $db->beginTransaction(); $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC'); echo "Test 1. calling function with invalid parameters\n"; // execute() has not been called yet // NOTE: no warning $tmp = $stmt->getColumnMeta(0); printf(" 1.1 Expecting false got %s\n", var_export($tmp, true)); echo(" 1.2 "); $stmt->execute(); // PDOStatement::getColumnMeta() expects exactly 1 argument, 0 given in try { $tmp = $stmt->getColumnMeta(); } catch (ArgumentCountError $e) { if (false !== $tmp) { printf("[1.2] Expecting false got %s\n", var_export($tmp, true)); } echo $e->getMessage(), "\n"; } // invalid offset echo " 1.3 "; try { $tmp = $stmt->getColumnMeta(-1); } catch (ValueError $e) { if (false !== $tmp) { printf("[1.3] Expecting false got %s\n", var_export($tmp, true)); } echo $e->getMessage(), "\n"; } // PDOStatement::getColumnMeta(): Argument #1 must be of type int, array given in echo " 1.4 "; try { $tmp = $stmt->getColumnMeta(array()); } catch (TypeError $e) { if (false !== $tmp) printf("[1.4] Expecting false got %s\n", var_export($tmp, true)); echo $e->getMessage(), "\n"; } // PDOStatement::getColumnMeta() expects exactly 1 argument, 2 given in echo " 1.5 "; try { $tmp = $stmt->getColumnMeta(1, 1); } catch (ArgumentCountError $e) { if (false !== $tmp) printf("[1.5] Expecting false got %s\n", var_export($tmp, true)); echo $e->getMessage(), "\n"; } // invalid offset $tmp = $stmt->getColumnMeta(1); printf(" 1.6 Expecting false because of invalid offset got %s\n", var_export($tmp, true)); echo "Test 2. testing return values\n"; echo "Test 2.1 testing array returned\n"; $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC'); $stmt->execute(); $native = $stmt->getColumnMeta(0); if (count($native) == 0) { printf("[008] Meta data seems wrong, %s / %s\n", var_export($native, true), var_export($emulated, true)); } function test_return($meta, $offset, $native_type, $pdo_type){ if (empty($meta)) { printf("[%03d + 2] getColumnMeta() failed, %d - %s\n", $offset, $stmt->errorCode(), var_export($stmt->errorInfo(), true)); return false; } $elements = array('flags', 'scale', 'name', 'len', 'precision', 'pdo_type'); foreach ($elements as $k => $element) if (!isset($meta[$element])) { printf("[%03d + 3] Element %s missing, %s\n", $offset, $element, var_export($meta, true)); return false; } if (!is_null($native_type)) { if (!isset($meta['native_type'])) { printf("[%03d + 4] Element native_type missing, %s\n", $offset, var_export($meta, true)); return false; } if (!is_array($native_type)) $native_type = array($native_type); $found = false; foreach ($native_type as $k => $type) { if ($meta['native_type'] == $type) { $found = true; break; } } if (!$found) { printf("[%03d + 5] Expecting native type %s, %s\n", $offset, var_export($native_type, true), var_export($meta, true)); return false; } } if (!is_null($pdo_type) && ($meta['pdo_type'] != $pdo_type)) { printf("[%03d + 6] Expecting PDO type %s got %s (%s)\n", $offset, $pdo_type, var_export($meta, true), var_export($meta['native_type'])); return false; } return true; } function test_meta(&$db, $offset, $sql_type, $value, $native_type, $pdo_type) { $db->exec(<<prepare($sql); $stmt->execute(); if (!$db->exec(sprintf("INSERT INTO test(id, label) VALUES (1, '%s')", $value))) { printf("[%03d] + 1] Insert failed, %d - %s\n", $offset, $db->errorCode(), var_export($db->errorInfo(), true)); return false; } $stmt = $db->prepare('SELECT id, label FROM test'); $stmt->execute(); $meta = $stmt->getColumnMeta(1); return test_return($meta, $offset, $native_type, $pdo_type); } echo "Test 2.2 testing numeric columns\n"; test_meta($db, 20, 'NUMBER' , 0 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 30, 'NUMBER' , 256 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 40, 'INT' , 256 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 50, 'INTEGER' , 256 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 60, 'NUMBER' , 256.01 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 70, 'NUMBER' , -8388608 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 80, 'NUMBER' , 2147483648 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 90, 'NUMBER' , 4294967295 , 'NUMBER', PDO::PARAM_STR); test_meta($db, 100, 'DEC' , 1.01 , 'NUMBER' , PDO::PARAM_STR); test_meta($db, 110, 'DECIMAL' , 1.01 , 'NUMBER' , PDO::PARAM_STR); test_meta($db, 120, 'FLOAT' , 1.01 , 'FLOAT' , PDO::PARAM_STR); test_meta($db, 130, 'DOUBLE PRECISION', 1.01 , 'FLOAT' , PDO::PARAM_STR); test_meta($db, 140, 'BINARY_FLOAT' , 1.01 , 'BINARY_FLOAT' , PDO::PARAM_STR); test_meta($db, 150, 'BINARY_DOUBLE' , 1.01 , 'BINARY_DOUBLE', PDO::PARAM_STR); echo "Test 2.3 testing temporal columns\n"; $db->exec("alter session set nls_date_format='YYYY-MM-DD'"); test_meta($db, 160, 'DATE' , '2008-04-23' , 'DATE', PDO::PARAM_STR); echo "Test 2.4 testing string columns\n"; test_meta($db, 170, 'CHAR(1)' , 'a' , 'CHAR' , PDO::PARAM_STR); test_meta($db, 180, 'CHAR(10)' , '0123456789' , 'CHAR' , PDO::PARAM_STR); test_meta($db, 190, 'CHAR(255)' , str_repeat('z', 255) , 'CHAR' , PDO::PARAM_STR); test_meta($db, 200, 'VARCHAR(1)' , 'a' , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 210, 'VARCHAR(10)' , '0123456789' , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 220, 'VARCHAR(255)' , str_repeat('z', 255) , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 230, 'VARCHAR2(1)' , 'a' , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 240, 'VARCHAR2(10)' , '0123456789' , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 250, 'VARCHAR2(255)' , str_repeat('z', 255) , 'VARCHAR2' , PDO::PARAM_STR); test_meta($db, 260, 'NCHAR(1)' , 'a' , 'NCHAR' , PDO::PARAM_STR); test_meta($db, 270, 'NCHAR(10)' , '0123456789' , 'NCHAR' , PDO::PARAM_STR); test_meta($db, 280, 'NCHAR(255)' , str_repeat('z', 255) , 'NCHAR' , PDO::PARAM_STR); test_meta($db, 290, 'NVARCHAR2(1)' , 'a' , 'NVARCHAR2', PDO::PARAM_STR); test_meta($db, 300, 'NVARCHAR2(10)' , '0123456789' , 'NVARCHAR2', PDO::PARAM_STR); test_meta($db, 310, 'NVARCHAR2(255)', str_repeat('z', 255) , 'NVARCHAR2', PDO::PARAM_STR); echo "Test 2.5 testing lobs columns\n"; test_meta($db, 320, 'CLOB' , str_repeat('b', 255) , 'CLOB' , PDO::PARAM_LOB); test_meta($db, 330, 'BLOB' , str_repeat('b', 256) , 'BLOB' , PDO::PARAM_LOB); test_meta($db, 340, 'NCLOB' , str_repeat('b', 255) , 'NCLOB' , PDO::PARAM_LOB); test_meta($db, 350, 'LONG' , str_repeat('b', 256) , 'LONG' , PDO::PARAM_STR); test_meta($db, 360, 'LONG RAW' , str_repeat('b', 256) , 'LONG RAW', PDO::PARAM_STR); test_meta($db, 370, 'RAW(256)' , str_repeat('b', 256) , 'RAW' , PDO::PARAM_STR); $db->exec(<<query('SELECT count(*) FROM dual'); $meta = $stmt->getColumnMeta(0); test_return($meta, 380, 'NUMBER', PDO::PARAM_STR); $stmt = $db->query("SELECT TO_DATE('2008-04-23') FROM dual"); $meta = $stmt->getColumnMeta(0); test_return($meta, 390, 'DATE', PDO::PARAM_STR); $stmt = $db->query("SELECT TO_CHAR(542) FROM dual"); $meta = $stmt->getColumnMeta(0); test_return($meta, 400, 'VARCHAR2', PDO::PARAM_STR); echo "Test 2.7 testing flags returned\n"; $sql = sprintf('CREATE TABLE test(id INT NOT NULL, label INT NULL)'); $stmt = $db->prepare($sql); $stmt->execute(); $db->exec('INSERT INTO test(id, label) VALUES (1, 1)'); $stmt = $db->query('SELECT id, label FROM test'); $meta = $stmt->getColumnMeta(0); // verify the flags array contains a not_null flag and not nullable flags if (!isset($meta['flags'])) { printf("[1001] No flags contained in metadata %s\n", var_export($meta, true)); } else { $flags = $meta['flags']; $found = false; foreach ($flags as $k => $flag) { if ($flag == 'not_null') $found = true; if ($flag == 'nullable') printf("[1003] Flags seem wrong %s\n", var_export($meta, true)); } if (!$found) printf("[1002] Flags seem wrong %s\n", var_export($meta, true)); } $meta = $stmt->getColumnMeta(1); // verify the flags array contains a nullable flag and not not_null flags if (!isset($meta['flags'])) { printf("[1003] No flags contained in metadata %s\n", var_export($meta, true)); } else { $flags = $meta['flags']; $found = false; foreach ($flags as $k => $flag) { if ($flag == 'not_null') printf("[1004] Flags seem wrong %s\n", var_export($meta, true)); if ($flag == 'nullable') $found = true; } if (!$found) printf("[1005] Flags seem wrong %s\n", var_export($meta, true)); } } catch (PDOException $e) { // we should never get here, we use warnings, but never trust a system... printf("[001] %s, [%s} %s\n", $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo())); } $db->exec(<< --EXPECT-- Preparations before the test Test 1. calling function with invalid parameters 1.1 Expecting false got false 1.2 PDOStatement::getColumnMeta() expects exactly 1 argument, 0 given 1.3 PDOStatement::getColumnMeta(): Argument #1 ($column) must be greater than or equal to 0 1.4 PDOStatement::getColumnMeta(): Argument #1 ($column) must be of type int, array given 1.5 PDOStatement::getColumnMeta() expects exactly 1 argument, 2 given 1.6 Expecting false because of invalid offset got false Test 2. testing return values Test 2.1 testing array returned Test 2.2 testing numeric columns Test 2.3 testing temporal columns Test 2.4 testing string columns Test 2.5 testing lobs columns Test 2.6 testing function return Test 2.7 testing flags returned done! ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_stream_1.phpt ================================================ --TEST-- swoole_pdo_oracle: stream_get_contents length & offset test --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Initialization $stmtarray = array( "create table pdo_oci_stream_1_tab (id number, data clob)", ); foreach ($stmtarray as $stmt) { $dbh->exec($stmt); } $dbh->exec(" declare lob1 clob := 'abc' || lpad('j',30000,'j') || 'xyz'; begin insert into pdo_oci_stream_1_tab (id,data) values (1, 'abcdefghijklmnopqrstuvwxyz'); insert into pdo_oci_stream_1_tab (id,data) values (2, lob1); end;"); echo "Test 1\n"; $s = $dbh->prepare("select data from pdo_oci_stream_1_tab where id = 1"); $s->execute(); $r = $s->fetch(); // stream_get_contents ( resource $handle [, int $maxlength = -1 [, int $offset = -1 ]] ) echo 'Read '.stream_get_contents($r['data'], 1, 1)."$\n"; // b echo 'Read '.stream_get_contents($r['data'], 2, 1)."$\n"; // cd echo 'Read '.stream_get_contents($r['data'], 2, 0)."$\n"; // ab echo 'Read '.stream_get_contents($r['data'], 26, 0)."$\n"; // abcdefghijklmnopqrstuvwxyz echo 'Read '.stream_get_contents($r['data'], 27, 0)."$\n"; // abcdefghijklmnopqrstuvwxyz echo 'Read '.stream_get_contents($r['data'], 27, 1)."$\n"; // bcdefghijklmnopqrstuvwxyz echo 'Read '.stream_get_contents($r['data'], 1, 20)."$\n"; // u echo 'Read '.stream_get_contents($r['data'], 1, 25)."$\n"; // z echo 'Read '.stream_get_contents($r['data'], 1, 26)."$\n"; // echo 'Read '.stream_get_contents($r['data'], 1, 0)."$\n"; // a echo "\nTest 2\n"; $s = $dbh->prepare("select data from pdo_oci_stream_1_tab where id = 2"); $s->execute(); $r = $s->fetch(); echo 'Read '.stream_get_contents($r['data'], 5, 0)."\n"; // abcjj echo 'Read '.stream_get_contents($r['data'], 5, 2)."\n"; // cjjjj echo 'Read '.stream_get_contents($r['data'], 6, 1)."\n"; // bcjjjj echo 'Read '.strlen(stream_get_contents($r['data'], -1,0))."\n"; // 30006 echo 'Read '.strlen(stream_get_contents($r['data'], 0,0))."\n"; // 0 echo 'Read '.strlen(stream_get_contents($r['data'], 0,1))."\n"; // 0 echo 'Read '.strlen(stream_get_contents($r['data'], 10,100))."\n"; // 10 echo 'Read '.stream_get_contents($r['data'], 6, 30000)."\n"; // jjjxyz echo 'Read '.stream_get_contents($r['data'], 7, 30000)."\n"; // jjjxyz echo 'Read '.strlen(stream_get_contents($r['data']))."\n"; // 0 echo 'Read '.strlen(stream_get_contents($r['data'], 0))."\n"; // 0 echo 'Read '.strlen(stream_get_contents($r['data'], -1))."\n"; // 0 echo 'Read '.stream_get_contents($r['data'], -1, 30000)."\n"; // jjjxyz // Clean up $stmtarray = array( "drop table pdo_oci_stream_1_tab" ); foreach ($stmtarray as $stmt) { $dbh->exec($stmt); } }); ?> --EXPECT-- Test 1 Read b$ Read cd$ Read ab$ Read abcdefghijklmnopqrstuvwxyz$ Read abcdefghijklmnopqrstuvwxyz$ Read bcdefghijklmnopqrstuvwxyz$ Read u$ Read z$ Read $ Read a$ Test 2 Read abcjj Read cjjjj Read bcjjjj Read 30006 Read 0 Read 0 Read 10 Read jjjxyz Read jjjxyz Read 0 Read 0 Read 0 Read jjjxyz ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_stream_2.phpt ================================================ --TEST-- swoole_pdo_oracle: Insert and fetch 1K records from a table that contains 1 number and 2 LOB columns (stress test) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $query = "begin execute immediate 'drop table pdo_oci_stream_2'; exception when others then if sqlcode <> -942 then raise; end if; end;"; $stmt = $db->prepare($query); $stmt->execute(); $query = "create table pdo_oci_stream_2 (id number, data1 blob, data2 blob)"; $stmt = $db->prepare($query); $stmt->execute(); function do_insert($db, $id, $data1, $data2) { $db->beginTransaction(); $stmt = $db->prepare("insert into pdo_oci_stream_2 (id, data1, data2) values (:id, empty_blob(), empty_blob()) returning data1, data2 into :blob1, :blob2"); $stmt->bindParam(':id', $id); $stmt->bindParam(':blob1', $blob1, PDO::PARAM_LOB); $stmt->bindParam(':blob2', $blob2, PDO::PARAM_LOB); $blob1 = null; $blob2 = null; $stmt->execute(); fwrite($blob1, $data1); fclose($blob1); fwrite($blob2, $data2); fclose($blob2); $db->commit(); } $a1 = str_repeat('a', 4086); $a2 = str_repeat('b', 4087); $a3 = str_repeat('c', 4088); $a4 = str_repeat('d', 4089); $a5 = str_repeat('e', 4090); $a6 = str_repeat('f', 4091); $a7 = str_repeat('g', 4092); $a8 = str_repeat('h', 4093); $a9 = str_repeat('i', 4094); $a10 = str_repeat('j', 4095); printf("Inserting 1000 Records ... "); for($i=0; $i<100; $i++) { do_insert($db, $i * 10 + 1, $a1, $a10); do_insert($db, $i * 10 + 2, $a2, $a9); do_insert($db, $i * 10 + 3, $a3, $a8); do_insert($db, $i * 10 + 4, $a4, $a7); do_insert($db, $i * 10 + 5, $a5, $a6); do_insert($db, $i * 10 + 6, $a6, $a5); do_insert($db, $i * 10 + 7, $a7, $a4); do_insert($db, $i * 10 + 8, $a8, $a3); do_insert($db, $i * 10 + 9, $a9, $a2); do_insert($db, $i * 10 + 10, $a10, $a1); } printf("Done\n"); /* Cleanup is done in pdo_oci_stream_2b.phpt */ //$db->exec("drop table pdo_oci_stream_2"); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams // Since each column only has one lob descriptor, the last row is // shown twice because the lob descriptor for each column is reused in // the stream $i = 0; $j = 9; $a_val = ord('a'); foreach($db->query("select data1 as d4_1, data2 as d4_2 from pdo_oci_stream_2 order by id") as $row) { $a = $row['d4_1']; $a1 = $row['d4_2']; $str1 = stream_get_contents($a); $str2 = stream_get_contents($a1); $str1len = strlen($str1); $str2len = strlen($str2); $b = ord($str1[0]); $b1 = ord($str2[0]); if (($b != ($a_val + $i)) && ($str1len != (4086 + $i)) && ($b1 != ($a_val + $j)) && ($str2len != (4086 + $j))) { printf("There is a bug!\n"); printf("Col1:\n"); printf("a_val = %d\n", $a_val); printf("b = %d\n", $b); printf("i = %d\n", $i); printf("str1len = %d\n", $str1len); printf("Col2:\n"); printf("a_val = %d\n", $a_val); printf("b1 = %d\n", $b1); printf("j = %d\n", $j); printf("str2len = %d\n", $str1len); } $i++; if ($i>9) $i = 0; $j--; if ($j<0) $j = 9; } echo "Fetch operation done!\n"; /* Cleanup */ $db->exec("drop table pdo_oci_stream_2"); }); ?> --EXPECT-- Inserting 1000 Records ... Done Fetch operation done! ================================================ FILE: tests/swoole_pdo_oracle/pdo_oci_templob_1.phpt ================================================ --TEST-- swoole_pdo_oracle: Test to verify all implicitly created temporary LOB are cleaned up --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $db = PdoOracleTest::create(); $clobquery1 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery2 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery3 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery4 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery5 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery6 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery7 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery8 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery9 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $clobquery10 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; $stmt= $db->prepare($clobquery1); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery2); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery3); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery4); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery5); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery6); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery7); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery8); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery9); $stmt->execute(); $row = $stmt->fetch(); $stmt= $db->prepare($clobquery10); $stmt->execute(); $row = $stmt->fetch(); $query1 = "SELECT SYS_CONTEXT('USERENV', 'SID') SID FROM DUAL"; $stmt1 = $db->prepare($query1); $stmt1->execute(); $row1 = $stmt1->fetch(); $sid_value = $row1[0]; $query2 = "SELECT (CACHE_LOBS+NOCACHE_LOBS+ABSTRACT_LOBS) FROM V\$TEMPORARY_LOBS WHERE SID = :SID_VALUE"; $stmt2 = $db->prepare($query2); $stmt2->bindParam(':SID_VALUE', $sid_value); $stmt2->execute(); $row2 = $stmt2->fetch(); /* 1 temporary LOB still exists in V$TEMPORARY_LOBS since the destructor of $stmt is not yet called by PHP */ if ($row2[0] > 1) { echo "TEMP_LOB is not yet cleared!" . $row2[0] . "\n"; } else { echo "Success! All the temporary LOB in previously closed statements are properly cleaned.\n"; } }); ?> --EXPECTF-- TEMP_LOB is not yet cleared!10 ================================================ FILE: tests/swoole_pdo_oracle/pdo_oracle.inc ================================================ getMessage()); } } public static function create(): PDO { $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD); $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); return $db; } } ================================================ FILE: tests/swoole_pdo_oracle/pecl_bug_11345.phpt ================================================ --TEST-- swoole_pdo_oracle: PECL PDO_OCI Bug #11345 (Test invalid character set name) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { try { $dbh = new PDO('oci:dbname=xxx;charset=yyy', 'abc', 'def'); } catch (PDOException $e) { echo 'Connection failed: ' . $e->getMessage(). "\n"; } }); ?> --EXPECTF-- Connection failed: SQLSTATE[HY000]: OCINlsCharSetNameToId: unknown character set name (%s) ================================================ FILE: tests/swoole_pdo_oracle/pecl_bug_6364.phpt ================================================ --TEST-- swoole_pdo_oracle: PECL PDO_OCI Bug #6364 (segmentation fault on stored procedure call with OUT binds) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_ORACLE]); run(function() { $dbh = PdoOracleTest::create(); $dbh->exec ("create table bug_6364_t (c1 varchar2(10), c2 varchar2(10), c3 varchar2(10), c4 varchar2(10), c5 varchar2(10))"); $dbh->exec ("create or replace procedure bug_6364_sp(p1 IN varchar2, p2 IN varchar2, p3 IN varchar2, p4 OUT varchar2, p5 OUT varchar2) as begin insert into bug_6364_t (c1, c2, c3) values (p1, p2, p3); p4 := 'val4'; p5 := 'val5'; end;"); $stmt = $dbh->prepare("call bug_6364_sp('p1','p2','p3',?,?)"); $out_param1 = "a"; $out_param2 = "a"; $stmt->bindParam(1, $out_param1,PDO::PARAM_STR, 1024); $stmt->bindParam(2, $out_param2,PDO::PARAM_STR, 1024); $stmt->execute() or die ("Execution error: " . var_dump($dbh->errorInfo())); var_dump($out_param1); var_dump($out_param2); foreach ($dbh->query("select * from bug_6364_t") as $row) { var_dump($row); } print "Done\n"; // Cleanup $dbh->exec ("drop procedure bug_6364_sp"); $dbh->exec ("drop table bug_6364_t"); }); ?> --EXPECT-- string(4) "val4" string(4) "val5" array(10) { ["c1"]=> string(2) "p1" [0]=> string(2) "p1" ["c2"]=> string(2) "p2" [1]=> string(2) "p2" ["c3"]=> string(2) "p3" [2]=> string(2) "p3" ["c4"]=> NULL [3]=> NULL ["c5"]=> NULL [4]=> NULL } Done ================================================ FILE: tests/swoole_pdo_oracle/transcation.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI transcation1 --SKIPIF-- --FILE-- SWOOLE_HOOK_ALL]); run(function() { $db = PdoOracleTest::create(); $db->exec('create table transcation1 (id int)'); go(function () use($db){ $db->beginTransaction(); $stmt = $db->prepare("insert into transcation1 values (?)"); $stmt->execute([1]); go(function () use($db){ $db->beginTransaction(); $stmt = $db->prepare("insert into transcation1 values (?)"); $stmt->execute([2]); $db->rollback(); }); sleep(2); $db->commit(); $stmt = $db->prepare("select id from transcation1 where id = ?"); $stmt->execute([1]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 1); }); sleep(4); }); ?> --EXPECTF-- Fatal error: Uncaught PDOException: There is already an active transaction in %s:%d Stack trace: #0 %s(%d): PDO->beginTransaction() %A thrown in %s on line %d ================================================ FILE: tests/swoole_pdo_oracle/transcation2.phpt ================================================ --TEST-- swoole_pdo_oracle: PDO OCI transcation2 --SKIPIF-- --FILE-- SWOOLE_HOOK_ALL]); run(function() { $db = PdoOracleTest::create(); $db->exec('create table transcation2 (id int)'); go(function() { $db = PdoOracleTest::create(); $db->beginTransaction(); $stmt = $db->prepare("insert into transcation2 values (?)"); $stmt->execute([1]); $db->commit(); $stmt = $db->prepare("select id from transcation2 where id = ?"); $stmt->execute([1]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 1); }); go(function(){ $db = PdoOracleTest::create(); $db->beginTransaction(); $stmt = $db->prepare("insert into transcation2 values (?)"); $stmt->execute([2]); $db->commit(); $stmt = $db->prepare("select id from transcation2 where id = ?"); $stmt->execute([2]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 2); }); sleep(1); $db->exec('drop table transcation2'); }); ?> --EXPECT-- bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_pgsql/base.phpt ================================================ --TEST-- swoole_pdo_pgsql: test hook pgsql --SKIPIF-- --FILE-- prepare('SELECT * FROM pg_catalog.pg_tables limit 1'); $statement->execute(); var_dump($statement->fetchAll(PDO::FETCH_COLUMN)); }); Co\go(function () { $pdo = pdo_pgsql_test_inc::create(); $statement = $pdo->prepare('SELECT * FROM pg_catalog.pg_tables limit 1'); $statement->execute(); var_dump($statement->fetchAll(PDO::FETCH_COLUMN)); }); }); echo "Done\n"; ?> --EXPECTF-- array(1) { [0]=> string(%d) "%s" } array(1) { [0]=> string(%d) "%s" } Done ================================================ FILE: tests/swoole_pdo_pgsql/blocking.phpt ================================================ --TEST-- swoole_pdo_pgsql: test hook pgsql --SKIPIF-- --FILE-- prepare('SELECT pg_sleep(1)'); $statement->execute(); Assert::eq($sleep_count, 0); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_pgsql/bug_5635.phpt ================================================ --TEST-- swoole_pdo_pgsql: Github bug #5635 --SKIPIF-- --FILE-- exec('create table bug_5635 (id int, data varchar(1024));'); $pdo->exec(<< SWOOLE_HOOK_PDO_PGSQL]); run(function() { $waitGroup = new WaitGroup(); $channel = new Channel(1); Coroutine::create(function() use ($waitGroup, $channel) { $start = time(); $waitGroup->add(); $pdo = pdo_pgsql_test_inc::create(); $stmt = $pdo->query("select * from bug_5635;"); $data = $stmt->fetchAll(); Assert::true(count($data) == 5000000); $channel->push($data ?? [], 10); $waitGroup->done(); echo 'DONE' . PHP_EOL; }); Coroutine::create(function() use ($waitGroup, $channel) { $waitGroup->add(); $result = $channel->pop(0.5); if (!$result) { echo 'channel pop timeout' . PHP_EOL; } $waitGroup->done(); }); var_dump(1); Coroutine::sleep(0.2); var_dump(2); $waitGroup->wait(); }); ?> --CLEAN-- exec('drop table bug_5635;'); ?> --EXPECTF-- int(1) int(2) channel pop timeout DONE ================================================ FILE: tests/swoole_pdo_pgsql/libpq_version.phpt ================================================ --TEST-- swoole_pdo_pgsql: libpq version --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_pdo_pgsql/pdo_pgsql.inc ================================================ exec('CREATE TABLE ' . self::TABLE . ' ( id SERIAL primary key NOT NULL, name character varying(32), age integer)'); } $pdo->exec('TRUNCATE ' . self::TABLE); } public static function getTable() { return self::TABLE; } public static function tableExists($pdo, $table) { try { $result = $pdo->query("SELECT 1 FROM {$table} LIMIT 1"); } catch (Exception $e) { return false; } return $result !== false; } public static function create(): PDO { $host = PGSQL_HOST; $port = PGSQL_PORT; $user = PGSQL_USER; $password = PGSQL_PASSWORD; $dbname = PGSQL_DBNAME; return new PDO("pgsql:host={$host};port={$port};dbname={$dbname}", $user, $password); } } ================================================ FILE: tests/swoole_pdo_pgsql/query.phpt ================================================ --TEST-- swoole_pdo_pgsql: test query --SKIPIF-- --FILE-- prepare('INSERT INTO ' . pdo_pgsql_test_inc::getTable() . ' (name, age) values (?, ?)'); $list = []; for ($i = 0; $i < N; $i++) { $name = base64_encode(random_bytes(8)); $age = random_int(18, 35); $stmt->bindValue(1, $name); $stmt->bindValue(2, $age); $stmt->execute(); $list[] = [ 'id' => $pdo->lastInsertId(), 'name' => $name, 'age' => $age, ]; } foreach ($list as $rs) { Co\go(function () use ($rs) { $pdo = pdo_pgsql_test_inc::create(); $statement = $pdo->query('select * from ' . pdo_pgsql_test_inc::getTable() . ' where id = ' . $rs['id'] . ' limit 1'); Assert::eq($statement->fetch(PDO::FETCH_ASSOC), $rs); }); } }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_pgsql/race.phpt ================================================ --TEST-- swoole_pdo_pgsql: race --SKIPIF-- --FILE-- prepare('SELECT pg_sleep(1)'); try { $statement->execute(); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); } catch (\PDOException $e) { $msg[] = $e->getMessage(); } }); } Assert::count($msg, 1); Assert::contains($msg[0], 'SQLSTATE[HY000]: General error'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_pgsql/sleep.phpt ================================================ --TEST-- swoole_pdo_pgsql: test hook pgsql --SKIPIF-- --FILE-- prepare('SELECT pg_sleep(1)'); $statement->execute(); Assert::eq($sleep_count, N); Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); }); echo "Done\n"; ?> --EXPECTF-- Done ================================================ FILE: tests/swoole_pdo_pgsql/transaction.phpt ================================================ --TEST-- swoole_pdo_pgsql: test query --SKIPIF-- --FILE-- prepare('INSERT INTO ' . pdo_pgsql_test_inc::getTable() . ' (name, age) values (?, ?)'); $stmt->bindValue(1, base64_encode(random_bytes(8))); $stmt->bindValue(2, random_int(18, 35)); $stmt->execute(); var_dump('insert'); Co::join([Co\go(static function (): void { $pdo = pdo_pgsql_test_inc::create(); try { $pdo->beginTransaction(); $pdo->exec('DROP TABLE IF EXISTS ' . pdo_pgsql_test_inc::getTable()); throw new Exception('interrupt!!!'); $pdo->commit(); } catch (\Exception $e) { $pdo->rollBack(); var_dump('rollback'); } })]); var_dump('wait1'); var_dump(pdo_pgsql_test_inc::tableExists($pdo, pdo_pgsql_test_inc::getTable())); Co::join([Co\go(static function (): void { $pdo = pdo_pgsql_test_inc::create(); try { $pdo->beginTransaction(); $pdo->exec('DROP TABLE IF EXISTS ' . pdo_pgsql_test_inc::getTable()); $pdo->commit(); } catch (\Exception $e) { $pdo->rollBack(); var_dump($e->getMessage()); } })]); var_dump('wait2'); var_dump(pdo_pgsql_test_inc::tableExists($pdo, pdo_pgsql_test_inc::getTable())); }); echo "Done\n"; ?> --EXPECTF-- string(6) "insert" string(8) "rollback" string(5) "wait1" bool(true) string(5) "wait2" bool(false) Done ================================================ FILE: tests/swoole_pdo_sqlite/bug33841.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLite Bug #33841 (rowCount() does not work on prepared statements) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = PdoSqliteTest::create(); $db->exec('CREATE TABLE test (text)'); $stmt = $db->prepare("INSERT INTO test VALUES ( :text )"); $stmt->bindParam(':text', $name); $name = 'test1'; var_dump($stmt->execute(), $stmt->rowCount()); $stmt = $db->prepare("UPDATE test SET text = :text "); $stmt->bindParam(':text', $name); $name = 'test2'; var_dump($stmt->execute(), $stmt->rowCount()); }); ?> --EXPECT-- bool(true) int(1) bool(true) int(1) ================================================ FILE: tests/swoole_pdo_sqlite/bug35336.phpt ================================================ --TEST-- swoole_pdo_sqlite:FETCH_CLASS + __set()) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $a = new PDO("sqlite::memory:");// pool ("sqlite::memory:"); $a->query ("CREATE TABLE test (a integer primary key, b text)"); $b = $a->prepare("insert into test (b) values (?)"); $b->execute(array (5)); $rez = $a->query ("SELECT * FROM test")->fetchAll(PDO::FETCH_CLASS, 'EEE'); echo "Done\n"; }); ?> --EXPECT-- hello world hello world Done ================================================ FILE: tests/swoole_pdo_sqlite/bug38334.phpt ================================================ --TEST-- swoole_pdo_sqlite: Proper data-type support for PDO_SQLITE --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->exec('CREATE TABLE test (i INTEGER , f DOUBLE, s VARCHAR(255))'); $db->exec('INSERT INTO test VALUES (42, 46.7, "test")'); var_dump($db->query('SELECT * FROM test')->fetch(PDO::FETCH_ASSOC)); // Check handling of integers larger than 32-bit. $db->exec('INSERT INTO test VALUES (10000000000, 0.0, "")'); $i = $db->query('SELECT i FROM test WHERE f = 0.0')->fetchColumn(0); if (PHP_INT_SIZE >= 8) { var_dump($i === 10000000000); } else { var_dump($i === '10000000000'); } // Check storing of strings into integer/float columns. $db->exec('INSERT INTO test VALUES ("test", "test", "x")'); var_dump($db->query('SELECT * FROM test WHERE s = "x"')->fetch(PDO::FETCH_ASSOC)); }); ?> --EXPECT-- array(3) { ["i"]=> int(42) ["f"]=> float(46.7) ["s"]=> string(4) "test" } bool(true) array(3) { ["i"]=> string(4) "test" ["f"]=> string(4) "test" ["s"]=> string(1) "x" } ================================================ FILE: tests/swoole_pdo_sqlite/bug43831.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #43831 ($this gets mangled when extending PDO with persistent connection) --SKIPIF-- --FILE-- true)); } } class Baz extends PDO { function __construct($dsn) { parent::__construct($dsn, null, null, array(PDO::ATTR_PERSISTENT => true)); } } class Bar extends Baz { function quux() { echo get_class($this), "\n"; $foo = new Foo("sqlite::memory:"); echo get_class($this), "\n"; } } class MyPDO extends PDO {} Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); run(function() { $bar = new Bar("sqlite::memory:"); $bar->quux(); $bar = new PDO("sqlite::memory:", null, null, array(PDO::ATTR_PERSISTENT => true)); $baz = new MyPDO("sqlite::memory:", null, null, array(PDO::ATTR_PERSISTENT => true)); var_dump($bar); unset($bar); var_dump($baz); var_dump($bar); }); ?> --EXPECTF-- Bar Bar object(PDO)#%d (0) { } object(MyPDO)#%d (0) { } Warning: Undefined variable $bar in %s on line %d NULL ================================================ FILE: tests/swoole_pdo_sqlite/bug44327_2_1.phpt ================================================ --TEST-- swoole_pdo_sqlite:queryString property & numeric offsets / Crash) --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $x = $db->query('select 1 as queryString'); var_dump($x, $x->queryString); $y = $x->fetch(); var_dump($y, @$y->queryString); print "--------------------------------------------\n"; $x = $db->query('select 1 as queryString'); var_dump($x, $x->queryString); $y = $x->fetch(PDO::FETCH_LAZY); var_dump($y, $y->queryString); }); ?> --EXPECTF-- object(PDOStatement)#%d (1) { ["queryString"]=> string(23) "select 1 as queryString" } string(23) "select 1 as queryString" array(2) { ["queryString"]=> string(1) "1" [0]=> string(1) "1" } NULL -------------------------------------------- object(PDOStatement)#%d (1) { ["queryString"]=> string(23) "select 1 as queryString" } string(23) "select 1 as queryString" object(PDORow)#%d (1) { ["queryString"]=> string(1) "1" } string(1) "1" ================================================ FILE: tests/swoole_pdo_sqlite/bug44327_2_2.phpt ================================================ --TEST-- swoole_pdo_sqlite:queryString property & numeric offsets / Crash) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $x = $db->query('select 1 as queryString'); var_dump($x, $x->queryString); $y = $x->fetch(); var_dump($y, @$y->queryString); print "--------------------------------------------\n"; $x = $db->query('select 1 as queryString'); var_dump($x, $x->queryString); $y = $x->fetch(PDO::FETCH_LAZY); var_dump($y, $y->queryString); }); ?> --EXPECTF-- object(PDOStatement)#%d (1) { ["queryString"]=> string(23) "select 1 as queryString" } string(23) "select 1 as queryString" array(2) { ["queryString"]=> int(1) [0]=> int(1) } NULL -------------------------------------------- object(PDOStatement)#%d (1) { ["queryString"]=> string(23) "select 1 as queryString" } string(23) "select 1 as queryString" object(PDORow)#%d (1) { ["queryString"]=> string(23) "select 1 as queryString" } string(23) "select 1 as queryString" ================================================ FILE: tests/swoole_pdo_sqlite/bug44327_3_1.phpt ================================================ --TEST-- swoole_pdo_sqlite:queryString property & numeric offsets / Crash) --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $x = $db->query('select 1 as queryStringxx'); $y = $x->fetch(PDO::FETCH_LAZY); var_dump($y, $y->queryString, $y->queryStringzz, $y->queryStringxx); print "---\n"; var_dump($y[5], $y->{3}); }); ?> --EXPECTF-- object(PDORow)#%d (2) { ["queryString"]=> string(25) "select 1 as queryStringxx" ["queryStringxx"]=> string(1) "1" } string(25) "select 1 as queryStringxx" NULL string(1) "1" --- NULL NULL ================================================ FILE: tests/swoole_pdo_sqlite/bug44327_3_2.phpt ================================================ --TEST-- swoole_pdo_sqlite:queryString property & numeric offsets / Crash) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $x = $db->query('select 1 as queryStringxx'); $y = $x->fetch(PDO::FETCH_LAZY); var_dump($y, $y->queryString, $y->queryStringzz, $y->queryStringxx); print "---\n"; var_dump($y[5], $y->{3}); }); ?> --EXPECTF-- object(PDORow)#%d (2) { ["queryString"]=> string(25) "select 1 as queryStringxx" ["queryStringxx"]=> int(1) } string(25) "select 1 as queryStringxx" NULL int(1) --- NULL NULL ================================================ FILE: tests/swoole_pdo_sqlite/bug46139.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #46139 (PDOStatement->setFetchMode() forgets FETCH_PROPS_LATE) --SKIPIF-- --FILE-- test); } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = PdoSqliteTest::create(); $stmt = $db->query("SELECT 'foo' test, 1"); $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); $r1 = $stmt->fetch(); printf("'%s'\n", $r1->test); $stmt = $db->query("SELECT 'foo' test, 1"); $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); $r1 = $stmt->fetchAll(); printf("'%s'\n", $r1[0]->test); $stmt = $db->query("SELECT 'foo' test, 1"); $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); $r1 = $stmt->fetch(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE); printf("'%s'\n", $r1->test); }); ?> --EXPECT-- NULL 'foo' NULL 'foo' NULL 'foo' ================================================ FILE: tests/swoole_pdo_sqlite/bug46542.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #46542 Extending PDO class with a __call() function --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $a = new A('sqlite:' . __DIR__ . '/dummy.db'); $a->truc(); $a->TRUC(); }); ?> --CLEAN-- --EXPECT-- A::truc A::TRUC ================================================ FILE: tests/swoole_pdo_sqlite/bug48773.phpt ================================================ --TEST-- swoole_pdo_sqlite:ATTR_STATEMENT_CLASS with ctor_args) --SKIPIF-- --FILE-- setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new foo('sqlite::memory:', '', ''); $stmt = $db->query('SELECT 1'); var_dump($stmt); }); ?> --EXPECTF-- object(bar)#%d (1) { ["queryString"]=> string(8) "SELECT 1" } ================================================ FILE: tests/swoole_pdo_sqlite/bug50728.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #50728 (All PDOExceptions hardcode 'code' property to 0) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { try { $a = new PDO("sqlite:/this/path/should/not/exist.db"); } catch (PDOException $e) { var_dump($e->getCode()); } }); ?> --EXPECT-- int(14) ================================================ FILE: tests/swoole_pdo_sqlite/bug52487.phpt ================================================ --TEST-- swoole_pdo_sqlite:FETCH_INTO leaks memory) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = PdoSqliteTest::create(); $stmt = $db->prepare("select 1 as attr"); for ($i = 0; $i < 10; $i++) { $stmt->setFetchMode(PDO::FETCH_INTO, new stdClass); } print "ok\n"; }); ?> --EXPECT-- ok ================================================ FILE: tests/swoole_pdo_sqlite/bug66033.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #66033 (Segmentation Fault when constructor of PDO statement throws an exception) --SKIPIF-- --FILE-- dbh = $dbh; throw new Exception("Blah"); } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO('sqlite::memory:', null, null); $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DBStatement', array($pdo))); $pdo->exec("CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY, title TEXT, message TEXT, time INTEGER)"); try { $pdoStatement = $pdo->query("select * from messages"); } catch (Exception $e) { var_dump($e->getMessage()); } }); ?> --EXPECT-- string(4) "Blah" ================================================ FILE: tests/swoole_pdo_sqlite/bug70862.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing sqliteCreateCollation() --SKIPIF-- = 80200) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.1 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->exec('CREATE TABLE test(field BLOB)'); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); class HelloWrapper { public function stream_open() { return true; } public function stream_eof() { return true; } public function stream_read() { return NULL; } public function stream_stat() { return array(); } } stream_wrapper_register("hello", "HelloWrapper"); $f = fopen("hello://there", "r"); $stmt = $db->prepare('INSERT INTO test(field) VALUES (:para)'); $stmt->bindParam(":para", $f, PDO::PARAM_LOB); $stmt->execute(); var_dump($f); }); ?> +++DONE+++ --EXPECT-- string(0) "" +++DONE+++ ================================================ FILE: tests/swoole_pdo_sqlite/bug70862_1.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing sqliteCreateCollation() --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->exec('CREATE TABLE test(field BLOB)'); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); class HelloWrapper { public function stream_open() { return true; } public function stream_eof() { return true; } public function stream_read() { return NULL; } public function stream_stat() { return array(); } } stream_wrapper_register("hello", "HelloWrapper"); $f = fopen("hello://there", "r"); $stmt = $db->prepare('INSERT INTO test(field) VALUES (:para)'); $stmt->bindParam(":para", $f, PDO::PARAM_LOB); $stmt->execute(); var_dump($f); }); ?> +++DONE+++ --EXPECTF-- Deprecated: Creation of dynamic property HelloWrapper::$context is deprecated in %s on line %d string(0) "" +++DONE+++ ================================================ FILE: tests/swoole_pdo_sqlite/bug78192_1.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLite Bug #78192 SegFault when reuse statement after schema change --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $connection = new \PDO('sqlite::memory:'); $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $connection->query('CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)'); $stmt = $connection->prepare('INSERT INTO user (id, name) VALUES(:id, :name)'); $stmt->execute([ 'id' => 10, 'name' => 'test', ]); $stmt = $connection->prepare('SELECT * FROM user WHERE id = :id'); $stmt->execute(['id' => 10]); var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); $connection->query('ALTER TABLE user ADD new_col VARCHAR(255)'); $stmt->execute(['id' => 10]); var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); }); ?> --EXPECT-- array(1) { [0]=> array(2) { ["id"]=> string(2) "10" ["name"]=> string(4) "test" } } array(1) { [0]=> array(3) { ["id"]=> string(2) "10" ["name"]=> string(4) "test" ["new_col"]=> NULL } } ================================================ FILE: tests/swoole_pdo_sqlite/bug78192_2.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLite Bug #78192 SegFault when reuse statement after schema change --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $connection = new \PDO('sqlite::memory:'); $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $connection->query('CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)'); $stmt = $connection->prepare('INSERT INTO user (id, name) VALUES(:id, :name)'); $stmt->execute([ 'id' => 10, 'name' => 'test', ]); $stmt = $connection->prepare('SELECT * FROM user WHERE id = :id'); $stmt->execute(['id' => 10]); var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); $connection->query('ALTER TABLE user ADD new_col VARCHAR(255)'); $stmt->execute(['id' => 10]); var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); }); ?> --EXPECT-- array(1) { [0]=> array(2) { ["id"]=> int(10) ["name"]=> string(4) "test" } } array(1) { [0]=> array(3) { ["id"]=> int(10) ["name"]=> string(4) "test" ["new_col"]=> NULL } } ================================================ FILE: tests/swoole_pdo_sqlite/bug79664_1.phpt ================================================ --TEST-- swoole_pdo_sqlite:getColumnMeta fails on empty result set) --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO('sqlite::memory:', null, null, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $stmt = $pdo->query('select 1 where 0'); if ($stmt->columnCount()) { var_dump($stmt->getColumnMeta(0)); } }); ?> --EXPECT-- array(6) { ["native_type"]=> string(4) "null" ["flags"]=> array(0) { } ["name"]=> string(1) "1" ["len"]=> int(-1) ["precision"]=> int(0) ["pdo_type"]=> int(2) } ================================================ FILE: tests/swoole_pdo_sqlite/bug79664_2.phpt ================================================ --TEST-- swoole_pdo_sqlite:getColumnMeta fails on empty result set) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO('sqlite::memory:', null, null, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $stmt = $pdo->query('select 1 where 0'); if ($stmt->columnCount()) { var_dump($stmt->getColumnMeta(0)); } }); ?> --EXPECT-- array(6) { ["native_type"]=> string(4) "null" ["pdo_type"]=> int(0) ["flags"]=> array(0) { } ["name"]=> string(1) "1" ["len"]=> int(-1) ["precision"]=> int(0) } ================================================ FILE: tests/swoole_pdo_sqlite/bug81740.phpt ================================================ --TEST-- swoole_pdo_sqlite:quote() may return unquoted string) --SKIPIF-- --INI-- memory_limit=-1 --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO("sqlite::memory:"); $string = str_repeat("a", 0x80000000); var_dump($pdo->quote($string)); }); ?> --EXPECT-- bool(false) ================================================ FILE: tests/swoole_pdo_sqlite/bug_42589.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLite Feature Request #42589 (getColumnMeta() should also return table name) --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO("sqlite::memory:"); $db->exec('CREATE TABLE test (field1 VARCHAR(10))'); $db->exec('INSERT INTO test VALUES("test")'); $result = $db->query('SELECT * FROM test t1 LEFT JOIN test t2 ON t1.field1 = t2.field1'); $meta1 = $result->getColumnMeta(0); $meta2 = $result->getColumnMeta(1); var_dump(!empty($meta1['table']) && $meta1['table'] == 'test'); var_dump(!empty($meta2['table']) && $meta2['table'] == 'test'); }); ?> --EXPECT-- bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_sqlite/bug_44159_sqlite_version_1.phpt ================================================ --TEST-- swoole_pdo_sqlite: SQLite variant --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO("sqlite:".__DIR__."/foo.db"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, NULL)); var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 1)); var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 'nonsense')); @unlink(__DIR__."/foo.db"); }); ?> --EXPECT-- bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_sqlite/bug_44159_sqlite_version_2.phpt ================================================ --TEST-- swoole_pdo_sqlite: SQLite variant --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO("sqlite:".__DIR__."/foo.db"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); try { var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, NULL)); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 1)); try { var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 'nonsense')); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } @unlink(__DIR__."/foo.db"); }); ?> --EXPECT-- Attribute value must be of type int for selected attribute, null given bool(true) Attribute value must be of type int for selected attribute, string given ================================================ FILE: tests/swoole_pdo_sqlite/bug_47769.phpt ================================================ --TEST-- swoole_pdo_sqlite: Bug #47769 (Strange extends PDO) --SKIPIF-- --FILE-- isProtected(); $this->isPrivate(); print $str ."\n"; return $str; } } Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); run(function() { $test = new test('sqlite::memory:'); $test->quote('foo'); $test->isProtected(); }); ?> --EXPECTF-- this is a protected method. this is a private method. foo Fatal error: Uncaught Error: Call to protected method test::isProtected() from global scope in %s:%d Stack trace: %A thrown in %s on line %d ================================================ FILE: tests/swoole_pdo_sqlite/bug_63916-2.phpt ================================================ --TEST-- swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite --SKIPIF-- 4) die('skip 32-bit only'); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $num = PHP_INT_MAX; // 32 bits $conn = new PDO('sqlite::memory:'); $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); $stmt->bindValue(':id', 1, PDO::PARAM_INT); $stmt->bindValue(':num', $num, PDO::PARAM_INT); $stmt->execute(); $stmt = $conn->query('SELECT num FROM users'); $result = $stmt->fetchAll(PDO::FETCH_COLUMN); var_dump($num,$result[0]); }); ?> --EXPECT-- int(2147483647) int(2147483647) ================================================ FILE: tests/swoole_pdo_sqlite/bug_63916_1.phpt ================================================ --TEST-- swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite --SKIPIF-- = 80100) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.0 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); if (PHP_INT_SIZE < 8) die('skip'); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $num = 100004313234244; // exceeds 32 bits $conn = new PDO('sqlite::memory:'); $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); $stmt->bindValue(':id', 1, PDO::PARAM_INT); $stmt->bindValue(':num', $num, PDO::PARAM_INT); $stmt->execute(); $stmt = $conn->query('SELECT num FROM users'); $result = $stmt->fetchAll(PDO::FETCH_COLUMN); var_dump($num,$result[0]); }); ?> --EXPECT-- int(100004313234244) string(15) "100004313234244" ================================================ FILE: tests/swoole_pdo_sqlite/bug_63916_2.phpt ================================================ --TEST-- swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $num = 100004313234244; // exceeds 32 bits $conn = new PDO('sqlite::memory:'); $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); $stmt->bindValue(':id', 1, PDO::PARAM_INT); $stmt->bindValue(':num', $num, PDO::PARAM_INT); $stmt->execute(); $stmt = $conn->query('SELECT num FROM users'); $result = $stmt->fetchAll(PDO::FETCH_COLUMN); var_dump($num,$result[0]); }); ?> --EXPECT-- int(100004313234244) int(100004313234244) ================================================ FILE: tests/swoole_pdo_sqlite/bug_64705.phpt ================================================ --TEST-- swoole_pdo_sqlite:__construct() fails --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $dsn = 'sqlite:./bug64705NonExistingDir/bug64705NonExistingDb'; try { $pdo = new \PDO($dsn, null, null); } catch (\PDOException $e) { var_dump(!empty($e->errorInfo) && is_array($e->errorInfo)); } }); ?> --EXPECT-- bool(true) ================================================ FILE: tests/swoole_pdo_sqlite/coroutine.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLITE coroutine --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->exec('create table test (id int)'); for($i = 0; $i <= 20; $i++) { go(function() use ($i, $db) { $stmt = $db->prepare('insert into test values(?)'); $stmt->execute([$i]); $stmt = $db->prepare('select id from test where id = ?'); $stmt->execute([$i]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); }); } }); ?> --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_sqlite/coroutine2.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLITE coroutine --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); $db = new PDO('sqlite:test.db'); $db->exec('create table if not exists test (id int)'); $db->exec('delete from test'); run(function() { for($i = 0; $i <= 20; $i++) { go(function() use ($i) { $db = new PDO('sqlite:test.db'); $stmt = $db->prepare('insert into test values(?)'); $stmt->execute([$i]); $stmt = $db->prepare('select id from test where id = ?'); $stmt->execute([$i]); var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); }); } }); ?> --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_pdo_sqlite/debugdumpparams_001.phpt ================================================ --TEST-- swoole_pdo_sqlite:debugDumpParams() with bound params --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $x= $db->prepare('select :a, :b, ?'); $x->bindValue(':a', 1, PDO::PARAM_INT); $x->bindValue(':b', 'foo'); $x->bindValue(3, 1313); var_dump($x->debugDumpParams()); }); ?> --EXPECT-- SQL: [16] select :a, :b, ? Params: 3 Key: Name: [2] :a paramno=-1 name=[2] ":a" is_param=1 param_type=1 Key: Name: [2] :b paramno=-1 name=[2] ":b" is_param=1 param_type=2 Key: Position #2: paramno=2 name=[0] "" is_param=1 param_type=2 NULL ================================================ FILE: tests/swoole_pdo_sqlite/gh9032.phpt ================================================ --TEST-- swoole_pdo_sqlite: SQLite3 authorizer crashes on NULL values --SKIPIF-- --INI-- open_basedir=. --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $db->exec('attach database \':memory:\' AS "db1"'); var_dump($db->exec('create table db1.r (id int)')); try { $st = $db->prepare('attach database :a AS "db2"'); $st->execute([':a' => ':memory:']); var_dump($db->exec('create table db2.r (id int)')); } catch (PDOException $ex) { echo $ex->getMessage(), PHP_EOL; } }); ?> --EXPECT-- int(0) int(0) ================================================ FILE: tests/swoole_pdo_sqlite/open_basedir.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDO SQLite open_basedir check --SKIPIF-- --INI-- open_basedir=. --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function () { chdir(__DIR__); try { $db = new PDO('sqlite:../not_in_open_basedir.sqlite'); } catch (Exception $e) { echo $e->getMessage() . "\n"; } try { $db = new PDO('sqlite:file:../not_in_open_basedir.sqlite'); } catch (Exception $e) { echo $e->getMessage() . "\n"; } try { $db = new PDO('sqlite:file:../not_in_open_basedir.sqlite?mode=ro'); } catch (Exception $e) { echo $e->getMessage() . "\n"; } }); ?> --EXPECT-- open_basedir prohibits opening ../not_in_open_basedir.sqlite open_basedir prohibits opening file:../not_in_open_basedir.sqlite open_basedir prohibits opening file:../not_in_open_basedir.sqlite?mode=ro ================================================ FILE: tests/swoole_pdo_sqlite/pdo_035.phpt ================================================ --TEST-- swoole_pdo_sqlite: PDORow + get_parent_class() --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->exec('CREATE TABLE test (id int)'); $db->exec('INSERT INTO test VALUES (23)'); $stmt = $db->prepare('SELECT id FROM test'); $stmt->execute(); $result = $stmt->fetch(PDO::FETCH_LAZY); echo get_class($result), "\n"; var_dump(get_parent_class($result)); try { $result->foo = 1; } catch (Error $e) { echo $e->getMessage(), "\n"; } try { $result[0] = 1; } catch (Error $e) { echo $e->getMessage(), "\n"; } try { unset($result->foo); } catch (Error $e) { echo $e->getMessage(), "\n"; } try { unset($result[0]); } catch (Error $e) { echo $e->getMessage(), "\n"; } }); ?> --EXPECT-- PDORow bool(false) Cannot write to PDORow property Cannot write to PDORow offset Cannot unset PDORow property Cannot unset PDORow offset ================================================ FILE: tests/swoole_pdo_sqlite/pdo_fetch_func_001.phpt ================================================ --TEST-- swoole_pdo_sqlite:FETCH_FUNC --SKIPIF-- = 80200) { require __DIR__ . '/../include/skipif.inc'; skip('php version 8.1 or lower'); } require __DIR__ . '/../include/bootstrap.php'; require __DIR__ . '/pdo_sqlite.inc'; PdoSqliteTest::skip(); ?> --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->exec('CREATE TABLE testing (id INTEGER , name VARCHAR)'); $db->exec('INSERT INTO testing VALUES(1, "php")'); $db->exec('INSERT INTO testing VALUES(2, "")'); $st = $db->query('SELECT * FROM testing'); $st->fetchAll(PDO::FETCH_FUNC, function($x, $y) use ($st) { var_dump($st); print "data: $x, $y\n"; }); $st = $db->query('SELECT name FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 'strtoupper')); try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } class foo { public function method($x) { return "--- $x ---"; } } class bar extends foo { public function __construct($db) { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array($this, 'parent::method'))); } static public function test($x, $y) { return $x .'---'. $y; } private function test2($x, $y) { return $x; } public function test3($x, $y) { return $x .'==='. $y; } } new bar($db); $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test'))); try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } }); ?> --EXPECTF-- object(PDOStatement)#%d (1) { ["queryString"]=> string(21) "SELECT * FROM testing" } data: 1, php object(PDOStatement)#%d (1) { ["queryString"]=> string(21) "SELECT * FROM testing" } data: 2, array(2) { [0]=> string(3) "PHP" [1]=> string(0) "" } function "nothing" not found or invalid function name function "" not found or invalid function name PDOStatement::fetchAll(): Argument #2 must be a callable, null given no array or string given cannot access "self" when no class scope is active array(2) { [0]=> string(9) "--- 1 ---" [1]=> string(9) "--- 2 ---" } array(2) { [0]=> string(7) "1---php" [1]=> string(4) "2---" } non-static method bar::test2() cannot be called statically non-static method bar::test3() cannot be called statically class bar does not have a method "inexistent" ================================================ FILE: tests/swoole_pdo_sqlite/pdo_fetch_func_001_1.phpt ================================================ --TEST-- swoole_pdo_sqlite:FETCH_FUNC --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->exec('CREATE TABLE testing (id INTEGER , name VARCHAR)'); $db->exec('INSERT INTO testing VALUES(1, "php")'); $db->exec('INSERT INTO testing VALUES(2, "")'); $st = $db->query('SELECT * FROM testing'); $st->fetchAll(PDO::FETCH_FUNC, function($x, $y) use ($st) { var_dump($st); print "data: $x, $y\n"; }); $st = $db->query('SELECT name FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 'strtoupper')); try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } class foo { public function method($x) { return "--- $x ---"; } } class bar extends foo { public function __construct($db) { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array($this, 'parent::method'))); } static public function test($x, $y) { return $x .'---'. $y; } private function test2($x, $y) { return $x; } public function test3($x, $y) { return $x .'==='. $y; } } new bar($db); $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test'))); try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } try { $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); } catch (\TypeError $e) { echo $e->getMessage(), \PHP_EOL; } }); ?> --EXPECTF-- object(PDOStatement)#%d (1) { ["queryString"]=> string(21) "SELECT * FROM testing" } data: 1, php object(PDOStatement)#%d (1) { ["queryString"]=> string(21) "SELECT * FROM testing" } data: 2, array(2) { [0]=> string(3) "PHP" [1]=> string(0) "" } function "nothing" not found or invalid function name function "" not found or invalid function name PDOStatement::fetchAll(): Argument #2 must be a callable, null given no array or string given cannot access "self" when no class scope is active Deprecated: Callables of the form ["bar", "parent::method"] are deprecated in %s on line %d array(2) { [0]=> string(9) "--- 1 ---" [1]=> string(9) "--- 2 ---" } array(2) { [0]=> string(7) "1---php" [1]=> string(4) "2---" } non-static method bar::test2() cannot be called statically non-static method bar::test3() cannot be called statically class bar does not have a method "inexistent" ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite.inc ================================================ getMessage()); } } public static function create(): PDO { return new PDO(SQLITE_DSN); } } ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_extendederror_attr.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { echo "Creating new PDO" . PHP_EOL; $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )"); echo "Inserting first time which should succeed" . PHP_EOL; $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); $errorInfo = $db->errorInfo(); echo sprintf("First Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; echo "Inserting second time which should fail" . PHP_EOL; $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); $errorInfo = $db->errorInfo(); echo sprintf("Second Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; echo "Creating new PDO with Extended Result Codes turned on" . PHP_EOL; $const = PHP_VERSION_ID >= 80500 ? PDO\SQLITE::ATTR_EXTENDED_RESULT_CODES : PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES; $db = new PDO('sqlite::memory:', '', '', [$const => TRUE]); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )"); echo "Inserting first time which should succeed" . PHP_EOL; $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); $errorInfo = $db->errorInfo(); echo sprintf("First (Extended) Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; echo "Inserting second time which should fail" . PHP_EOL; $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); $errorInfo = $db->errorInfo(); echo sprintf("Second (Extended) Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; }); ?> --EXPECT-- Creating new PDO Inserting first time which should succeed First Error Info: SQLSTATE Error Code: (00000), Driver Specific Error Code: () Inserting second time which should fail Second Error Info: SQLSTATE Error Code: (23000), Driver Specific Error Code: (19) Creating new PDO with Extended Result Codes turned on Inserting first time which should succeed First (Extended) Error Info: SQLSTATE Error Code: (00000), Driver Specific Error Code: () Inserting second time which should fail Second (Extended) Error Info: SQLSTATE Error Code: (HY000), Driver Specific Error Code: (1555) ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_filename_uri.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing filename uri --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { // create with default read-write|create mode $filename = "file:" . __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_filename_uri.db"; $db = new PDO('sqlite:' . $filename); var_dump($db->exec('CREATE TABLE test1 (id INT);')); // create with readonly mode $filename = "file:" . __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_filename_uri.db?mode=ro"; $db = new PDO('sqlite:' . $filename); var_dump($db->exec('CREATE TABLE test2 (id INT);')); }); ?> --CLEAN-- --EXPECTF-- int(0) Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in %s Stack trace: %s %A thrown in %s ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_get_attribute.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing getAttribute() --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $pdo = new PDO('sqlite::memory:'); var_dump($pdo->getAttribute(PDO::ATTR_SERVER_VERSION)); var_dump($pdo->getAttribute(PDO::ATTR_CLIENT_VERSION)); }); ?> --EXPECTF-- string(%d) "%s" string(%d) "%s" ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_lastinsertid.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing lastInsertId() --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->query('CREATE TABLE IF NOT EXISTS foo (id INT AUTO INCREMENT, name TEXT)'); $db->query('INSERT INTO foo VALUES (NULL, "PHP")'); $db->query('INSERT INTO foo VALUES (NULL, "PHP6")'); var_dump($db->query('SELECT * FROM foo')); var_dump($db->errorInfo()); var_dump($db->lastInsertId()); $db->query('DROP TABLE foo'); }); ?> --EXPECTF-- object(PDOStatement)#%d (1) { ["queryString"]=> string(17) "SELECT * FROM foo" } array(3) { [0]=> string(5) "00000" [1]=> NULL [2]=> NULL } string(1) "2" ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_open_flags.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing open flags --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function () { $filename = __DIR__ . DIRECTORY_SEPARATOR . 'pdo_sqlite_open_flags.db'; // Default open flag is read-write|create $db = new PDO('sqlite:' . $filename, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); var_dump($db->exec('CREATE TABLE test1 (id INT);')); if (PHP_VERSION_ID >= 80500) { $key = Pdo\Sqlite::ATTR_OPEN_FLAGS; $value = Pdo\SQLITE::OPEN_READONLY; } else { $key = PDO::SQLITE_ATTR_OPEN_FLAGS; $value = PDO::SQLITE_OPEN_READONLY; } $db = new PDO('sqlite:' . $filename, null, null, [$key => $value, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); var_dump($db->exec('CREATE TABLE test2 (id INT);')); $db->exec('drop table test1'); $db->exec('drop table test2'); }); ?> --CLEAN-- --EXPECTF-- int(0) Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in %s Stack trace: %s %A thrown in %s ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_statement_getattribute.phpt ================================================ --TEST-- swoole_pdo_sqlite:getAttribute() --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $st = $db->prepare('SELECT 1;'); $const = PHP_VERSION_ID >= 80500 ? PDO\SQLITE::ATTR_READONLY_STATEMENT: PDO::SQLITE_ATTR_READONLY_STATEMENT; var_dump($st->getAttribute($const)); $st = $db->prepare('CREATE TABLE test (a TEXT);'); var_dump($st->getAttribute($const)); }); ?> --EXPECT-- bool(true) bool(false) ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_tostring_exception.phpt ================================================ --TEST-- swoole_pdo_sqlite: __toString() exception during PDO Sqlite parameter binding --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->exec('CREATE TABLE t(id int, v varchar(255))'); $stmt = $db->prepare('INSERT INTO t VALUES(:i, :v)'); $param1 = 1234; $stmt->bindValue('i', $param1); $param2 = "foo"; $stmt->bindParam('v', $param2); $param2 = new throws; try { $stmt->execute(); } catch (Exception $e) { echo "Exception thrown ...\n"; } try { $stmt->execute(); } catch (Exception $e) { echo "Exception thrown ...\n"; } $query = $db->query("SELECT * FROM t"); while ($row = $query->fetch(PDO::FETCH_ASSOC)) { print_r($row); } }); ?> --EXPECT-- Exception thrown ... Exception thrown ... ================================================ FILE: tests/swoole_pdo_sqlite/pdo_sqlite_transaction.phpt ================================================ --TEST-- swoole_pdo_sqlite: Testing transaction --SKIPIF-- --FILE-- SWOOLE_HOOK_PDO_SQLITE]); run(function() { $db = new PDO('sqlite::memory:'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->beginTransaction(); $db->query('CREATE TABLE IF NOT EXISTS foobar (id INT AUTO INCREMENT, name TEXT)'); $db->commit(); $db->beginTransaction(); $db->query('INSERT INTO foobar VALUES (NULL, "PHP")'); $db->query('INSERT INTO foobar VALUES (NULL, "PHP6")'); $db->rollback(); $r = $db->query('SELECT COUNT(*) FROM foobar'); var_dump($r->rowCount()); $db->query('DROP TABLE foobar'); }); ?> --EXPECTF-- int(0) Warning: PDO::query(): SQLSTATE[HY000]: General error: 6 database table is locked in %s on line %d ================================================ FILE: tests/swoole_process/alarm.phpt ================================================ --TEST-- swoole_process: alarm --SKIPIF-- --FILE-- 10) { Process::alarm(-1); Process::signal(SIGALRM, null); Swoole\Event::del(STDIN); Swoole\Event::exit(); } }); //100ms Process::alarm(10 * 1000); //never calback Swoole\Event::add(STDIN, function () {}); Swoole\Event::wait(); ?> --EXPECT-- #0 alarm #1 alarm #2 alarm #3 alarm #4 alarm #5 alarm #6 alarm #7 alarm #8 alarm #9 alarm #10 alarm ================================================ FILE: tests/swoole_process/close.phpt ================================================ --TEST-- swoole_process: close --SKIPIF-- --FILE-- close(); //}); //$proc->start(); // //$proc = new Swoole\Process(swoole_function(Swoole\Process $proc) { // usleep(200000); // // Assert::true(false, 'never here'); //}); //$proc->start(); //$proc->close(); // // //\Swoole\Process::wait(true); //\Swoole\Process::wait(true); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/coro/ipc.phpt ================================================ --TEST-- swoole_process/coro: ipc with coroutine --SKIPIF-- --FILE-- exportSocket(); echo $socket->recv(); $socket->send("hello proc2\n"); echo "proc1 stop\n"; }, false, 1, true); Assert::assert($proc1->start()); $proc2 = new Swoole\Process(function (Swoole\Process $proc) use ($proc1) { Co::sleep(0.01); $socket = $proc1->exportSocket(); $socket->send("hello proc1\n"); echo $socket->recv(); echo "proc2 stop\n"; }, false, 0, true); Assert::assert($proc2->start()); Swoole\Process::wait(true); Swoole\Process::wait(true); ?> --EXPECT-- hello proc1 proc1 stop hello proc2 proc2 stop ================================================ FILE: tests/swoole_process/coro/set_protocol.phpt ================================================ --TEST-- swoole_process/coro: ipc with coroutine --SKIPIF-- --FILE-- exportSocket(); $socket->setProtocol([ 'open_length_check' => true, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 2, ]); while ($data = $socket->recvPacket()) { if (strlen($data) == 2) { echo "END\n"; return; } Assert::lengthBetween($data, 1024, 61000); } echo "ERROR\n"; }, false, 1, true); Assert::assert($proc1->start()); $n = N; while ($n--) { $len = rand(1024, 60000); $pkg = pack('n', $len) . random_bytes($len); $proc1->write($pkg); } $proc1->write(pack('n', 0)); Swoole\Process::wait(true); ?> --EXPECT-- END ================================================ FILE: tests/swoole_process/coro/signal.phpt ================================================ --TEST-- swoole_process/coro: signal with coroutine --SKIPIF-- --FILE-- start(); Process::signal(SIGCHLD, function ($sig) { while ($ret = Process::wait(false)) { echo "PID={$ret['pid']}\n"; } }); go(function () { Co::sleep(.3); echo "END\n"; }); Event::wait(); ?> --EXPECTF-- PID=%d END ================================================ FILE: tests/swoole_process/coro/start.phpt ================================================ --TEST-- swoole_process/coro: start with coroutine --SKIPIF-- --FILE-- start(); Assert::assert($r > 0); $proc->close(); \Swoole\Process::wait(true); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/ctor.phpt ================================================ --TEST-- swoole_process: ctor --SKIPIF-- --FILE-- --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/daemon.phpt ================================================ --TEST-- swoole_process: daemon --SKIPIF-- --FILE-- start(); Process::wait(); fclose($sockets[0]); while (true) { $fp = $sockets[1]; $line = fgets($fp); if (empty($line)) { break; } else { echo $line; if ($line == "END\n") { break; } } } ?> --EXPECT-- ERROR 1 ERROR 2 ERROR 3 ERROR 4 ERROR 5 END ================================================ FILE: tests/swoole_process/deamon.phpt ================================================ --TEST-- swoole_process: deamon --SKIPIF-- --FILE-- push(posix_getpid()); }); $proc->useQueue(); $forkPid = $proc->start(); $demonPid = intval($proc->pop()); Assert::assert($forkPid !== $demonPid); \Swoole\Process::kill($demonPid, SIGKILL); \Swoole\Process::wait(true); \Swoole\Process::wait(true); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/echo.py ================================================ import sys def main(): s = raw_input() print "Python: " + s main() ================================================ FILE: tests/swoole_process/enable_coroutine.phpt ================================================ --TEST-- swoole_process: push --SKIPIF-- --FILE-- start(); $process::wait(); $process = new Swoole\Process(function(Swoole\Process $worker) { echo Co::getCid() . PHP_EOL; }, false, false, false); $process->set(['enable_coroutine' => true]); $process->start(); $process::wait(); ?> --EXPECT-- 1 1 ================================================ FILE: tests/swoole_process/exception.phpt ================================================ --TEST-- swoole_process: exception --SKIPIF-- --FILE-- pipe, function (Swoole\Process $process) { }); try { $this->run(); } catch (Throwable $e) { $this->onException($e); } }); $process->start(); } } class Process6 extends AbstractProcess { public function run() { AAAA(); } public function onException(Throwable $e) { throw $e; } } (new Process6())->start(); ?> --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function AAAA() in %s:%d Stack trace: #0 %s(%d): Process6->run() #1 [internal function]: AbstractProcess->{closure%S}(Object(Swoole\Process)) #2 %s(%d): Swoole\Process->start() #3 %s(%d): AbstractProcess->start() #4 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_process/exec.phpt ================================================ --TEST-- swoole_process: exec --SKIPIF-- --FILE-- exec("/usr/bin/printf", ["HELLO"]); }, true); $proc->start(); echo $proc->read(); $proc->exec("/usr/bin/printf", [" WORLD"]); \Swoole\Process::wait(true); ?> --EXPECT-- HELLO WORLD ================================================ FILE: tests/swoole_process/exit.phpt ================================================ --TEST-- swoole_process: exit --SKIPIF-- --FILE-- exit(9); }); $pid = $proc->start(); $i = \Swoole\Process::wait(true); Assert::same($i["code"], 9); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/freeQueue.phpt ================================================ --TEST-- swoole_process: freeQueue --SKIPIF-- --FILE-- useQueue(); Assert::assert($r); $proc->start(); $r = $proc->freeQueue(); Assert::assert($r); \Swoole\Process::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process/getaffinity.phpt ================================================ --TEST-- swoole_process: getAffinity --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_process/ignore_sigpipe.phpt ================================================ --TEST-- swoole_process: ignore SIGPIPE --SKIPIF-- --FILE-- setWaitTimeout(5); $pm->parentFunc = function ($pid) use ($pm) { echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'hook_flags' => SWOOLE_HOOK_ALL, 'log_level' => SWOOLE_LOG_ERROR, ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 1) == false) { echo "ERROR\n"; return; } while (1) { usleep(10000); if ($cli->send($GLOBALS['data']) == false) { Assert::eq($cli->errCode, SOCKET_EPIPE); break; } } $pm->wakeup(); }); $serv->on(Constant::EVENT_CONNECT, function (Server $serv, $fd, $rid) { $serv->close($fd); }); $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process/ignore_sigpipe_2.phpt ================================================ --TEST-- swoole_process: close --SKIPIF-- --FILE-- setWaitTimeout(5); $pm->parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 1) == false) { echo "ERROR\n"; return; } while (1) { usleep(10000); if ($cli->send($GLOBALS['data']) == false) { Assert::eq($cli->errCode, SOCKET_EPIPE); break; } } }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'hook_flags' => SWOOLE_HOOK_ALL, 'log_level' => SWOOLE_LOG_WARNING, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_CONNECT, function (Server $serv, $fd, $rid) { $serv->close($fd); }); $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process/kill.phpt ================================================ --TEST-- swoole_process: kill --SKIPIF-- --FILE-- start(); Swoole\Process::kill($pid, SIGKILL); $i = \Swoole\Process::wait(true); Assert::same($i["signal"], SIGKILL); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/msgq_capacity.phpt ================================================ --TEST-- swoole_process: sysv msgqueue capacity --SKIPIF-- --FILE-- useQueue(ftok(__DIR__, 1), 1, 1024 * 1024 * 64); const N = 32 * 1024 * 1024; $bytes = 0; while ($bytes < N) { $data = RandStr::getBytes(rand(4000, 8000)); $bytes += strlen($data); $process->push($data); } Assert::assert($process->statQueue()['queue_bytes'] > N); $rd_bytes = 0; while ($rd_bytes < N) { $recv = $process->pop(); $rd_bytes += strlen($recv); } Assert::same($process->statQueue()['queue_bytes'], 0); $process->freeQueue(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process/name.phpt ================================================ --TEST-- swoole_process: name --SKIPIF-- --FILE-- name($name); sleep(PHP_INT_MAX); }); $pid = $proc->start(); $count = (int)trim(shell_exec("ps aux|grep $name|grep -v grep|wc -l")); Assert::same($count, 1); \Swoole\Process::kill($pid, SIGKILL); \Swoole\Process::wait(true); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/null_callback.phpt ================================================ --TEST-- swoole_process: null callback --SKIPIF-- --FILE-- start(); ?> --EXPECTF-- Warning: Swoole\Process::start(): illegal callback function in %s ================================================ FILE: tests/swoole_process/pop.phpt ================================================ --TEST-- swoole_process: pop --SKIPIF-- --FILE-- useQueue(); $proc->push("SUCCESS"); echo $proc->pop(); $proc->freeQueue(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/priority.phpt ================================================ --TEST-- swoole_process: priority --SKIPIF-- --FILE-- setPriority(PRIO_PROCESS, PRIORITY); $priority = $worker->getPriority(PRIO_PROCESS); Assert::eq($priority, PRIORITY); usleep(20000); $worker->exit(0); }, false, false); $pid = $process->start(); Process::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process/priority_error.phpt ================================================ --TEST-- swoole_process: priority [2] --SKIPIF-- --FILE-- getPriority(-1000, posix_getpid()), false); Assert::eq(swoole_last_error(), SOCKET_EINVAL); Assert::eq($process->setPriority(-1000, posix_getpid(), PRIORITY), false); Assert::eq(swoole_last_error(), SOCKET_EINVAL); Assert::eq(@$process->getPriority(PRIO_USER, null), false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); Assert::eq(@$process->setPriority(PRIO_USER, PRIORITY, null), false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); ?> --EXPECT-- ================================================ FILE: tests/swoole_process/process_exec.phpt ================================================ --TEST-- swoole_process: exec --SKIPIF-- --FILE-- start(); function python_process(Swoole\Process $worker) { $worker->exec('/usr/bin/python', array(__DIR__ . "/echo.py")); } $process->write("Hello World\n"); echo $process->read(); ?> Done --EXPECTREGEX-- Python: Hello World Done.* ================================================ FILE: tests/swoole_process/process_id.phpt ================================================ --TEST-- swoole_process: Github bug #5825 --SKIPIF-- --FILE-- id, $n); }); Assert::same($process->id, $n); $process->start(); } ?> --EXPECT-- ================================================ FILE: tests/swoole_process/process_msgqueue.phpt ================================================ --TEST-- swoole_process: sysv msgqueue --SKIPIF-- --FILE-- useQueue(); $bytes = 0; foreach(range(1, 10) as $i) { $data = "hello worker[$i]"; $bytes += strlen($data); $process->push($data); } $queue = $process->statQueue(); ($queue['queue_num'] == 10 && $queue['queue_bytes'] == $bytes) && $output = "Success\n"; echo $output; $process->freeQueue(); ?> Done --EXPECTREGEX-- Success Done.* ================================================ FILE: tests/swoole_process/process_push.phpt ================================================ --TEST-- swoole_process: push --SKIPIF-- --FILE-- pop(); echo "$recv"; usleep(20000); $worker->exit(0); }, false, false); $process->useQueue(); $pid = $process->start(); $process->push("hello worker\n"); Process::wait(); ?> --EXPECT-- hello worker ================================================ FILE: tests/swoole_process/process_select.phpt ================================================ --TEST-- swoole_process: select --SKIPIF-- --FILE-- write("hello master\n"); $worker->exit(0); }, false); $pid = $process->start(); $r = array($process); $w = array(); $e = array(); $ret = swoole_select($r, $w, $e, 1.0); echo $process->read(); ?> Done --EXPECTREGEX-- hello master Done.* ================================================ FILE: tests/swoole_process/push.phpt ================================================ --TEST-- swoole_process: push --SKIPIF-- --FILE-- useQueue(); //$r = $proc->push("\0"); // Assert::false($r); // TODO max data ? // $r = $proc->push(str_repeat("\0", 1024 * 1024 * 8)); // Assert::false($r); //$proc->freeQueue(); $proc = new Swoole\Process(function() {}); $proc->useQueue(); $proc->start(); $r = $proc->push("\0"); Assert::true($r); $proc->freeQueue(); \Swoole\Process::wait(true); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/read.phpt ================================================ --TEST-- swoole_process: read --SKIPIF-- --FILE-- write("SUCCESS"); Assert::same($r, 7); }); $r = $proc->start(); Assert::assert($r > 0); Swoole\Timer::after(10, function() use($proc) { echo $proc->read(); // Swoole\Event::exit(); }); \Swoole\Process::wait(true); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/redirect.phpt ================================================ --TEST-- swoole_process: redirect --SKIPIF-- --FILE-- start(); $r = $proc->read(); echo "READ: $r~"; \Swoole\Process::wait(true); ?> --EXPECT-- READ: SUCCESS~ ================================================ FILE: tests/swoole_process/setaffinity.phpt ================================================ --TEST-- swoole_process: setAffinity --SKIPIF-- --FILE-- 1) { $r = Swoole\Process::setaffinity([0, 1]); Assert::assert($r); } echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/signal.phpt ================================================ --TEST-- swoole_process: signal --SKIPIF-- --FILE-- start(); Swoole\Timer::after(500, function() use ($pid) { Process::kill($pid, SIGTERM); Swoole\Timer::after(500, function() use ($pid) { Process::kill($pid, SIGTERM); }); }); //never calback Swoole\Event::add(STDIN, function ($fp) { echo fread($fp, 8192); }); Swoole\Event::wait(); ?> --EXPECT-- CHILD SIGTERM CHILD EXIT PARENT WAIT ================================================ FILE: tests/swoole_process/signal_in_manager.phpt ================================================ --TEST-- swoole_process: signal in manager --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { usleep(100000); $manager_pid = file_get_contents(PID_FILE); Process::kill($manager_pid, SIGINT); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { file_put_contents(PID_FILE, $serv->getManagerPid()); Process::signal(SIGINT, function () use ($pm) { echo "SIGINT triggered\n"; $pm->wakeup(); }); $pm->wakeup(); }); $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(PID_FILE); ?> --EXPECT-- SIGINT triggered ================================================ FILE: tests/swoole_process/signal_in_manager_2.phpt ================================================ --TEST-- swoole_process: signal in manager with task worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { usleep(100000); $manager_pid = file_get_contents(PID_FILE); Process::kill($manager_pid, SIGINT); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { file_put_contents(PID_FILE, $serv->getManagerPid()); Process::signal(SIGINT, function () use ($pm) { echo "SIGINT triggered\n"; $pm->wakeup(); }); $pm->wakeup(); }); $serv->on('Task', function ($server, $taskId, $workerId, $data) { }); $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(PID_FILE); ?> --EXPECT-- SIGINT triggered ================================================ FILE: tests/swoole_process/signal_in_manager_3.phpt ================================================ --TEST-- swoole_process: signal in manager with task worker - 2 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { usleep(100000); $manager_pid = file_get_contents(PID_FILE); Process::kill($manager_pid, SIGINT); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 2, 'task_worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { file_put_contents(PID_FILE, $serv->getManagerPid()); Process::signal(SIGINT, function () use ($pm) { echo "SIGINT triggered\n"; $pm->wakeup(); }); $pm->wakeup(); }); $serv->on('workerStop', function ($server) { echo "worker exit\n"; }); $serv->on('Task', function ($server, $taskId, $workerId, $data) { }); $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(PID_FILE); ?> --EXPECT-- SIGINT triggered worker exit worker exit worker exit ================================================ FILE: tests/swoole_process/signal_in_task_worker.phpt ================================================ --TEST-- swoole_process: signal in task worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $_pid = file_get_contents(PID_FILE); Process::kill($_pid, SIGINT); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { if ($serv->taskworker) { file_put_contents(PID_FILE, $serv->getWorkerPid()); Process::signal(SIGINT, function () use($pm) { echo "SIGINT triggered\n"; $pm->wakeup(); }); $pm->wakeup(); } }); $serv->on("Task", function (Server $serv) use ($pm) { }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(PID_FILE); ?> --EXPECT-- SIGINT triggered ================================================ FILE: tests/swoole_process/signal_twice.phpt ================================================ --TEST-- swoole_process: signal --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $n = N; while($n--) { Process::kill($pid, SIGUSR1); $pm->wait(); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $n = N; while($n--) { run(static function () use($n, $pm){ $running = true; Process::signal(SIGUSR1, function() use(&$running, $n) { $running = false; echo 'sigusr1 one-'.$n.PHP_EOL; }); $pm->wakeup(); go(static function () use(&$running) { while ($running) { Co::sleep(0.1); } }); }); } $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- sigusr1 one-1 sigusr1 one-0 ================================================ FILE: tests/swoole_process/start.phpt ================================================ --TEST-- swoole_process: start --SKIPIF-- --FILE-- start(); Assert::assert($r > 0); $proc->close(); \Swoole\Process::wait(true); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/timeout.phpt ================================================ --TEST-- swoole_process: pipe read timeout --SKIPIF-- --FILE-- start(); Assert::assert($r > 0); ini_set("swoole.display_errors", "off"); $proc->setTimeout(0.5); $ret = $proc->read(); Assert::false($ret); Swoole\Process::kill($proc->pid, SIGKILL); \Swoole\Process::wait(true); ?> --EXPECT-- ================================================ FILE: tests/swoole_process/useQueue.phpt ================================================ --TEST-- swoole_process: useQueue --SKIPIF-- --FILE-- pop(); }); $proc->useQueue(); $proc->start(); $proc->push("SUCCESS"); \Swoole\Process::wait(true); $proc->freeQueue(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/wait.phpt ================================================ --TEST-- swoole_process: wait --SKIPIF-- --FILE-- start(); $info = \Swoole\Process::wait(true); Assert::same($pid, $info["pid"]); Assert::same($info["code"], 0); Assert::same($info["signal"], 0); $proc = new Swoole\Process(function() { exit(1); }); $pid = $proc->start(); $info = \Swoole\Process::wait(true); Assert::same($pid, $info["pid"]); Assert::same($info["code"], 1); Assert::same($info["signal"], 0); $proc = new Swoole\Process(function() { \Swoole\Process::kill(posix_getpid(), SIGTERM); }); $pid = $proc->start(); $info = \Swoole\Process::wait(true); Assert::same($pid, $info["pid"]); Assert::same($info["code"], 0); Assert::same($info["signal"], SIGTERM); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/wait_signal.phpt ================================================ --TEST-- swoole_process: wait signal --SKIPIF-- --FILE-- true]); Process::signal(SIGINT, function () { echo "SIGINT\n"; Process::signal(SIGINT, null); }); echo "START\n"; Event::wait(); }, true, true); $r = $proc->start(); Assert::assert($r > 0); echo $proc->read(); Process::kill($r, SIGINT); echo $proc->read(); $retval = Process::wait(true); Assert::eq($retval['pid'], $r); Assert::eq($retval['code'], 0); Assert::eq($retval['signal'], 0); ?> --EXPECT-- START SIGINT ================================================ FILE: tests/swoole_process/write.phpt ================================================ --TEST-- swoole_process: write --SKIPIF-- --FILE-- write("SUCCESS"); Assert::same($r, 7); }); $r = $proc->start(); Assert::assert($r > 0); Swoole\Timer::after(10, function() use($proc) { echo $proc->read(); }); \Swoole\Process::wait(true); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_process/write_in_worker.phpt ================================================ --TEST-- swoole_process: write in worker --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $process = new Process(function (Process $process) use ($pm, $serv, $counter) { if ($counter->get() != 1) { $counter->set(1); echo "process start\n"; for ($i = 0; $i < 1024; $i++) { $data = $process->read(); Assert::same(strlen($data), 8192); } echo "process end\n"; $pm->wakeup(); } }); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($process, $pm) { usleep(1); for ($i = 0; $i < 1024; $i++) { Assert::same($process->write(str_repeat('A', 8192)), 8192); } }); $serv->on('WorkerStop', function (Server $serv) use ($process) { echo "worker end\n"; }); $serv->on('Receive', function () {}); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- process start process end worker end ================================================ FILE: tests/swoole_process_pool/bug_2652.phpt ================================================ --TEST-- swoole_process_pool: bug Github#2639 --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { $pool->getProcess($workerId)->useQueue(MSGQ_KEY); $pool->getProcess($workerId)->push('test'); Assert::same('test', $pool->getProcess($workerId)->pop()); $pool->shutdown(); sleep(20); echo "ERROR\n"; }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/create_websocket_server.phpt ================================================ --TEST-- swoole_process_pool: create websocket server in process pool --SKIPIF-- --FILE-- on("WorkerStart", function ($pool, $workerId) use ($port) { if ($workerId === 0) { $server = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $server->on("message", function ($server, $frame) { $server->push($frame->fd, $frame->data); }); $server->start(); } elseif ($workerId === 1) { run(function () use ($port, $pool) { $client = new Client('127.0.0.1', $port); while (!$client->upgrade('/')) {} $data = 'data'; $client->push($data); $frame = $client->recv(); Assert::eq($frame->data, $data); $pool->shutdown(); }); } }); $pool->start(); echo "DONE\n"; ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_process_pool/detach.phpt ================================================ --TEST-- swoole_process_pool: detach --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm, $atomic) { foreach (range(1, 2) as $i) { $fp = stream_socket_client("tcp://127.0.0.1:" . $pm->getFreePort(), $errno, $errstr) or die("error: $errstr\n"); $msg = "HELLO-{$i}"; fwrite($fp, pack('N', strlen($msg)) . $msg); } $pm->wait(); Assert::eq($atomic->get(), N + 1); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $pool = new Pool(1, SWOOLE_IPC_SOCKET); $pool->on('WorkerStart', function (Pool $pool, $workerId) use ($pm, $atomic) { echo("[Worker #{$workerId}] WorkerStart\n"); if ($atomic->get() == 0) { $pm->wakeup(); } }); $pool->on('Message', function (Pool $pool, $msg) use ($pm, $atomic) { if ($atomic->get() == 0) { $atomic->add(); $pool->detach(); $n = N; while ($n--) { usleep(1000); $atomic->add(); } $pm->wakeup(); } else { echo $msg . PHP_EOL; } }); $pool->listen('127.0.0.1', $pm->getFreePort()); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- [Worker #0] WorkerStart [Worker #0] WorkerStart HELLO-2 DONE ================================================ FILE: tests/swoole_process_pool/enable_coroutine.phpt ================================================ --TEST-- swoole_process_pool: enable coroutine --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($counter) { if ($counter->get() <= 5) { Co::sleep(0.05); $counter->add(1); echo "hello world\n"; } }); $pool->on("workerStop", function ($pool, $data) use ($counter) { echo "worker stop\n"; if ($counter->get() > 5) { $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- hello world worker stop hello world worker stop hello world worker stop hello world worker stop hello world worker stop hello world worker stop ================================================ FILE: tests/swoole_process_pool/enable_coroutine2.phpt ================================================ --TEST-- swoole_process_pool: enable coroutine --SKIPIF-- --FILE-- set(['enable_coroutine' => true]); $counter = new Swoole\Atomic(0); $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($counter) { if ($counter->get() <= 5) { Co::sleep(0.05); $counter->add(1); echo "hello world\n"; } }); $pool->on("workerStop", function ($pool, $data) use ($counter) { echo "worker stop\n"; if ($counter->get() > 5) { $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- hello world worker stop hello world worker stop hello world worker stop hello world worker stop hello world worker stop hello world worker stop ================================================ FILE: tests/swoole_process_pool/export_socket.phpt ================================================ --TEST-- swoole_process_pool: exportSocket --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { $process = $pool->getProcess(0); $socket = $process->exportSocket(); if ($workerId == 0) { echo $socket->recv(); $socket->send("hello proc1\n"); echo "proc0 stop\n"; } else { $socket->send("hello proc0\n"); echo $socket->recv(); echo "proc1 stop\n"; $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- hello proc0 proc0 stop hello proc1 proc1 stop ================================================ FILE: tests/swoole_process_pool/getprocess_1.phpt ================================================ --TEST-- swoole_process_pool: getProcess [1] --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { $process = $pool->getProcess(); Assert::same($process->pid, posix_getpid()); $pool->shutdown(); sleep(20); echo "ERROR\n"; }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/getprocess_2.phpt ================================================ --TEST-- swoole_process_pool: getProcess [2] --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { if ($workerId == 0) { $process1 = $pool->getProcess(); $process2 = $pool->getProcess(1); $process2->write(str_repeat('A', N)); Assert::same(@$pool->getProcess(2), false); if ($process1->read() == 'shutdown') { $pool->shutdown(); } } }); $pool->on("message", function ($pool, $data) { Assert::length($data, N); $process1 = $pool->getProcess(0); $process1->write("shutdown"); }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/getprocess_3.phpt ================================================ --TEST-- swoole_process_pool: getProcess [3] --SKIPIF-- --FILE-- set(['max_wait_time' => 2]); $pool->on('workerStart', function (Pool $pool, int $workerId) { if ($workerId == 0) { usleep(100_000); $process1 = $pool->getProcess(1); phpt_var_dump($process1); $pid1 = $process1->pid; Process::kill($process1->pid, SIGTERM); usleep(100_000); $process2 = $pool->getProcess(1); phpt_var_dump($process2); $pid2 = $process2->pid; Assert::notEq($pid1, $pid2); $pool->shutdown(); } }); $pool->on("message", function ($pool, $data) { }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/getprocess_4.phpt ================================================ --TEST-- swoole_process_pool: get process 4 [async] --SKIPIF-- --FILE-- set(['enable_coroutine' => true]); $pool->on('workerStart', function (Pool $pool, int $workerId) { if ($workerId == 0) { usleep(1000); $process1 = $pool->getProcess(1); phpt_var_dump($process1); $pid1 = $process1->pid; Process::kill($process1->pid, SIGTERM); usleep(100000); $process2 = $pool->getProcess(1); phpt_var_dump($process2); $pid2 = $process2->pid; Assert::notEq($pid1, $pid2); $pool->shutdown(); } }); $pool->on("message", function ($pool, $data) { }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/getprocess_5.phpt ================================================ --TEST-- swoole_process_pool: getProcess [5] --SKIPIF-- --FILE-- on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) { if ($workerId == 0) { $process1 = $pool->getProcess(); $process2 = $pool->getProcess(1); $process2->write(str_repeat('A', N)); Assert::same(@$pool->getProcess(2), false); if ($process1->read() == 'shutdown') { $pool->shutdown(); } } }); $pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) { Assert::length($data, N); $process1 = $pool->getProcess(0); $process1->write("shutdown"); }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/master_callback.phpt ================================================ --TEST-- swoole_process_pool: master callback --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { echo "worker start\n"; Assert::true($pool->workerRunning); Assert::eq($pool->workerId, 0); Assert::eq($pool->workerPid, posix_getpid()); pcntl_signal(SIGTERM, function (){ }); $pool->shutdown(); sleep(20); echo "worker exit\n"; }); $pool->on('workerStop', function (Swoole\Process\Pool $pool, int $workerId) { Assert::false($pool->workerRunning); echo "worker stop\n"; }); $pool->on('start', function (Swoole\Process\Pool $pool) { Assert::true($pool->running); echo "start\n"; }); $pool->on('shutdown', function (Swoole\Process\Pool $pool) { Assert::false($pool->running); echo "shutdown\n"; }); $pool->start(); ?> --EXPECT-- start worker start shutdown worker exit worker stop ================================================ FILE: tests/swoole_process_pool/master_pid.phpt ================================================ --TEST-- swoole_process_pool: master pid --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pid) { Assert::assert($pool->master_pid == $pid); posix_kill($pid, SIGTERM); sleep(20); echo "ERROR\n"; }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/max_wait_time.phpt ================================================ --TEST-- swoole_process_pool: max wait time --SKIPIF-- --FILE-- set([ Constant::OPTION_ENABLE_COROUTINE => true, Constant::OPTION_MAX_WAIT_TIME => 1, ]); $pool->on('workerStart', function (Pool $pool, int $workerId) use ($atomic): void { echo "workerStart: $workerId" . PHP_EOL; $atomic->wait(-1); }); $pool->on('start', function () use ($pool): void { Timer::after(500, function () use ($pool): void { $pool->shutdown(); }); echo 'start' . PHP_EOL; }); $pool->on('shutdown', function () use ($atomic): void { echo 'shutdown' . PHP_EOL; }); $pool->start(); })(); ?> --EXPECTF-- start workerStart: %d workerStart: %d workerStart: %d workerStart: %d shutdown ================================================ FILE: tests/swoole_process_pool/message.phpt ================================================ --TEST-- swoole_process_pool: simple send test --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', 8089, 5)); $data = "hello swoole!"; $client->send(pack('N', strlen($data)) . $data); $ret = $client->recv(); $len = unpack('Nlen', substr($ret, 0, 4))['len']; $ret .= $client->recv($len - (strlen($ret) - 4)); $ret = substr($ret, 4, $len); echo $ret; $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $pool = new Swoole\Process\Pool(1, SWOOLE_IPC_SOCKET); $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', 8089, 5)); $data = "hello swoole! (from workerStart)"; $client->send(pack('N', strlen($data)) . $data); $client->close(); }); $pool->on("message", function (Swoole\Process\Pool $pool, string $message) { echo "{$message}\n"; if ($message === "hello swoole!") { $pool->write("hello "); $pool->write("client!"); $pool->write("\n"); } }); $pool->listen('127.0.0.1', 8089); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- hello swoole! (from workerStart) hello swoole! hello client! ================================================ FILE: tests/swoole_process_pool/message_async.phpt ================================================ --TEST-- swoole_process_pool: message async [disable message bus] --SKIPIF-- --FILE-- set([ 'enable_coroutine' => true, ]); $pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { if ($workerId == 0) { foreach ($in as $item) { Assert::true($pool->sendMessage($item, 1)); System::sleep(0.002); } } }); $pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { $out[] = $data; if (count($out) == N) { Assert::eq($in, $out); echo "DONE\n"; $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process_pool/message_bus.phpt ================================================ --TEST-- swoole_process_pool: message bus --SKIPIF-- --FILE-- set([ 'enable_coroutine' => true, 'enable_message_bus' => true, ]); $pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { if ($workerId == 0) { foreach ($in as $item) { Assert::true($pool->sendMessage($item, 1)); Co::sleep(0.002); } } }); $pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { $out[] = $data; if (count($out) == N) { Assert::eq($in, $out); echo "DONE\n"; $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process_pool/message_bus_sync.phpt ================================================ --TEST-- swoole_process_pool: message bus [sync] --SKIPIF-- --FILE-- set([ 'enable_message_bus' => true, ]); $pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { if ($workerId == 0) { foreach ($in as $item) { Assert::true($pool->sendMessage($item, 1)); usleep(2000); } } }); $pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { $out[] = $data; if (count($out) == N) { Assert::eq($in, $out); echo "DONE\n"; $pool->shutdown(); } }); $pool->start(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process_pool/msgqueue.phpt ================================================ --TEST-- swoole_process_pool: sysv msgqueue --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $atomic) { $seg = msg_get_queue(MSGQ_KEY); foreach (range(1, N) as $i) { $data = json_encode(['data' => base64_encode(random_bytes(1024)), 'id' => uniqid(), 'index' => $i,]); msg_send($seg, $i, $data, false); } }; $pm->childFunc = function () use ($pm, $atomic) { $pool = new Swoole\Process\Pool(1, SWOOLE_IPC_MSGQUEUE, MSGQ_KEY); $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pm) { echo "worker start\n"; $pm->wakeup(); }); $pool->on("message", function (Swoole\Process\Pool $pool, string $message) use ($atomic) { $data = json_decode($message, true); Assert::assert($data); Assert::assert(is_array($data)); Assert::same(strlen(base64_decode($data['data'])), 1024); $atomic->add(1); if ($atomic->get() == 100) { $pool->shutdown(); echo "DONE\n"; } }); $pool->on('workerStop', function (Swoole\Process\Pool $pool, int $workerId) { echo "worker stop\n"; }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- worker start DONE worker stop ================================================ FILE: tests/swoole_process_pool/msgqueue_2.phpt ================================================ --TEST-- swoole_process_pool: sysv msgqueue [2] --SKIPIF-- --FILE-- on('workerStart', function (Pool $pool, int $workerId) { if ($workerId == 0) { echo "worker start\n"; Assert::true($pool->getProcess()->push('hello world' . PHP_EOL)); } else { echo $pool->getProcess()->pop(); $pool->shutdown(); } }); $pool->on('workerStop', function (Pool $pool, int $workerId) { if ($workerId == 1) { echo "worker stop\n"; } }); $pool->start(); ?> --EXPECT-- worker start hello world worker stop ================================================ FILE: tests/swoole_process_pool/reload.phpt ================================================ --TEST-- swoole_process_pool: reload --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { Swoole\Process::kill($pid, SIGUSR1); usleep(10000); //判断进程是否存在 Assert::assert(intval(shell_exec("ps aux | grep \"" . PROC_NAME . "\" |grep -v grep| awk '{ print $2}'")) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { cli_set_process_title(PROC_NAME); Co::set(['log_level' => SWOOLE_LOG_ERROR]); $pool = new Swoole\Process\Pool(2); $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pm) { $pm->wakeup(); Swoole\Timer::tick(1000, function () use ($workerId) { echo "sleep [$workerId] \n"; }); Swoole\Process::signal(SIGTERM, function () { Swoole\Event::exit(); }); Swoole\Event::wait(); }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/reuse_port.phpt ================================================ --TEST-- swoole_process_pool: co\socket reuse port --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR [1]\n"; return; } if (!$cli->send("hello\n")) { echo "ERROR [2]\n"; return; } $ret = $cli->recv(); if (!$ret) { echo "ERROR [3]\n"; return; } $result = unserialize($ret); if (!$result) { echo "ERROR [4]\n"; return; } $pids[$result['wid']] = 1; }); $sch->start(); Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $atomic = new Atomic(); $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { $socket = new Socket(AF_INET, SOCK_STREAM, 0); $socket->setOption(SOL_SOCKET, SO_REUSEPORT, true); $socket->bind('127.0.0.1', $pm->getFreePort()); $socket->listen(128); if ($atomic->add() == 2) { $pm->wakeup(); } Process::signal(SIGTERM, function () use ($socket) { $socket->cancel(); }); while (true) { $client = $socket->accept(); if (!$client) { if ($socket->errCode == SOCKET_ECANCELED) { break; } continue; } co::sleep(0.005); $data = $client->recv(); if (empty($data)) { $client->close(); break; } $client->send(serialize(['wid' => $id])); } }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_process_pool/shutdown.phpt ================================================ --TEST-- swoole_process_pool: shutdown --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { $pool->shutdown(); sleep(20); echo "ERROR\n"; }); $pool->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_process_pool/socket_coro.phpt ================================================ --TEST-- swoole_process_pool: co\socket --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $s = microtime(true); $sch = new Scheduler(); $sch->parallel(2, function () use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR\n"; return; } if (!$cli->send("hello\n")) { return; } $ret = $cli->recv(); if (!$ret) { return; } echo $ret; }); $sch->start(); echo "DONE\n"; Assert::lessThan(microtime(true) - $s, 0.25); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $socket = new Socket(AF_INET, SOCK_STREAM, 0); $socket->bind('127.0.0.1', $pm->getFreePort()); $atomic = new Atomic(); $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($socket, $pm, $atomic) { $socket->listen(128); if ($atomic->add() == 2) { $pm->wakeup(); } Process::signal(SIGTERM, function () use ($socket) { $socket->cancel(); }); while (true) { $client = $socket->accept(); if (!$client) { if ($socket->errCode == SOCKET_ECANCELED) { break; } continue; } usleep(100000); $data = $client->recv(); if (empty($data)) { $client->close(); break; } $client->send("Server[{$id}]: {$data}"); } echo "worker stop\n"; }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Server[%d]: hello Server[%d]: hello DONE worker stop worker stop ================================================ FILE: tests/swoole_process_pool/start_pool_twice_in_same_process.phpt ================================================ --TEST-- swoole_process_pool: start pool twice in the same process --SKIPIF-- --FILE-- on("WorkerStart", function (Pool $pool, $workerId) { $pool->shutdown(); }); $pool->start(); $pool = new Swoole\Process\Pool(1); $pool->on("WorkerStart", function (Pool $pool, $workerId) { $pool->shutdown(); }); $pool->start(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_process_pool/start_twice.phpt ================================================ --TEST-- swoole_process_pool: start twice --SKIPIF-- --FILE-- on("WorkerStart", function (Pool $pool, $workerId) { echo "CHILD START\n"; $pool->shutdown(); sleep(1); }); $pool->start(); echo "START 1\n"; $pool->start(); echo "START 2\n"; ?> --EXPECT-- CHILD START START 1 CHILD START START 2 ================================================ FILE: tests/swoole_process_pool/worker_exit_1.phpt ================================================ --TEST-- swoole_process_pool: worker exit --SKIPIF-- --FILE-- on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { echo "worker start\n"; Assert::eq($pool->workerId, $workerId); $count = 0; while ($GLOBALS['running']) { Co::sleep(0.03); echo "sleep\n"; if (++$count === 3) { $pool->shutdown(); } } }); $pool->on('workerStop', function ($pool, $data) { echo "worker stop\n"; }); $pool->on('workerExit', function ($pool, $data) { $GLOBALS['count']++; if ($GLOBALS['count'] == 3) { $GLOBALS['running'] = false; } echo ('worker exit') . PHP_EOL; }); $pool->start(); ?> --EXPECT-- worker start sleep sleep sleep worker exit sleep worker exit sleep worker exit worker stop ================================================ FILE: tests/swoole_redis_server/big_packet.phpt ================================================ --TEST-- swoole_redis_server: test big packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $redis = new redis; $redis->connect('127.0.0.1', $pm->getFreePort()); $redis->set('big_value', str_repeat('A', VALUE_LEN)); $ret = $redis->get('big_value'); Assert::same(strlen($ret ?? '' ?: ''), VALUE_LEN); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->data = array(); $server->setHandler('GET', function ($fd, $data) use ($server) { if (count($data) == 0) { return Server::format(Server::ERROR, "ERR wrong number of arguments for 'GET' command"); } $key = $data[0]; if (empty($server->data[$key])) { $server->send($fd, Server::format(Server::NIL)); } else { $server->send($fd, Server::format(Server::STRING, $server->data[$key])); } }); $server->setHandler('SET', function ($fd, $data) use ($server) { if (count($data) < 2) { return Server::format(Server::ERROR, "ERR wrong number of arguments for 'SET' command"); } $key = $data[0]; $server->data[$key] = $data[1]; $server->send($fd, Server::format(Server::STATUS, 'OK')); }); $server->on('WorkerStart', function ($server) use ($pm) { $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_redis_server/empty.phpt ================================================ --TEST-- swoole_redis_server: empty --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); $map = $redis->get('map'); Assert::eq($map, [0, '', false, null]); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->setHandler('GET', function ($fd, $data) use ($server) { $server->send($fd, Server::format(Server::SET, [0, '', false, null])); }); $server->on('WorkerStart', function ($server) use ($pm) { $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_redis_server/format.phpt ================================================ --TEST-- swoole_redis_server: format --SKIPIF-- --FILE-- '99', "java" => '88', "c++" => '666', 9999 => 'hello']); ?> --EXPECT-- -ERR -BAD REQUEST $-1 +OK +SUCCESS :1000 $12 hello swoole *3 $3 php $2 is $4 best *8 $3 php $2 99 $4 java $2 88 $3 c++ $3 666 $4 9999 $5 hello ================================================ FILE: tests/swoole_redis_server/getHandler.phpt ================================================ --TEST-- swoole_redis_server: getHandler --SKIPIF-- --FILE-- setHandler('GET', function ($fd, $data) use ($server) { $server->send($fd, Server::format(Server::STRING, "hello")); }); $callback = $server->getHandler('GET'); Assert::assert(is_callable($callback)); ?> --EXPECT-- ================================================ FILE: tests/swoole_redis_server/large_data_map.phpt ================================================ --TEST-- swoole_redis_server: large data map --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); $map = $redis->get('map'); Assert::notEmpty($map); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->setHandler('GET', function ($fd, $data) use ($server) { $key = $data[0]; if ($key == 'map') { $out = Server::format(Server::MAP, ['a' => str_repeat('a', N), 'b' => str_repeat('b', N * 3)]); } $server->send($fd, $out); }); $server->on('WorkerStart', function ($server) use ($pm) { $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_redis_server/nested_map.phpt ================================================ --TEST-- swoole_redis_server: nested map --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); $map = $redis->get('map'); Assert::notEmpty($map); Assert::eq($map[0], 'uuid'); Assert::eq($map[1], UUID); Assert::isArray($map[3]); Assert::eq($map[5], NUMBER); Assert::eq($map[3][1], NUMBER); Assert::eq($map[3][2], UUID); $set = $redis->get('set'); Assert::notEmpty($set); Assert::eq($set[0], UUID); Assert::isArray($set[1]); Assert::eq($set[2], NUMBER); Assert::eq($set[1][1], NUMBER); Assert::eq($set[1][3], UUID); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->setHandler('GET', function ($fd, $data) use ($server) { $key = $data[0]; if ($key == 'map') { $out = Server::format(Server::MAP, [ 'uuid' => UUID, 'list' => [1, NUMBER, UUID], 'number' => NUMBER, ]); } elseif ($key == 'set') { $out = Server::format(Server::SET, [ UUID, ['number' => NUMBER, 'uuid' => UUID], NUMBER, ]); } else { $out = Server::format(Server::ERROR, 'bad key'); } $server->send($fd, $out); }); $server->on('WorkerStart', function ($server) use ($pm) { $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/accept.phpt ================================================ --TEST-- swoole_runtime: accept --SKIPIF-- --FILE-- array( 'so_reuseaddr' => true, ), ); $ctx = stream_context_create($opts); $socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx); if (!$socket) { echo "$errstr ($errno)
\n"; } else { $conn = stream_socket_accept($socket); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); fclose($conn); fclose($socket); } }); go(function () { $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { $data = fread($fp, 8192); fclose($fp); Assert::assert(strpos($data,'local time') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/accept_timeout.phpt ================================================ --TEST-- swoole_runtime: accept timeout --SKIPIF-- --FILE-- array( 'so_reuseaddr' => true, ), ); $ctx = stream_context_create($opts); $socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx); if (!$socket) { echo "$errstr ($errno)
\n"; } else { $conn = @stream_socket_accept($socket, 1); Assert::false($conn); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/async_protocol.phpt ================================================ --TEST-- swoole_runtime: async protocol --SKIPIF-- --FILE-- bind(__DIR__ . '/test.sock'); for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); echo "[Server] recv : $data\n"; } }); go(function () { $fp = stream_socket_client("async.udg://".__DIR__."/test.sock", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { for ($i = 0; $i < N; $i++) { fwrite($fp, "hello-{$i}"); } fclose($fp); } }); Swoole\Event::wait(); ?> --EXPECT-- [Server] recv : hello-0 [Server] recv : hello-1 [Server] recv : hello-2 [Server] recv : hello-3 [Server] recv : hello-4 ================================================ FILE: tests/swoole_runtime/base.phpt ================================================ --TEST-- swoole_runtime: base --SKIPIF-- --FILE-- run(); Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_SLEEP); go(function () { usleep(1000); echo '1' . PHP_EOL; }); echo '2' . PHP_EOL; go(function () use ($server) { $cli = stream_socket_client("tcp://127.0.0.1:{$server->getPort()}", $errno, $errstr, 1); $read = $write = [$cli]; $n = stream_select($read, $write, $except, 1); echo 'select' . PHP_EOL; Assert::same($n, 1); Assert::count($read, 1); Assert::count($write, 1); fread($cli, 8192); $n = stream_select($read, $write, $except, 1); Assert::same($n, 1); Assert::count($read, 0); Assert::count($write, 1); }); echo '3' . PHP_EOL; Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_FILE ^ SWOOLE_HOOK_STREAM_FUNCTION); go(function () { $read = [fopen(__FILE__, 'r')]; $n = stream_select($read, $write, $except, 1); Assert::same($n, 1); Assert::count($read, 1); echo '4' . PHP_EOL; }); go(function () use ($server) { usleep(10 * 1000); echo 'sleep2' . PHP_EOL; $server->shutdown(); }); echo '5' . PHP_EOL; Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL); // all go(function () { usleep(5 * 1000); echo 'sleep1' . PHP_EOL; }); echo '6' . PHP_EOL; go(function () use ($server) { $read = [stream_socket_client("tcp://127.0.0.1:{$server->getPort()}", $errno, $errstr, 1)]; $n = stream_select($read, $write, $except, 1); Assert::same($n, 1); Assert::count($read, 1); echo 'select' . PHP_EOL; }); echo '7' . PHP_EOL; Swoole\Event::wait(); Swoole\Runtime::enableCoroutine(0); // disable all ?> --EXPECT-- 1 2 3 4 5 6 7 select select sleep1 sleep2 ================================================ FILE: tests/swoole_runtime/bindto.phpt ================================================ --TEST-- swoole_runtime: socket context bindto --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(); run(function () use($pm) { $context = stream_context_create([ 'socket' => [ 'bindto' => '0:9100', ], ]); file_get_contents("http://127.0.0.1:{$pm->getFreePort()}", false, $context); $pm->kill(); echo "Done\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { Assert::eq($request->server['remote_addr'], '127.0.0.1'); Assert::eq($request->server['remote_port'], 9100); $response->end('success'); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/bindto2.phpt ================================================ --TEST-- swoole_runtime: socket context bindto --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Runtime::enableCoroutine(); run(function () use($pm) { $context = stream_context_create([ 'socket' => [ 'bindto' => ':9100', ], ]); file_get_contents("http://127.0.0.1:{$pm->getFreePort()}", false, $context); $pm->kill(); echo "Done\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { Assert::eq($request->server['remote_addr'], '127.0.0.1'); Assert::eq($request->server['remote_port'], 9100); $response->end('success'); }); $pm->wakeup(); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/block.phpt ================================================ --TEST-- swoole_runtime: pdo create outside coroutine --SKIPIF-- --FILE-- exec("SELECT sleep(0.1)"); Assert::same($pdo->errorCode(), PDO::ERR_NONE); }); } Swoole\Event::wait(); Assert::assert((microtime(true) - $start) > 5 * 0.1); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/bug_4657.phpt ================================================ --TEST-- swoole_runtime: bug 4657 --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/bug_5104.phpt ================================================ --TEST-- swoole_runtime: Github#5104 https://github.com/swoole/swoole-src/issues/5104 --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/cancel_sleep.phpt ================================================ --TEST-- swoole_runtime: cancel sleep --SKIPIF-- --FILE-- --EXPECT-- success1 success2 success3 ================================================ FILE: tests/swoole_runtime/destruct.phpt ================================================ --TEST-- swoole_runtime: socket destruct close --SKIPIF-- --FILE-- connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis->rawCommand('CLIENT', 'KILL', 'TYPE', 'normal'); $redis->close(); usleep(100); Swoole\Runtime::enableCoroutine(); $timer_id = Swoole\Timer::tick(1000 / MAX_CONCURRENCY_MID, function () { $redis = new Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); Assert::assert($redis->set('foo', 'bar')); Assert::same($redis->get('foo'), 'bar'); }); go(function () use ($timer_id) { co::sleep(1); Swoole\Timer::clear($timer_id); $redis = new Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $info = (array) $redis->info('clients'); phpt_var_dump($info); Assert::same($info['connected_clients'], 1, var_dump_return($info)); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/enable_crypto.phpt ================================================ --TEST-- swoole_runtime: stream_socket_enable_crypto --SKIPIF-- --FILE-- \n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; stream_socket_enable_crypto($fp,true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); fwrite($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); Assert::assert(strpos($content,'map.baidu.com') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/exec/exec.phpt ================================================ --TEST-- swoole_runtime/unsafe: dns --SKIPIF-- --FILE-- getChildOutput(), 'ping statistics'), 3); Assert::eq($pm->getChildExitStatus(), 0); ?> --EXPECTF-- ================================================ FILE: tests/swoole_runtime/file_hook/a.inc ================================================ --FILE-- = 1); } Swoole\Runtime::enableCoroutine(false); Assert::same(md5($content), md5_file(TEST_IMAGE)); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_hook/b.inc ================================================ --FILE-- --EXPECT-- SUCCESS SUCCESS SUCCESS SUCCESS ================================================ FILE: tests/swoole_runtime/file_hook/bug_ftell_2g.phpt ================================================ --TEST-- swoole_runtime/file_hook: fseek ftell file larger than 2G bug --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_hook/bug_scandir.phpt ================================================ --TEST-- swoole_runtime/file_hook: bug #3792 --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/file_hook/fgets.phpt ================================================ --TEST-- swoole_runtime/file_hook: fgets --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_hook/flock.phpt ================================================ --TEST-- swoole_runtime/file_hook: flock --SKIPIF-- --FILE-- $num]); go(function () use ($num) { for ($i = $num; $i--;) { go('test_flock'); } }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/file_hook/fread.phpt ================================================ --TEST-- swoole_runtime/file_hook: fread --SKIPIF-- --FILE-- --EXPECT-- open read ================================================ FILE: tests/swoole_runtime/file_hook/include.phpt ================================================ --TEST-- swoole_runtime/file_hook: include --SKIPIF-- --FILE-- --EXPECT-- A B ================================================ FILE: tests/swoole_runtime/file_hook/include_2.phpt ================================================ --TEST-- swoole_runtime/file_hook: include --SKIPIF-- --FILE-- --EXPECT-- 0-1 0-2 1-1 1-2 2-1 2-2 3-1 3-2 ================================================ FILE: tests/swoole_runtime/file_hook/lock_ex.phpt ================================================ --TEST-- swoole_runtime/file_hook: file_put_contents with LOCK_EX --SKIPIF-- --FILE-- --EXPECTF-- %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK ================================================ FILE: tests/swoole_runtime/file_hook/lock_nb_1.phpt ================================================ --TEST-- swoole_runtime/file_hook: file_put_contents with LOCK_NB --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/file_hook/lock_nb_2.phpt ================================================ --TEST-- swoole_runtime/file_hook: file_put_contents with LOCK_NB[2] --SKIPIF-- --FILE-- --EXPECTF-- [1] LOCK [2] LOCK yield [1] UNLOCK [2] LOCK resume [2] UNLOCK ================================================ FILE: tests/swoole_runtime/file_hook/lock_sh.phpt ================================================ --TEST-- swoole_runtime/file_hook: file_put_contents with LOCK_SH --SKIPIF-- --FILE-- --EXPECTF-- %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK %d OK ================================================ FILE: tests/swoole_runtime/file_hook/open_basedir.phpt ================================================ --TEST-- swoole_runtime/file_hook: support open_basedir config --SKIPIF-- --FILE-- --EXPECTF-- Warning: mkdir(): open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s) in %s on line 7 bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/file_hook/read.phpt ================================================ --TEST-- swoole_runtime/file_hook: read file --SKIPIF-- --FILE-- SOURCE_ROOT_PATH . '/README.md', 'hash' => '', ], [ 'file' => SOURCE_ROOT_PATH . '/package.xml', 'hash' => '', ], [ 'file' => TEST_IMAGE, 'hash' => '', ], ); foreach ($files as &$f) { $f['hash'] = md5_file($f['file']); } Swoole\Runtime::enableCoroutine(); foreach ($files as $k => $v) { go(function () use ($v, $k) { $content = readfile_co($v['file']); Assert::same(md5($content), $v['hash']); }); } Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_hook/readdir.phpt ================================================ --TEST-- swoole_runtime/file_hook: readdir --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_lock/async_file.phpt ================================================ --TEST-- swoole_runtime/file_lock: async file --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/file_lock/lock_ex.phpt ================================================ --TEST-- swoole_runtime/file_lock: file_lock_1 --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/file_lock/lock_nb.phpt ================================================ --TEST-- swoole_runtime/file_lock: lock_nb --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/file_lock/lock_sh_1.phpt ================================================ --TEST-- swoole_runtime/file_lock: lock_sh_1 --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/ftp_fopen_wrapper.phpt ================================================ --TEST-- swoole_runtime: ftp fopen wrapper --SKIPIF-- --FILE-- --EXPECTF-- DONE ================================================ FILE: tests/swoole_runtime/get_hook_flags.phpt ================================================ --TEST-- swoole_runtime: get hook flags --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/gethostbyname.phpt ================================================ --TEST-- swoole_runtime: sleep --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/hook_default.phpt ================================================ --TEST-- swoole_runtime: enable hook by default --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/hook_enable_coroutine.phpt ================================================ --TEST-- swoole_runtime: enableCoroutine --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/hook_set_flags.phpt ================================================ --TEST-- swoole_runtime: set hook flags --SKIPIF-- --FILE-- 0]); Co\run(function () { Assert::eq(Swoole\Runtime::getHookFlags(), 0); }); Co::set(['hook_flags' => SWOOLE_HOOK_CURL]); Co\run(function () { Assert::eq(Swoole\Runtime::getHookFlags(), SWOOLE_HOOK_CURL); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/library.phpt ================================================ --TEST-- swoole_runtime: library --SKIPIF-- --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_runtime/nonblock.phpt ================================================ --TEST-- swoole_runtime: nonblock --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/out_of_coroutine.phpt ================================================ --TEST-- swoole_runtime: out of coroutine --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/pdo.phpt ================================================ --TEST-- swoole_runtime: pdo --SKIPIF-- --FILE-- exec("SELECT sleep({$time})"); if (Assert::assert($pdo->errorCode() === PDO::ERR_NONE)){ global $count; $count++; } } function onRequest() { mysql_sleep(.1); } $start = microtime(true); for ($i = MAX_CONCURRENCY_LOW; $i--;) { go('onRequest'); } Swoole\Event::wait(); Assert::same($count, MAX_CONCURRENCY_LOW); Assert::assert((microtime(true) - $start) < .5); //关闭协程,否则会致命错误 Swoole\Runtime::enableCoroutine(false); mysql_sleep(.1); //block IO echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/persistent.phpt ================================================ --TEST-- swoole_runtime: socket persistent then destruct --SKIPIF-- --FILE-- connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $redis->rawCommand('CLIENT', 'KILL', 'TYPE', 'normal'); $redis->close(); usleep(100); Swoole\Runtime::enableCoroutine(); $map = []; $timer_id = Swoole\Timer::tick(1000 / MAX_CONCURRENCY_MID, function () use (&$map) { $redis = new Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); Assert::assert($redis->set('foo', 'bar')); Assert::same($redis->get('foo'), 'bar'); $map[] = $redis; }); go(function () use ($timer_id, &$map) { Co::sleep(1); Swoole\Timer::clear($timer_id); $redis = new Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $info = (array)$redis->info('clients'); phpt_var_dump($info); Assert::same($info['connected_clients'], count($map) + 1, var_dump_return($info)); $map = []; // destruct Co::sleep(0.001); // defer close switch_process(); $info = (array)$redis->info('clients'); phpt_var_dump($info); Assert::same($info['connected_clients'], 1, var_dump_return($info)); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/proc/1.phpt ================================================ --TEST-- swoole_runtime/proc: proc_open --SKIPIF-- --FILE-- array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $cat = proc_open( "/bin/cat", $ds, $pipes ); proc_close($cat); echo "I didn't segfault!\n"; }); Swoole\Event::wait(); ?> --EXPECT-- I didn't segfault! ================================================ FILE: tests/swoole_runtime/proc/2.phpt ================================================ --TEST-- swoole_runtime/proc: proc_open --SKIPIF-- --FILE-- --EXPECTF-- bool(true) array(8) { ["command"]=> string(12) "/bin/sleep 2" ["pid"]=> int(%d) ["running"]=> bool(true) ["signaled"]=> bool(false) ["stopped"]=> bool(false) ["exitcode"]=> int(-1) ["termsig"]=> int(0) ["stopsig"]=> int(0) } bool(true) array(8) { ["command"]=> string(12) "/bin/sleep 2" ["pid"]=> int(%d) ["running"]=> bool(false) ["signaled"]=> bool(true) ["stopped"]=> bool(false) ["exitcode"]=> int(-1) ["termsig"]=> int(15) ["stopsig"]=> int(0) } Done! ================================================ FILE: tests/swoole_runtime/proc/3.phpt ================================================ --TEST-- swoole_runtime/proc: proc_open() with > 16 pipes --SKIPIF-- --FILE-- --CLEAN-- --EXPECTF-- int(28) array(28) { [3]=> resource(%d) of type (Unknown) [4]=> resource(%d) of type (Unknown) [5]=> resource(%d) of type (Unknown) [6]=> resource(%d) of type (Unknown) [7]=> resource(%d) of type (Unknown) [8]=> resource(%d) of type (Unknown) [9]=> resource(%d) of type (Unknown) [10]=> resource(%d) of type (Unknown) [11]=> resource(%d) of type (Unknown) [12]=> resource(%d) of type (Unknown) [13]=> resource(%d) of type (Unknown) [14]=> resource(%d) of type (Unknown) [15]=> resource(%d) of type (Unknown) [16]=> resource(%d) of type (Unknown) [17]=> resource(%d) of type (Unknown) [18]=> resource(%d) of type (Unknown) [19]=> resource(%d) of type (Unknown) [20]=> resource(%d) of type (Unknown) [21]=> resource(%d) of type (Unknown) [22]=> resource(%d) of type (Unknown) [23]=> resource(%d) of type (Unknown) [24]=> resource(%d) of type (Unknown) [25]=> resource(%d) of type (Unknown) [26]=> resource(%d) of type (Unknown) [27]=> resource(%d) of type (Unknown) [28]=> resource(%d) of type (Unknown) [29]=> resource(%d) of type (Unknown) [30]=> resource(%d) of type (Unknown) } ================================================ FILE: tests/swoole_runtime/proc/4.phpt ================================================ --TEST-- swoole_runtime/proc: proc_open() with no pipes --SKIPIF-- --FILE-- --CLEAN-- --EXPECTF-- int(0) array(0) { } ================================================ FILE: tests/swoole_runtime/proc/5.phpt ================================================ --TEST-- swoole_runtime/proc: proc_open() with invalid pipes --SKIPIF-- --FILE-- --CLEAN-- --EXPECTF-- Warning: proc_open(): pi is not a valid descriptor spec/mode in %s on line %d Warning: proc_open(): Descriptor item must be either an array or a File-Handle in %s on line %d array(4) { [3]=> resource(%d) of type (Unknown) [4]=> resource(%d) of type (Unknown) [5]=> resource(%d) of type (Unknown) [6]=> resource(%d) of type (Unknown) } Warning: proc_open(test): %s to open stream: %s in %s on line %d array(4) { [3]=> resource(%d) of type (Unknown) [4]=> resource(%d) of type (Unknown) [5]=> resource(%d) of type (Unknown) [6]=> resource(%d) of type (Unknown) } END ================================================ FILE: tests/swoole_runtime/proc/close_after_wait.phpt ================================================ --TEST-- swoole_runtime/proc: proc_close() after wait --SKIPIF-- --FILE-- array("pipe", "/dev/null"), 1 => array("pipe", "/dev/null"), 2 => array("pipe", "/dev/null") ); $proc = proc_open('/bin/sleep 30', $descriptorspec, $pipes); Assert::notEmpty($proc); echo "wait begin\n"; go(function() use($proc) { usleep(100000); proc_terminate($proc); }); $info = Co\System::wait(); Assert::notEmpty($info); echo "wait end\n"; proc_close($proc); echo "proc_close end\n"; }); ?> --EXPECTF-- wait begin wait end proc_close end ================================================ FILE: tests/swoole_runtime/proc/proc_open_pipes.inc ================================================ --FILE-- connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); $redis->get("key"); }); go(function () { $redis = new Redis; Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); $redis->get("key"); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/redis_pconnect.phpt ================================================ --TEST-- swoole_runtime: hook stream redis pconnect --SKIPIF-- --FILE-- pconnect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); $redis->get("key"); }); go(function () { $redis = new Redis; Assert::assert($redis->pconnect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); $redis->get("key"); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/remote_object/dns.phpt ================================================ --TEST-- swoole_runtime/remote_object: dns --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/sento.phpt ================================================ --TEST-- swoole_runtime: stream_socket_sendto --SKIPIF-- --FILE-- \n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; stream_socket_sendto($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); Assert::assert(strpos($content,'map.baidu.com') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/set_hook_flags.phpt ================================================ --TEST-- swoole_runtime: set hook flags --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/sleep.phpt ================================================ --TEST-- swoole_runtime: sleep --SKIPIF-- --FILE-- getMessage(), 'must be greater than or equal to 0'); } // usleep $s = microtime(true); $t = ms_random(0.01, 0.1); usleep($t * 1000 * 1000); time_approximate($t, microtime(true) - $s); usleep(0); try { usleep(-1); } catch (Throwable $e) { Assert::contains($e->getMessage(), 'must be greater than or equal to 0'); } // time_nanosleep try { time_nanosleep(-1, 1); } catch (Throwable $e) { Assert::contains($e->getMessage(), 'must be greater than or equal to 0'); } Assert::true(time_nanosleep(0, 1)); Assert::true(time_nanosleep(0, 1000 * 1000)); // time_sleep_until $s = microtime(true); Assert::true(time_sleep_until($s + 1)); time_approximate(1, microtime(true) - $s); Assert::false(time_sleep_until($s)); }); echo "NON-BLOCKED\n"; Swoole\Event::wait(); echo "\nDONE\n"; ?> --EXPECTF-- NON-BLOCKED Warning: time_sleep_until(): Argument #1 ($timestamp) must be greater than or equal to the current time in %s on line %d DONE ================================================ FILE: tests/swoole_runtime/sleep_yield.phpt ================================================ --TEST-- swoole_runtime: sleep yield --SKIPIF-- --FILE-- --EXPECTF-- string(1) "a" string(1) "b" ================================================ FILE: tests/swoole_runtime/sockets/basic/bug46360.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Bug 46360 - TCP_NODELAY constant (sock_get_option, sock_set_option) --SKIPIF-- --CREDITS-- Florian Anderiasch fa@php.net --FILE-- --EXPECTF-- int(%d) ================================================ FILE: tests/swoole_runtime/sockets/basic/bug49341.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: add SO_REUSEPORT support for socket_set_option() --SKIPIF-- --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/bug63000.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Multicast on OSX --SKIPIF-- --FILE-- '224.0.0.251', "interface" => 0, )); var_dump($so); }); ?> --EXPECT-- bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/bug76839.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: socket_recvfrom may return an invalid 'from' address on MacOS --SKIPIF-- --FILE-- --EXPECT-- Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' Received 'Ping!' Responded to sender with 'Pong!' ================================================ FILE: tests/swoole_runtime/sockets/basic/ipv4loop.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv4 Loopback test --SKIPIF-- --FILE-- --EXPECT-- string(10) "ABCdef123 " ================================================ FILE: tests/swoole_runtime/sockets/basic/ipv6_skipif.inc ================================================ --FILE-- --EXPECT-- string(10) "ABCdef123 " ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_helpers.php.inc ================================================ '224.0.0.23', "interface" => 'lo', )); if ($so === false) { die('skip interface \'lo\' is unavailable.'); } if (!defined("MCAST_BLOCK_SOURCE")) { die('skip source operations are unavailable'); }?> --FILE-- $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $i = 0; checktimeout($s, 500); while (($str = socket_read($s, 3000)) !== FALSE) { $i++; echo "$i> ", $str, "\n"; if ($i == 1) { echo "leaving group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", $port); var_dump($r); } if ($i == 2) { echo "re-joining group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends2, $m = "ignored mcast packet (different interface)", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 3) { echo "blocking source\n"; $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", $port); var_dump($r); } if ($i == 4) { echo "unblocking source\n"; $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 5) { echo "leaving group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", $port); var_dump($r); } if ($i == 6) { echo "joining source group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 7) { echo "leaving source group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", $port); var_dump($r); } if ($i == 8) { /* echo "rjsg\n"; $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so);*/ break; } } }); ?> --EXPECTF-- creating send socket bound to 127.0.0.1 bool(true) creating unbound socket and hoping the routing table causes an interface other than lo to be used for sending messages to 224.0.0.23 bool(true) creating receive socket bool(true) bool(true) int(14) 1> initial packet leaving group bool(true) int(20) int(14) 2> unicast packet re-joining group bool(true) int(42) int(12) 3> mcast packet blocking source bool(true) int(31) int(14) 4> unicast packet unblocking source bool(true) int(27) 5> mcast packet from 127.0.0.1 leaving group bool(true) int(20) int(14) 6> unicast packet joining source group bool(true) int(27) 7> mcast packet from 127.0.0.1 leaving source group bool(true) int(20) int(14) 8> unicast packet ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_ipv4_send.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv4 send options --SKIPIF-- --FILE-- --EXPECT-- Setting IP_MULTICAST_TTL bool(true) int(9) Setting IP_MULTICAST_LOOP bool(true) int(0) bool(true) int(1) Setting IP_MULTICAST_IF interface 0: bool(true) int(0) interface 1: bool(true) int(1) ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_ipv4_send_error.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv4 send options with unusual values --SKIPIF-- --FILE-- getMessage() . \PHP_EOL; } $r = @socket_get_option($s, $level, IP_MULTICAST_TTL); var_dump($r); echo "Setting IP_MULTICAST_TTL with \"254\"\n"; $r = @socket_set_option($s, $level, IP_MULTICAST_TTL, "254"); var_dump($r); $r = @socket_get_option($s, $level, IP_MULTICAST_TTL); var_dump($r); echo "Setting IP_MULTICAST_TTL with -1\n"; //should give error, not be the same as 255 try { $r = @socket_set_option($s, $level, IP_MULTICAST_TTL, -1); var_dump($r); } catch (\ValueError $e) { echo $e->getMessage() . \PHP_EOL; } $r = @socket_get_option($s, $level, IP_MULTICAST_TTL); var_dump($r); }); ?> --EXPECT-- Setting IP_MULTICAST_LOOP with 256 bool(true) int(1) Setting IP_MULTICAST_LOOP with false bool(true) int(0) Setting IP_MULTICAST_TTL with 256 bool(false) int(1) Setting IP_MULTICAST_TTL with "254" bool(true) int(254) Setting IP_MULTICAST_TTL with -1 bool(false) int(254) ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_ipv6_recv.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv6 receive options --SKIPIF-- 'ff01::114', "interface" => 0, )); if ($so === false) { die('skip unable to join multicast group on any interface.'); } $r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', $port); if ($r === false) { die('skip unable to send multicast packet.'); } if (!defined("MCAST_JOIN_SOURCE_GROUP")) die('skip source operations are unavailable'); $so = @socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array( "group" => 'ff01::114', "interface" => 0, )); $so = @socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array( "group" => 'ff01::114', "interface" => 0, "source" => '2001::dead:beef', )); if ($so === false) { die('skip protocol independent multicast API is unavailable.'); }?> --FILE-- $mcastaddr, "interface" => $interface, )) or die("err"); var_dump($so); echo "socket_sendto[1]\n"; $r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); checktimeout($s, 500); echo "socket_recvfrom[1]\n"; $r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort); var_dump($r, $str, $from); $sblock = $from; $r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $i = 0; checktimeout($s, 500); while (($str = socket_read($s, 3000)) !== false) { $i++; echo "$i> ", $str, "\n"; if ($i == 1) { echo "leaving group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", $port); var_dump($r); } if ($i == 2) { echo "re-joining group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 3) { echo "blocking source\n"; $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", $port); var_dump($r); } if ($i == 4) { echo "unblocking source\n"; $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 5) { echo "leaving group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", $port); var_dump($r); } if ($i == 6) { echo "joining source group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet from desired source", strlen($m), 0, $mcastaddr, $port); var_dump($r); } if ($i == 7) { echo "leaving source group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, $port); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", $port); var_dump($r); } if ($i == 8) { /*echo "joining source group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, "source" => $sblock, )); var_dump($so);*/ break; } } }); ?> --EXPECTF-- creating send socket creating receive socket bool(true) bool(true) socket_sendto[1] int(14) socket_recvfrom[1] int(14) string(14) "testing packet" string(%d) "%s" int(14) 1> initial packet leaving group bool(true) int(20) int(14) 2> unicast packet re-joining group bool(true) int(12) 3> mcast packet blocking source bool(true) int(31) int(14) 4> unicast packet unblocking source bool(true) int(12) 5> mcast packet leaving group bool(true) int(20) int(14) 6> unicast packet joining source group bool(true) int(32) 7> mcast packet from desired source leaving source group bool(true) int(20) int(14) 8> unicast packet ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_ipv6_recv_limited.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv6 receive options (limited) --SKIPIF-- 'ff01::114', "interface" => 0, )); if ($so === false) { die('skip unable to join multicast group on any interface.'); } $r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', 3000); if ($r === false) { die('skip unable to send multicast packet.'); } $so = @socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array( "group" => 'ff01::114', "interface" => 0, )); if (defined("MCAST_JOIN_SOURCE_GROUP")) { $so = @socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array( "group" => 'ff01::114', "interface" => 0, "source" => '2001::dead:beef', )); if ($so !== false) { die('skip protocol independent multicast API is available.'); } } ?> --FILE-- $mcastaddr, "interface" => $interface, )) or die("err"); var_dump($so); $r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, 3000); var_dump($r); checktimeout($s, 500); $r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort); var_dump($r, $str, $from); $sblock = $from; $r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000); var_dump($r); $i = 0; checktimeout($s, 500); while (($str = socket_read($s, 3000, 500)) !== false) { $i++; echo "$i> ", $str, "\n"; if ($i == 1) { echo "leaving group\n"; $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); var_dump($r); $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); var_dump($r); } if ($i == 2) { echo "re-joining group\n"; $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( "group" => $mcastaddr, "interface" => $interface, )); var_dump($so); $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000); var_dump($r); } if ($i == 3) { break; } } }); ?> --EXPECTF-- creating send socket creating receive socket bool(true) bool(true) int(14) int(14) string(14) "testing packet" string(%d) "%s" int(14) 1> initial packet leaving group bool(true) int(20) int(14) 2> unicast packet re-joining group bool(true) int(12) 3> mcast packet ================================================ FILE: tests/swoole_runtime/sockets/basic/mcast_ipv6_send.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: IPv6 send options --SKIPIF-- --FILE-- --EXPECT-- Setting IPV6_MULTICAST_TTL bool(true) int(9) Setting IPV6_MULTICAST_LOOP bool(true) int(0) bool(true) int(1) Setting IPV6_MULTICAST_IF interface 0: bool(true) int(0) interface 1: bool(true) int(1) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_accept_failure.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: socket_accept() failure --SKIPIF-- --FILE-- --EXPECTF-- bool(false) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_bind.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_bind - basic test --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECT-- bool(true) bool(true) int(16) NULL ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_clear_error.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: void socket_clear_error ([ resource $socket ] ) ; --CREDITS-- marcosptf - - #phparty7 - @phpsp - novatec/2015 - sao paulo - br --SKIPIF-- --FILE-- --EXPECTF-- int(%d) int(%d) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_connect_params.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_connect - test with empty parameters --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- getMessage() . \PHP_EOL; } try { Assert::false(socket_connect($s_c, '0.0.0.0')); } catch (\ValueError $e) { echo $e->getMessage() . \PHP_EOL; } $s_w = socket_connect($s_c, '0.0.0.0', $port); socket_close($s_c); }); ?> --EXPECTF-- Too few arguments to function swoole_socket_connect(), 1 passed and at least 2 expected Warning: Swoole\Coroutine\Socket::connect(): Invalid port argument[0] in %s on line %d ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_create_listen-nobind.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket_create_listen() returns false, when it cannot bind to the port. --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_create_listen.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket binds on 31338 --SKIPIF-- --FILE-- --EXPECT-- string(7) "0.0.0.0" int(31338) --CREDITS-- Till Klampaeckel, till@php.net PHP Testfest Berlin 2009-05-09 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_create_listen_used.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_create_listen - test for used socket --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_create_pair.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test for socket_create_pair() --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_getopt.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: mixed socket_getopt( resource $socket , int $level , int $optname ) ; --CREDITS-- marcosptf - - #phparty7 - @phpsp - novatec/2015 - sao paulo - br --SKIPIF-- --FILE-- --EXPECT-- Setting IP_MULTICAST_TTL bool(true) int(9) Setting IP_MULTICAST_LOOP bool(true) int(0) bool(true) int(1) Setting IP_MULTICAST_IF interface 0: bool(true) int(0) interface 1: bool(true) int(1) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_getpeername.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_getpeername - basic test --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECTF-- bool(true) bool(false) NULL NULL ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_getpeername_ipv4loop.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_getpeername_ipv4loop - basic test --CREDITS-- Tatjana Andersen tatjana.andersen@redpill-linpro.com # TestFest 2009 - NorwayUG --SKIPIF-- --FILE-- --EXPECT-- string(9) "127.0.0.1" bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_getpeername_ipv6loop.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_getpeername_ipv6loop - basic test --CREDITS-- Tatjana Andersen tatjana.andersen@redpill-linpro.com # TestFest 2009 - NorwayUG --SKIPIF-- --FILE-- --EXPECT-- string(3) "::1" bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_getsockname.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_getsockname - basic test --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECTF-- bool(true) bool(true) string(7) "0.0.0.0" int(%i) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_listen-wrongparams.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test parameter handling in socket_listen(). --SKIPIF-- --FILE-- --EXPECTF-- bool(false) --CREDITS-- Till Klampaeckel, till@php.net Berlin TestFest 2009 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_read_params.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_read- test with incorrect parameter --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_select-wrongparams-2.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test parameter handling in socket_select(). --SKIPIF-- --FILE-- getMessage() . "\n"; } }); ?> --EXPECTF-- socket_select(): At least one array argument must be passed --CREDITS-- Till Klampaeckel, till@php.net Berlin TestFest 2009 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_select.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test parameter handling in socket_select(). --SKIPIF-- --FILE-- --EXPECT-- int(0) --CREDITS-- Till Klampaeckel, till@php.net Berlin TestFest 2009 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_select_error.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: socket_select() error conditions --SKIPIF-- --FILE-- getMessage(), PHP_EOL; } }); ?> --EXPECT-- socket_select(): Argument #1 ($read) must only have elements of type Socket, string given ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_sentto_recvfrom_ipv4_udp.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket_recvfrom() receives data sent by socket_sendto() via IPv4 UDP --SKIPIF-- --FILE-- --EXPECTF-- Received Ping! from remote address %s and remote port %d ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_sentto_recvfrom_ipv6_udp.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP --SKIPIF-- --FILE-- --EXPECTF-- Received Ping! from remote address %s and remote port %d ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_sentto_recvfrom_unix.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket_recvfrom() receives data sent by socket_sendto() through a Unix domain socket --SKIPIF-- --FILE-- --EXPECTF-- bool(false) bool(false) Received Ping! --CREDITS-- Falko Menge PHP Testfest Berlin 2009-05-09 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_block-retval.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test socket_set_block return values --SKIPIF-- --FILE-- getMessage(), "\n"; } }); ?> --EXPECT-- bool(true) bool(false) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_nonblock-retval.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test socket_set_nonblock return values --SKIPIF-- --FILE-- getMessage(), "\n"; } }); ?> --EXPECT-- bool(true) bool(false) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_nonblock.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_set_block - basic test --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_bindtodevice.phpt ================================================ --TEST-- swoole_runtime/sockets/basic:SO_BINDTODEVICE --DESCRIPTION-- -Bind to loopback 'lo' device (should exist) -Bind to unexisting device --SKIPIF-- --FILE-- --EXPECTF-- bool(true) Warning: Swoole\Coroutine\Socket::setOption(): setsockopt(%d) failed, Error: No such device[%d] in %s on line %d bool(false) --CREDITS-- Damjan Cvetko, foreach.org ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_error_socket_option.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test if socket_set_option() returns 'Unable to set socket option' failure for invalid options --SKIPIF-- --FILE-- --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): setsockopt(%d) failed, Error: Permission denied[13] in %s on line %d --CREDITS-- Moritz Neuhaeuser, info@xcompile.net PHP Testfest Berlin 2009-05-10 ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_in6_pktinfo.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: socket_set_option() with IPV6_PKTINFO --SKIPIF-- --FILE-- '::1', "ifindex" => 0 ])); //Oddly, Linux does not support IPV6_PKTINFO in sockgetopt(). //See do_ipv6_getsockopt() on the kernel sources //A work-around with is sort-of possible (with IPV6_2292PKTOPTIONS), //but not worth it //var_dump(socket_get_option($s, IPPROTO_IPV6, IPV6_PKTINFO)); }); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): error converting user data (path: in6_pktinfo): The key 'addr' is required in %s on line %d bool(false) bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_rcvtimeo.phpt ================================================ --TEST-- swoole_runtime/sockets/basic:SO_RCVTIMEO --DESCRIPTION-- -wrong params -set/get params comparison --SKIPIF-- --FILE-- getMessage() . \PHP_EOL; } //set/get comparison $options = array("sec" => 1, "usec" => 0); $retval_2 = socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $options); $retval_3 = socket_get_option($socket, SOL_SOCKET, SO_RCVTIMEO); var_dump($retval_2); var_dump($retval_3 === $options); socket_close($socket); }); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): no key "sec" passed in optval in %s on line %d bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_seolinger.phpt ================================================ --TEST-- swoole_runtime/sockets/basic:SO_SEOLINGER --DESCRIPTION-- -wrong params -set/get params comparison -l_linger not given --SKIPIF-- --FILE-- getMessage() . \PHP_EOL; } // set/get comparison $options = array("l_onoff" => 1, "l_linger" => 1); $retval_2 = socket_set_option($socket, SOL_SOCKET, SO_LINGER, $options); var_dump($retval_2); $retval_3 = socket_get_option($socket, SOL_SOCKET, SO_LINGER); // l_linger not given $options_2 = array("l_onoff" => 1); try { var_dump(socket_set_option($socket, SOL_SOCKET, SO_LINGER, $options_2)); } catch (\ValueError $e) { echo $e->getMessage() . \PHP_EOL; } var_dump($retval_3["l_linger"] === $options["l_linger"]); // value of l_onoff is not always 1, Darwin returns 128 var_dump((bool)$retval_3["l_onoff"] === (bool)$options["l_onoff"]); socket_close($socket); }); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): no key "l_onoff" passed in optval in %s on line %d bool(true) Warning: Swoole\Coroutine\Socket::setOption(): no key "l_linger" passed in optval in %s on line %d bool(false) bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_set_option_sndtimeo.phpt ================================================ --TEST-- swoole_runtime/sockets/basic:SO_SNDTIMEO --DESCRIPTION-- -wrong params -set/get params comparison --SKIPIF-- --FILE-- getMessage() . \PHP_EOL; } //set/get comparison $options = array("sec" => 1, "usec" => 0); $retval_2 = socket_set_option( $socket, SOL_SOCKET, SO_SNDTIMEO, $options); $retval_3 = socket_get_option( $socket, SOL_SOCKET, SO_SNDTIMEO); var_dump($retval_2); var_dump($retval_3 === $options); socket_close($socket); }); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): no key "sec" passed in optval %s on line %d bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_setopt_basic.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Test socket_setopt() basic functionality --CREDITS-- Rodrigo Prado de Jesus User Group: PHPSP #PHPTestFestBrasil --SKIPIF-- --FILE-- 1, "usec" => 0); $retval_1 = socket_setopt( $socket, SOL_SOCKET, SO_SNDTIMEO, $options); $retval_2 = socket_getopt( $socket, SOL_SOCKET, SO_SNDTIMEO); var_dump($retval_1); var_dump($retval_2 === $options); socket_close($socket); }); ?> --EXPECT-- bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_shutdown.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: bool socket_shutdown ( resource $socket [, int $how = 2 ] ) ; --CREDITS-- marcosptf - - #phparty7 - @phpsp - novatec/2015 - sao paulo - br --SKIPIF-- --FILE-- --CLEAN-- --EXPECTF-- bool(true) bool(true) bool(true) bool(false) bool(false) ================================================ FILE: tests/swoole_runtime/sockets/basic/socket_strerror.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: ext/sockets - socket_strerror - basic test --CREDITS-- Florian Anderiasch fa@php.net --SKIPIF-- --FILE-- --EXPECT-- string(23) "Operation not permitted" ================================================ FILE: tests/swoole_runtime/sockets/basic/unixloop.phpt ================================================ --TEST-- swoole_runtime/sockets/basic: Unix domain socket Loopback test --SKIPIF-- --FILE-- --EXPECT-- string(10) "ABCdef123 " ================================================ FILE: tests/swoole_runtime/sockets/error.phpt ================================================ --TEST-- swoole_runtime/sockets: error --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/get_name.phpt ================================================ --TEST-- swoole_runtime/sockets: getsockname & getpeername --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/import.phpt ================================================ --TEST-- swoole_runtime/sockets: import --SKIPIF-- --FILE-- --EXPECT-- string(12) "test message" Done ================================================ FILE: tests/swoole_runtime/sockets/nonblock.phpt ================================================ --TEST-- swoole_runtime/sockets: timeout --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/socketpair.phpt ================================================ --TEST-- swoole_runtime/sockets: tcp server --SKIPIF-- --FILE-- --EXPECT-- hello co-2, #3 hello co-2, #2 hello co-2, #1 hello co-2, #0 hello co-1, #3 hello co-1, #2 hello co-1, #1 hello co-1, #0 Done ================================================ FILE: tests/swoole_runtime/sockets/tcp_client.phpt ================================================ --TEST-- swoole_runtime/sockets: tcp client --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/tcp_server.phpt ================================================ --TEST-- swoole_runtime/sockets: tcp server --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/timeout.phpt ================================================ --TEST-- swoole_runtime/sockets: timeout --SKIPIF-- --FILE-- 0, "usec"=> 50000, ); socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, $timeout); Assert::eq(socket_get_option($sock, SOL_SOCKET, SO_RCVTIMEO), $timeout); Assert::eq(socket_recv($sock, $buf, 1024, 0), false); Assert::eq(socket_last_error($sock), SOCKET_ETIMEDOUT); $n = socket_recv($sock, $buf, 1024, 0); Assert::greaterThanEq($n, 10); Assert::eq(strlen($buf), $n); Assert::eq($buf, 'Swoole: hello world'); socket_close($sock); }); }); echo "Done\n"; ?> --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/sockets/udp.phpt ================================================ --TEST-- swoole_runtime/sockets: udp --SKIPIF-- --FILE-- --EXPECT-- Done ================================================ FILE: tests/swoole_runtime/ssl/capture_peer_cert.phpt ================================================ --TEST-- swoole_runtime/ssl: capture_peer_cert --SKIPIF-- --FILE-- [ "capture_peer_cert" => true, 'capture_peer_cert_chain' => true, 'verify_peer' => false ] ]); $r = stream_socket_client("ssl://{$domain}:443", $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $g); if (!$r) { return false; } $cont = stream_context_get_params($r); if (!$cont) { return false; } return $cont; } Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); Co\run(function () { $result = capture_peer_cert('www.baidu.com'); $info1 = openssl_x509_parse($result["options"]["ssl"]["peer_certificate"]); Assert::isArray($info1); Assert::contains($info1['name'], 'baidu.com'); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/ssl/client.phpt ================================================ --TEST-- swoole_runtime/ssl: ssl client --SKIPIF-- --FILE-- \n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); Assert::assert(strpos($content,'map.baidu.com') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/ssl/enable_crypto.phpt ================================================ --TEST-- swoole_runtime/ssl: stream_socket_enable_crypto --SKIPIF-- --FILE-- \n"; } else { $ready->push(true); $conn = stream_socket_accept($socket); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); fclose($conn); fclose($socket); echo "OK\n"; } }); go(function () use ($ready) { $ready->pop(); $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { stream_context_set_option($fp, ["ssl" => [ "local_cert" => SSL_FILE_DIR . '/client.crt', "local_pk" => SSL_FILE_DIR . '/client.key', ]]); // Enable SSL encryption after the connection is established Assert::assert(stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)); $data = fread($fp, 8192); fclose($fp); Assert::assert(strpos($data, 'local time') !== false); echo "OK\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- OK OK ================================================ FILE: tests/swoole_runtime/ssl/local_cert.phpt ================================================ --TEST-- swoole_runtime/ssl: client with local_cert/local_pk --SKIPIF-- --FILE-- \n"; } else { $ready->push(true); $conn = stream_socket_accept($socket); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); fclose($conn); fclose($socket); echo "OK\n"; } }); go(function () use ($ready) { $ready->pop(); $context = stream_context_create(); stream_context_set_option($context, 'ssl', 'local_cert', SSL_FILE_DIR . '/client.crt'); stream_context_set_option($context, 'ssl', 'local_pk', SSL_FILE_DIR . '/client.key'); $fp = stream_socket_client("ssl://127.0.0.1:8000", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context); if (!$fp) { echo "$errstr ($errno)
\n"; } else { $data = fread($fp, 8192); fclose($fp); Assert::assert(strpos($data, 'local time') !== false); echo "OK\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- OK OK ================================================ FILE: tests/swoole_runtime/ssl/server.phpt ================================================ --TEST-- swoole_runtime/ssl: ssl server --SKIPIF-- --FILE-- \n"; } else { $ready->push(true); $conn = stream_socket_accept($socket); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); fclose($conn); fclose($socket); echo "OK\n"; } }); go(function () use ($ready) { $ready->pop(); $fp = stream_socket_client("ssl://127.0.0.1:8000", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { $data = fread($fp, 8192); fclose($fp); Assert::assert(strpos($data,'local time') !== false); echo "OK\n"; } }); Swoole\Event::wait(); ?> --EXPECT-- OK OK ================================================ FILE: tests/swoole_runtime/ssl/without_key.phpt ================================================ --TEST-- swoole_runtime/ssl: client without local_pk --SKIPIF-- --FILE-- \n"; } else { $ready->push(true); $conn = stream_socket_accept($socket); fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); fclose($conn); fclose($socket); echo "OK\n"; } }); go(function () use ($ready) { $ready->pop(); $context = stream_context_create(); stream_context_set_option($context, 'ssl', 'local_cert', SSL_FILE_DIR . '/client.crt'); $fp = stream_socket_client("ssl://127.0.0.1:8000", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context); if (!$fp) { echo "$errstr ($errno)
\n"; } else { $data = fread($fp, 8192); fclose($fp); Assert::assert(strpos($data, 'local time') !== false); echo "OK\n"; } }); Swoole\Event::wait(); ?> --EXPECTF-- Warning: stream_socket_client(): ssl require key file in %s on line %d OK OK ================================================ FILE: tests/swoole_runtime/stdin.phpt ================================================ --TEST-- swoole_runtime: stdin --SKIPIF-- --FILE-- -1]); $proc = new Process(function ($p) { Co\run(function () use ($p) { $p->write('start' . PHP_EOL); go(function () { co::sleep(0.05); echo "sleep\n"; }); echo fread(STDIN, 1024); }); }, true, SOCK_STREAM); $proc->start(); echo $proc->read(); usleep(100000); $proc->write('hello world' . PHP_EOL); echo $proc->read(); echo $proc->read(); Process::wait(); ?> --EXPECT-- start sleep hello world ================================================ FILE: tests/swoole_runtime/stream_context.phpt ================================================ --TEST-- swoole_runtime: stream context --SKIPIF-- --FILE-- array( 'bindto' => '0:7000', ), ); $ctx = stream_context_create($opts); $fp = stream_socket_client("tcp://www.baidu.com:80", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); if (!$fp) { echo "$errstr ($errno)
\n"; } else { fwrite($fp, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nAccept: */*\r\n\r\n"); $content = ''; stream_set_timeout($fp, 5, 30000); while (!feof($fp)) { $content .= fread($fp, 8192); } fclose($fp); Assert::assert(strpos($content,'map.baidu.com') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/stream_context_pass_null.phpt ================================================ --TEST-- swoole_runtime: stream context pass null --SKIPIF-- --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_runtime/stream_copy_to_stream_socket.phpt ================================================ --TEST-- swoole_runtime: stream_copy_to_stream() with socket as $source --SKIPIF-- --FILE-- \n"; } else { $local = stream_socket_accept($socket); $remote = stream_socket_client('tcp://' . TEST_DOMAIN_3 . ':80', $errno, $errstr, 30, STREAM_CLIENT_CONNECT); go(function () use ($local, $remote) { stream_copy_to_stream($local, $remote); }); stream_copy_to_stream($remote, $local); fclose($local); fclose($remote); fclose($socket); } }); $fp = stream_socket_client($uri, $errno, $errstr, 30); if (!$fp) { echo "{$errstr} ({$errno})
\n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: " . TEST_DOMAIN_3 . "\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); Assert::assert(strpos($content, 'Location: https://www.gov.cn/') !== false); } echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/stream_get_meta_data.phpt ================================================ --TEST-- swoole_runtime: stream_get_meta_data --SKIPIF-- --FILE-- \n"; } else { //200ms stream_set_timeout($fp, 0, 200000); $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); $content = fread($fp, 1024); Assert::isEmpty($content); $res = stream_get_meta_data($fp); Assert::false($res['eof']); Assert::true($res['blocked']); Assert::true($res['timed_out']); fclose($fp); } } $pm = new ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $port = $pm->getFreePort(); test($port); Runtime::setHookFlags(SWOOLE_HOOK_ALL); Co\run(function () use ($port) { test($port); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) { //donot send any }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/stream_select/base.phpt ================================================ --TEST-- swoole_runtime/stream_select: base --SKIPIF-- --FILE-- \n"; } else { fwrite($fp1, "GET / HTTP/1.0\r\nHost: " . TEST_DOMAIN_1 . "\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, 10); Assert::same($n, 1); Assert::same(count($r_array), 1); Assert::same($r_array[0], $fp1); $html = ''; while (!feof($fp1)) { $html .= fgets($fp1, 1024); } Assert::assert(strlen($html) > 1024); fclose($fp1); } }); Swoole\Runtime::enableCoroutine(0); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/stream_select/blocked.phpt ================================================ --TEST-- swoole_runtime/stream_select: blocked --SKIPIF-- --FILE-- \n"; } else { fwrite($fp1, "GET / HTTP/1.0\r\nHost: " . TEST_DOMAIN_1 . "\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, 10); Assert::assert($n == 1); Assert::assert(count($r_array) == 1); Assert::assert($r_array[0] == $fp1); $html = ''; while (!feof($fp1)) { $html .= fgets($fp1, 1024); } Assert::assert(strlen($html) > 1024); fclose($fp1); } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/stream_select/bug46024.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #46024 stream_select() doesn't return the correct number --SKIPIF-- --FILE-- ['pipe', 'r'], 1 => ['pipe', 'w']] , $pipes, __DIR__, [], [] ); var_dump($proc); if (!$proc) { exit(1); } $r = [$pipes[1]]; $w = [$pipes[0]]; $e = null; $ret = stream_select($r, $w, $e, 1); var_dump($ret === (count($r) + count($w))); fread($pipes[1], 1); $r = [$pipes[1]]; $w = [$pipes[0]]; $e = null; $ret = stream_select($r, $w, $e, 1); var_dump($ret === (count($r) + count($w))); foreach ($pipes as $pipe) { fclose($pipe); } proc_terminate($proc); if (defined('SIGKILL')) { proc_terminate($proc, SIGKILL); } else { proc_terminate($proc); } proc_close($proc); }); Swoole\Event::wait(); ?> --EXPECTF-- resource(%d) of type (process/coroutine) bool(true) bool(true) ================================================ FILE: tests/swoole_runtime/stream_select/bug60120.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #60120 proc_open hangs with stdin/out with 2048+ bytes --SKIPIF-- --FILE-- true, 'bypass_shell' => false]); $process = proc_open($cmd, $descriptors, $pipes, getcwd(), [], $options); foreach ($pipes as $pipe) { stream_set_blocking($pipe, false); } $writePipes = [$pipes[0]]; $stdinLen = strlen($stdin); $stdinOffset = 0; unset($pipes[0]); while ($pipes || $writePipes) { $r = $pipes; $w = $writePipes; $e = null; $n = stream_select($r, $w, $e, 60); if (false === $n) { break; } elseif ($n === 0) { proc_terminate($process); } if ($w) { $written = fwrite($writePipes[0], substr($stdin, $stdinOffset), 8192); if (false !== $written) { $stdinOffset += $written; } if ($stdinOffset >= $stdinLen) { fclose($writePipes[0]); $writePipes = null; } } foreach ($r as $pipe) { $type = array_search($pipe, $pipes); $data = fread($pipe, 8192); var_dump($data); if (false === $data || feof($pipe)) { fclose($pipe); unset($pipes[$type]); } } } }); Swoole\Event::wait(); ?> --EXPECTF-- string(2049) "%s" string(2049) "%s" string(0) "" string(0) "" ================================================ FILE: tests/swoole_runtime/stream_select/bug60602.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #60602 proc_open() modifies environment if it contains arrays --SKIPIF-- --FILE-- ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // strerr ]; $environment = ['test' => [1, 2, 3]]; $cmd = (substr(PHP_OS, 0, 3) == 'WIN') ? 'dir' : 'ls'; $p = proc_open($cmd, $descs, $pipes, '.', $environment); if (is_resource($p)) { $data = ''; while (1) { $w = $e = null; $n = stream_select($pipes, $w, $e, 300); if ($n === false) { echo "no streams \n"; break; } else { if ($n === 0) { echo "process timed out\n"; proc_terminate($p, 9); break; } else { if ($n > 0) { $line = fread($pipes[1], 8192); if (strlen($line) == 0) { /* EOF */ break; } $data .= $line; } } } } var_dump(strlen($data)); $ret = proc_close($p); var_dump($ret); var_dump(is_array($environment['test'])); } else { echo "no process\n"; } }); Swoole\Event::wait(); ?> --EXPECTF-- %s: Array to string conversion in %s on line %d int(%d) int(0) bool(true) ================================================ FILE: tests/swoole_runtime/stream_select/bug64438.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #64438 proc_open hangs with stdin/out with 4097+ bytes --SKIPIF-- --FILE-- true, 'bypass_shell' => false]); $process = proc_open($cmd, $descriptors, $pipes, getcwd(), [], $options); foreach ($pipes as $pipe) { stream_set_blocking($pipe, false); } $writePipes = [$pipes[0]]; $stdinLen = strlen($stdin); $stdinOffset = 0; unset($pipes[0]); while ($pipes || $writePipes) { $r = $pipes; $w = $writePipes; $e = null; $n = stream_select($r, $w, $e, 60); if ($n === false) { break; } if ($n === 0) { proc_terminate($process); } if ($w) { $written = fwrite($writePipes[0], substr($stdin, $stdinOffset), 8192); if ($written !== false) { $stdinOffset += $written; } if ($stdinOffset >= $stdinLen) { fclose($writePipes[0]); $writePipes = null; } } foreach ($r as $pipe) { $type = array_search($pipe, $pipes); $data = fread($pipe, 8192); var_dump($data); if ($data === false || feof($pipe)) { fclose($pipe); unset($pipes[$type]); } } } }); Event::wait(); ?> --EXPECTF-- string(4097) "%s" string(4097) "%s" string(0) "" string(0) "" ================================================ FILE: tests/swoole_runtime/stream_select/bug64770.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #64770 stream_select() fails with pipes from proc_open() --SKIPIF-- --FILE-- ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout 2 => ['pipe', 'w'], // strerr ]; $other_opts = ['suppress_errors' => false]; $cmd = (substr(PHP_OS, 0, 3) == 'WIN') ? 'dir' : 'ls'; $p = proc_open($cmd, $descs, $pipes, '.', null, $other_opts); if (is_resource($p)) { $data = ''; while (1) { $w = $e = null; $n = stream_select($pipes, $w, $e, 300); if ($n === false) { echo "no streams \n"; break; } else { if ($n === 0) { echo "process timed out\n"; proc_terminate($p, 9); break; } else { if ($n > 0) { $line = fread($pipes[1], 8192); if (strlen($line) == 0) { /* EOF */ break; } $data .= $line; } } } } var_dump(strlen($data)); $ret = proc_close($p); var_dump($ret); } else { echo "no process\n"; } }); Swoole\Event::wait(); ?> --EXPECTF-- int(%d) int(0) ================================================ FILE: tests/swoole_runtime/stream_select/bug69521.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #69521 Segfault in gc_collect_cycles() --SKIPIF-- --FILE-- --EXPECT-- Sending bug 69521 ================================================ FILE: tests/swoole_runtime/stream_select/bug72075.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #72075 (Referencing socket resources breaks stream_select) --SKIPIF-- --FILE-- --EXPECT-- 0 ================================================ FILE: tests/swoole_runtime/stream_select/conflict.phpt ================================================ --TEST-- swoole_runtime/stream_select: conflict --SKIPIF-- --FILE-- true]); $n = new Atomic(1); go(function () use ($n) { $server = stream_socket_server('tcp://0.0.0.0:8000', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); while ($n->get()) { $conn = @stream_socket_accept($server, 0.1); if ($conn) { go(function () use ($conn) { Assert::eq(fread($conn, 8192), ''); fclose($conn); echo "Server done\n"; }); break; } } }); go(function () use ($n) { $fp1 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); go(function () use ($fp1) { Co::sleep(0.01); $read = null; $write = [$fp1]; $except = null; $rs = stream_select($read, $write, $except, 5); Assert::eq($rs, 0); stream_socket_shutdown($fp1, STREAM_SHUT_RDWR); echo "shutdown\n"; }); $rs = fread($fp1, 8192); Assert::eq($rs, ''); echo "Client done\n"; }); Event::wait(); ?> --EXPECTF-- [%s] WARNING ReactorEpoll::add(): [Reactor#0] epoll_ctl(epfd=%d, EPOLL_CTL_ADD, fd=%d, fd_type=%d, events=1024) failed, Error: File exists[17] shutdown Server done Client done ================================================ FILE: tests/swoole_runtime/stream_select/never_timeout.phpt ================================================ --TEST-- swoole_runtime/stream_select: never timeout --SKIPIF-- --FILE-- run(); $server2 = SwooleTest\CoServer::createHttpHelloWorld(); $server2->run(); $fp1 = stream_socket_client("tcp://127.0.0.1:{$server1->getPort()}", $errno, $errstr, 1); $fp2 = stream_socket_client("tcp://127.0.0.1:{$server2->getPort()}", $errno, $errstr, 1); if (Assert::resource($fp1)) { fwrite($fp1, "GET / HTTP/1.0\r\nHost: 127.0.0.1\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, null); Assert::assert($n == 1); Assert::assert(count($r_array) == 1); Assert::assert($r_array[0] == $fp1); $response = ''; while (!feof($fp1)) { $response .= fgets($fp1); } Assert::contains($response, '200 OK'); fclose($fp1); } $server1->shutdown(); $server2->shutdown(); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/stream_select/preserve_keys.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug #53427 + emulate_read (stream_select does not preserve keys) --SKIPIF-- --FILE-- --EXPECTF-- DONE ================================================ FILE: tests/swoole_runtime/stream_select/return_val.phpt ================================================ --TEST-- swoole_runtime/stream_select: Bug the result of stream_select() is not equal to $read + $write + $error --SKIPIF-- --FILE-- ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']] , $pipes, __DIR__, [], [] ); var_dump($proc); if (!$proc) { exit(1); } $r = $pipes; $w = []; $e = []; $ret = stream_select($r, $w, $e, 1); var_dump($ret, (count($r) + count($w) + count($e))); foreach ($pipes as $pipe) { fclose($pipe); } proc_terminate($proc); if (defined('SIGKILL')) { proc_terminate($proc, SIGKILL); } else { proc_terminate($proc); } proc_close($proc); }); ?> --EXPECTF-- resource(%d) of type (process/coroutine) int(0) int(0) ================================================ FILE: tests/swoole_runtime/stream_select/rw_events.phpt ================================================ --TEST-- swoole_runtime/stream_select: rw events --SKIPIF-- --FILE-- get()) { $conn = @stream_socket_accept($server, 0.1); if ($conn) { go(function () use ($conn) { fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); echo fread($conn, 8192); fclose($conn); }); } } }); go(function () use ($n) { $fp1 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); $fp2 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); $r_array = [$fp1, $fp2]; $w_array = [$fp1, $fp2]; $e_array = null; usleep(1000); $retval = stream_select($r_array, $w_array, $e_array, 10); Assert::same($retval, 2); Assert::same(count($r_array), 2); Assert::same(count($w_array), 2); $n->set(0); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/stream_select/stream_set_blocking.phpt ================================================ --TEST-- swoole_runtime/stream_select: swoole_runtime/stream_set_blocking --SKIPIF-- --FILE-- --EXPECTF-- string(7) "0:test " string(2) "1:" ================================================ FILE: tests/swoole_runtime/stream_select/timeout.phpt ================================================ --TEST-- swoole_runtime/stream_select: timeout --SKIPIF-- --FILE-- \n"; } else { $r_array = [$fp1]; $w_array = $e_array = null; $s = microtime(true); $timeout = ms_random(0.1, 0.5); $n = stream_select($r_array, $w_array, $e_array, 0, $timeout * 1000000); Assert::same($n, 0); time_approximate($timeout, microtime(true) - $s); echo "SUCCESS\n"; } }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_runtime/stream_socket_pair.phpt ================================================ --TEST-- swoole_runtime: stream_socket_pair --SKIPIF-- --FILE-- --EXPECT-- Write byte: 13 Read byte: 13, beffer: hello world 0 Write byte: 13 Read byte: 13, beffer: hello world 1 Write byte: 13 Read byte: 13, beffer: hello world 2 Write byte: 13 Read byte: 13, beffer: hello world 3 Write byte: 13 Read byte: 13, beffer: hello world 4 Write byte: 13 Read byte: 13, beffer: hello world 5 Write byte: 13 Read byte: 13, beffer: hello world 6 Write byte: 13 Read byte: 13, beffer: hello world 7 Write byte: 13 Read byte: 13, beffer: hello world 8 Write byte: 13 Read byte: 13, beffer: hello world 9 ================================================ FILE: tests/swoole_runtime/tcp-c10k.phpt ================================================ --TEST-- swoole_runtime: server and client concurrency --SKIPIF-- --FILE-- ['so_reuseaddr' => true, 'backlog' => MAX_CONCURRENCY_MID]]); $socket = stream_socket_server( "tcp://0.0.0.0:{$port}", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx ); if (!$socket) { echo "$errstr ($errno)\n"; } else { $i = 0; while ($conn = stream_socket_accept($socket, 1)) { for ($n = MAX_REQUESTS; $n--;) { $data = fread($conn, tcp_length(fread($conn, tcp_type_length()))); Assert::same($data, "Hello Swoole Server #{$n}!"); fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!")); } if (++$i === MAX_CONCURRENCY_MID) { fclose($socket); echo "DONE\n"; break; } } } }); for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($port) { $fp = stream_socket_client("tcp://127.0.0.1:{$port}", $errno, $errstr, 1); if (!$fp) { echo "$errstr ($errno)\n"; } else { for ($n = MAX_REQUESTS; $n--;) { fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!")); $data = fread($fp, tcp_length(fread($fp, tcp_type_length()))); Assert::same($data, "Hello Swoole Client #{$n}!"); } fclose($fp); } }); } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/tcp.phpt ================================================ --TEST-- swoole_runtime: tcp --SKIPIF-- --FILE-- \n"; } else { $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); Assert::assert(strpos($content,'map.baidu.com') !== false); } }); Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_runtime/udg.phpt ================================================ --TEST-- swoole_runtime: udg --SKIPIF-- --FILE-- bind(__DIR__ . '/test.sock'); for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); echo "[Server] recv : $data\n"; } }); go(function () { $fp = stream_socket_client("udg://".__DIR__."/test.sock", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { for ($i = 0; $i < N; $i++) { fwrite($fp, "hello-{$i}"); } fclose($fp); } }); Swoole\Event::wait(); ?> --EXPECT-- [Server] recv : hello-0 [Server] recv : hello-1 [Server] recv : hello-2 [Server] recv : hello-3 [Server] recv : hello-4 ================================================ FILE: tests/swoole_runtime/udp-c10k.phpt ================================================ --TEST-- swoole_runtime: server and client concurrency --SKIPIF-- --FILE-- bind('127.0.0.1', $port); $client_map = []; for ($c = MAX_CONCURRENCY_MID; $c--;) { for ($n = 0; $n < MAX_REQUESTS; $n++) { $recv = $socket->recvfrom($peer); $client_uid = "{$peer['address']}:{$peer['port']}"; $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1; Assert::same($recv, "Client: Hello #{$id}!"); $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!"); } } $socket->close(); echo "DONE\n"; }); for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($port) { $fp = stream_socket_client("udp://127.0.0.1:{$port}", $errno, $errstr, 1); if (!$fp) { echo "$errstr ($errno)\n"; } else { for ($n = 0; $n < MAX_REQUESTS; $n++) { fwrite($fp, "Client: Hello #{$n}!"); $recv = fread($fp, 1024); list($_address, $_port) = explode(':', (stream_socket_get_name($fp, true))); Assert::assert($_address === '127.0.0.1' && (int)$_port === $port); Assert::same($recv, "Server: Hello #{$n}!"); } fclose($fp); } }); } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_runtime/udp.phpt ================================================ --TEST-- swoole_runtime: udp client --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); echo "[Server] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n"; $socket->sendto($peer['address'], $peer['port'], "Swoole: $data"); } }); go(function () { $fp = stream_socket_client("udp://127.0.0.1:9601", $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)
\n"; } else { for ($i = 0; $i < N; $i++) { fwrite($fp, "hello-{$i}"); $data = fread($fp, 1024); list($address, $port) = explode(':', (stream_socket_get_name($fp, true))); echo "[Client] recvfrom[{$address}:{$port}] : $data\n"; } fclose($fp); } }); Swoole\Event::wait(); ?> --EXPECTF-- [Server] recvfrom[127.0.0.1:%d] : hello-0 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-0 [Server] recvfrom[127.0.0.1:%d] : hello-1 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-1 [Server] recvfrom[127.0.0.1:%d] : hello-2 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-2 [Server] recvfrom[127.0.0.1:%d] : hello-3 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-3 [Server] recvfrom[127.0.0.1:%d] : hello-4 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-4 ================================================ FILE: tests/swoole_runtime/unix.phpt ================================================ --TEST-- swoole_runtime: unix stream --SKIPIF-- --FILE-- bind(SOCK_FILE), 'bind error: ' . $socket->errCode); Assert::true($socket->listen(), 'listen error: ' . $socket->errCode); $client = $socket->accept(); Assert::notNull($client); for ($i = 0; $i < N; $i++) { $data = $client->recv(); $client->send("Swoole: {$data}"); } usleep(1000); }); go(function () { $fp = stream_socket_client('unix://' . SOCK_FILE, $errno, $errstr, 30); if (!$fp) { echo "{$errstr} ({$errno})
\n"; } else { for ($i = 0; $i < N; $i++) { fwrite($fp, "hello-{$i}"); $data = fread($fp, 1024); [$address] = explode(':', stream_socket_get_name($fp, true)); $address = basename($address); echo "[Client] recvfrom[{$address}] : {$data}\n"; } fclose($fp); } }); Event::wait(); ?> --EXPECT-- [Client] recvfrom[test.sock] : Swoole: hello-0 [Client] recvfrom[test.sock] : Swoole: hello-1 [Client] recvfrom[test.sock] : Swoole: hello-2 [Client] recvfrom[test.sock] : Swoole: hello-3 [Client] recvfrom[test.sock] : Swoole: hello-4 ================================================ FILE: tests/swoole_runtime/unsafe/pcntl_fork.phpt ================================================ --TEST-- swoole_runtime/unsafe: pcntl_fork --SKIPIF-- --FILE-- --EXPECTF-- Warning: pcntl_fork() has been disabled for security reasons in %s on line %d ================================================ FILE: tests/swoole_server/accept_zero.phpt ================================================ --TEST-- swoole_server: accept zero fd --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("stats"); $data = $client->recv(); $json = json_decode($data, true); Assert::eq($json[1], 1); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, ]); $serv->on("Start", function ($serv) use ($pm) { fclose(STDIN); $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->send($fd, json_encode(iterator_to_array($serv->connections))."\r\n"); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/addListener.phpt ================================================ --TEST-- swoole_server: addlistener --SKIPIF-- --FILE-- parentFunc = function ($pid) { Co\Run(function () { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); $r = $cli->connect(UDP_SERVER_HOST, UDP_SERVER_PORT, 1); Assert::assert($r); $cli->send("test"); $i = $cli->getpeername(); Assert::assert($i !== false); $cli->send('shutdown'); $cli->close(); echo "SUCCESS\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(UDP_SERVER_HOST, UDP_SERVER_PORT, SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Packet", function (Server $serv, $data, $clientInfo) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } $serv->sendto($clientInfo['address'], $clientInfo['port'], $data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/addProcess.phpt ================================================ --TEST-- swoole_server: addProcess --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); Assert::assert($r); $cli->send("test"); $data = $cli->recv(); Assert::same($data, 'test'); $cli->send('shutdown'); $cli->close(); echo "SUCCESS\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); $process = new \Swoole\Process(function ($process) use ($serv) { while (1) { $msg = json_decode($process->read(), true); $serv->send($msg['fd'], $msg['data']); } }); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) use ($process) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } else { $process->write(json_encode(['fd' => $fd, 'data' => $data])); } }); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/addProcess_base.phpt ================================================ --TEST-- swoole_server: addProcess with SWOOLE_BASE --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); Assert::assert($r); $cli->send("test"); $data = $cli->recv(); Assert::same($data, 'test'); $cli->send('shutdown'); $cli->close(); echo "SUCCESS\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE); $process = new \Swoole\Process(function ($process) use ($serv) { while (1) { $msg = json_decode($process->read(), true); $serv->send($msg['fd'], $msg['data']); } }); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) use ($process) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } else { $process->write(json_encode(['fd' => $fd, 'data' => $data])); } }); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/addProcess_with_error.phpt ================================================ --TEST-- swoole_server: addProcess with fatal error --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { class Process4 extends Swoole\Process { public function __construct() { parent::__construct([$this, 'run']); } public function run() { go(function () { global $atomic; if ($atomic->add() > 5) { global $pm; $pm->wakeup(); Co::yield(); return; } echo "sleep start then "; Co::sleep(0.01); echo "sleep end\n"; trigger_error('ERROR', E_USER_ERROR); }); } } $server = new Swoole\Server('127.0.0.1', get_one_free_port(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set(['log_file' => '/dev/null']); $server->on('packet', function () { }); $server->addProcess(new Process4); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d DONE --EXPECTF_85-- sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process4->{closure:Process4::run():%d}() #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process4->{closure:Process4::run():%d}() #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process4->{closure:Process4::run():%d}() #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process4->{closure:Process4::run():%d}() #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process4->{closure:Process4::run():%d}() #2 {main} DONE ================================================ FILE: tests/swoole_server/addProcess_with_event_wait.phpt ================================================ --TEST-- swoole_server: addProcess with event wait --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function () use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { class Process5 extends Swoole\Process { public function __construct() { parent::__construct([$this, 'run']); } public function run() { Swoole\Timer::tick(100, function (int $id) use (&$i) { global $pm; if (++$i === 10) { Swoole\Timer::clear($id); $pm->wakeup(); } echo "Tick {$i}\n"; }); } } $server = new Swoole\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set(['log_file' => '/dev/null']); $server->on('packet', function () { }); $server->addProcess(new Process5); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Tick 1 Tick 2 Tick 3 Tick 4 Tick 5 Tick 6 Tick 7 Tick 8 Tick 9 Tick 10 ================================================ FILE: tests/swoole_server/addProcess_with_tick.phpt ================================================ --TEST-- swoole_server: addProcess with swoole_timer_tick fatal error --SKIPIF-- --FILE-- add() > 5) { global $pm; $pm->wakeup(); Co::yield(); return; } echo "sleep start then "; Co::sleep(0.01); echo "sleep end\n"; trigger_error('ERROR', E_USER_ERROR); }); } } $pm->parentFunc = function () use ($pm) { $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', get_one_free_port(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set([ 'log_file' => '/dev/null', // 'worker_num' => 1, ]); $server->on('packet', function () { }); $server->addProcess(new Process3); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d DONE --EXPECTF_85-- sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process3->{closure:Process3::run():%d}(%d) #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process3->{closure:Process3::run():%d}(%d) #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process3->{closure:Process3::run():%d}(%d) #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process3->{closure:Process3::run():%d}(%d) #2 {main} sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d Stack trace: #0 %s(%d): trigger_error('ERROR', 256) #1 [internal function]: Process3->{closure:Process3::run():%d}(%d) #2 {main} DONE ================================================ FILE: tests/swoole_server/base/reload_1.phpt ================================================ --TEST-- swoole_server/base: reload all workers --SKIPIF-- --FILE-- new Swoole\Atomic(), 'task_worker' => new Swoole\Atomic() ]; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { while (!file_exists(TEST_PID_FILE)) { usleep(100 * 1000); } $pid = file_get_contents(TEST_PID_FILE); $random = mt_rand(1, 5); usleep(100 * 1000); for ($n = $random; $n--;) { Swoole\Process::kill($pid, SIGUSR1); usleep(200 * 1000); } /**@var $counter Swoole\Atomic[] */ $total = $counter['worker']->get() - $worker_num; $expect = $random * $worker_num; Assert::same($total, $expect, "[worker reload {$total} but expect {$expect}]"); $total = $counter['task_worker']->get() - $task_worker_num; $expect = $random * $task_worker_num; Assert::same($total, $expect, "[task worker reload {$total} but expect {$random}]"); $log = file_get_contents(TEST_LOG_FILE); $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); if (!Assert::assert(empty($log))) { var_dump($log); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { @unlink(TEST_LOG_FILE); @unlink(TEST_PID_FILE); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'log_file' => TEST_LOG_FILE, 'pid_file' => TEST_PID_FILE, 'worker_num' => $worker_num, 'task_worker_num' => $task_worker_num, ]); $server->on('ManagerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { /**@var $counter Swoole\Atomic[] */ global $counter; $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; $atomic->add(1); }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); $server->on('Task', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_server/base/reload_2.phpt ================================================ --TEST-- swoole_server/base: reload task workers --SKIPIF-- --FILE-- new Swoole\Atomic(), 'task_worker' => new Swoole\Atomic() ]; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { while (!file_exists(TEST_PID_FILE)) { usleep(100 * 1000); } $pid = file_get_contents(TEST_PID_FILE); $random = mt_rand(1, 5); usleep(100 * 1000); for ($n = $random; $n--;) { Swoole\Process::kill($pid, SIGUSR2); usleep(200 * 1000); } /**@var $counter Swoole\Atomic[] */ $total = $counter['worker']->get() - $worker_num; Assert::same($total, 0, "[worker reload {$total} but expect 0]"); $total = $counter['task_worker']->get() - $task_worker_num; $expect = $random * $task_worker_num; Assert::same($total, $expect, "[task worker reload {$total} but expect {$expect}]"); $log = file_get_contents(TEST_LOG_FILE); $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); if (!Assert::assert(empty($log))) { var_dump($log); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { @unlink(TEST_LOG_FILE); @unlink(TEST_PID_FILE); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'log_file' => TEST_LOG_FILE, 'pid_file' => TEST_PID_FILE, 'worker_num' => $worker_num, 'task_worker_num' => $task_worker_num, ]); $server->on('ManagerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { /**@var $counter Swoole\Atomic[] */ global $counter; $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; $atomic->add(1); }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); $server->on('Task', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_server/base/shutdown.phpt ================================================ --TEST-- swoole_server/base: shutdown --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send($pm->getRandomData()) > 0); }); $pm->wait(-1); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => mt_rand(2, 4), 'log_file' => '/dev/null', ]); $server->on('start', function () use ($pm) { echo "START\n"; }); $server->on('managerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { Assert::same($data, $pm->getRandomData()); $server->shutdown(); }); $server->on('shutdown', function () use ($pm) { echo "SHUTDOWN\n"; $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(0); ?> --EXPECT-- START SHUTDOWN ================================================ FILE: tests/swoole_server/base/shutdown_single.phpt ================================================ --TEST-- swoole_server/base: shutdown [single process] --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send($pm->getRandomData()) > 0); }); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('start', function (Server $server) use ($pm) { echo "START\n"; Assert::eq($server->getManagerPid(), 0); Assert::eq($server->getMasterPid(), posix_getpid()); $pm->wakeup(); }); $server->on('receive', function (Server $server, int $fd, int $rid, string $data) use ($pm) { Assert::same($data, $pm->getRandomData()); $server->shutdown(); }); $server->on('shutdown', function () { echo "SHUTDOWN\n"; }); $server->start(); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(0); ?> --EXPECT-- START SHUTDOWN ================================================ FILE: tests/swoole_server/bigPipeMessage.phpt ================================================ --TEST-- swoole_server: send big pipe message --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port, $pm) { Co\Run(function () use ($port) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); $cli->send("test"); $data = $cli->recv(); echo $data; $cli->close(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Server(TCP_SERVER_HOST, $port, SWOOLE_PROCESS); $serv->set([ "worker_num" => 2, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("pipeMessage", function ($serv, $worker_id, $data) { if (is_array($data) and strlen($data['data']) == N) { $serv->send($data['fd'], "OK\n"); } }); $serv->on("receive", function ($serv, $fd, $rid, $data) { $data = str_repeat("A", N); $serv->sendMessage(array('data' => $data, 'fd' => $fd), 1 - $serv->worker_id); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/big_session_id.phpt ================================================ --TEST-- swoole_server: big session id --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { global $count; for ($i = 0; $i < N; $i++) { go(function () use ($port, $i) { $cli = new Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); $data = $cli->recv(); Assert::same($data, (START_SESSION_ID + 1 + $i) . "-OK"); global $count; $count++; $cli->close(); }); } Event::wait(); Assert::same($count, N); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'log_file' => '/dev/null', 'start_session_id' => START_SESSION_ID, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Server $serv, $fd, $rid) { global $count; $count++; if ($count == N) { Swoole\Event::defer(function () use ($serv) { foreach ($serv->connections as $fd) { $serv->send($fd, "$fd-OK"); } }); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/big_udp_packet.phpt ================================================ --TEST-- swoole_server: send big udp packet to server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_UDP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $port)) { exit("connect failed\n"); } $client->send(str_repeat('A', N)); $data = $client->recv(); Assert::same(strlen($data), N); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set(['worker_num' => 1, 'log_file' => '/dev/null']); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('packet', function ($serv, $data, $client) { $serv->sendto($client['address'], $client['port'], str_repeat('B', strlen($data))); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/bind.phpt ================================================ --TEST-- swoole_server: bind --SKIPIF-- --FILE-- send(opcode_encode("bind", [2, 42])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $binded) = opcode_decode($recv); Assert::assert($binded); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/bug_11000_01.phpt ================================================ --TEST-- swoole_server: bug_11000_01 --SKIPIF-- --FILE-- childFunc = function () { $port = get_one_free_port(); $serv = new Server(TCP_SERVER_HOST, $port, SWOOLE_PROCESS); $process = new Process(function ($process) use ($serv) { usleep(10000); $stats = $serv->stats(); Assert::isArray($stats); Assert::keyExists($stats, 'connection_num'); Assert::keyExists($stats, 'request_count'); usleep(200000); $serv->shutdown(); }); $serv->set(['worker_num' => 2, 'log_file' => '/dev/null']); $serv->on('receive', function () { }); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/bug_1864.phpt ================================================ --TEST-- swoole_server: bug Github#1864 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { function run() { global $pm; $client = new \Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); static $index = 0; if ($client->connect('127.0.0.1', $pm->getFreePort()) == false) { echo "connect error\n"; return; } $count = rand(10240, 25600); for ($i = 0; $i < M; $i++) { $seq = $i % 10; $len = rand(10240, 25600); $message = "|||len=$len|||index=$index|||" . str_repeat(strval($seq), $len); $data = pack('N', $index++) . pack('N', strlen($message)) . $message; if ($client->send($data) == false) { echo "send failed, index=$index\n"; break; } } } for ($i = 0; $i < N; $i++) { run(); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $ss = [ 'daemonize' => 0, 'dispatch_mode' => 1, 'worker_num' => 1, 'backlog' => 512, 'max_request' => 0, 'enable_coroutine' => false, 'open_length_check' => true, 'package_max_length' => 1048576, // 1MB 'package_length_type' => 'N', 'package_length_offset' => 4, // | seq || size || message | 'package_body_offset' => 8, // seq + size 'socket_buffer_size' => 1048576 * 4, 'output_buffer_size' => 1048576, 'log_file' => TEST_LOG_FILE, ]; $status = new Swoole\Atomic(0); $tcp = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $tcp->set($ss); $tcp->on('receive', function (Server $server, $fd, $reactorID, $data) use ($status) { $size = unpack('N', substr($data, 4, 4))[1]; if ($size !== strlen($data) - 8) { $server->shutdown(); $status->set(1); } }); $tcp->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $tcp->on("shutdown", function (Server $serv) use ($status) { if ($status->get() == 1) { exit(10); } }); $tcp->start(); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(0); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/bug_2308.phpt ================================================ --TEST-- swoole_server: bug Github#2308 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('0.0.0.0', 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->set([ 'worker_num' => MAX_PROCESS_NUM, 'log_file' => '/dev/null', 'enable_coroutine' => false, 'hook_flags' => SWOOLE_HOOK_ALL, ]); $server->on('start', function () { Swoole\Coroutine::create(function () { $redis = new \Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $ret = $redis->set('foo', 'bar'); Assert::assert($ret); $ret = $redis->get('foo'); Assert::same($ret, 'bar'); }); }); $server->on('workerStart', function ($server) { Swoole\Timer::after(1000, function () use ($server) { $server->stop(); }); }); $server->on('Receive', function (Server $server, int $fd, int $reactor_id, string $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/bug_2313.phpt ================================================ --TEST-- swoole_server: bug Github#2313 --SKIPIF-- --FILE-- childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', 9501, SWOOLE_PROCESS); $process = new Swoole\Process(function () { }); $server->addProcess($process); var_dump($process->id); $pm->wakeup(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- int(%d) ================================================ FILE: tests/swoole_server/bug_2639.phpt ================================================ --TEST-- swoole_server: bug Github#2639 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem', ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); $data = $client->recv(); Assert::assert($data); $json = json_decode($data, true); Assert::isArray($json); Assert::same($json['subject']['O'], 'swoole'); }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'task_worker_num' => 1, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $cert_file = $serv->getClientInfo($fd)['ssl_client_cert']; $serv->send($fd, json_encode(openssl_x509_parse($cert_file))); $serv->task(['fd' => $fd]); }); $serv->on('task', function($serv, $taskId, $wid, $data) { $info = $serv->getClientInfo($data['fd']); Assert::isArray($info); Assert::assert(!array_key_exists('ssl_client_cert', $info)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/bug_2736.phpt ================================================ --TEST-- swoole_server: bug Github#2736 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set(['timeout' => 2, 'open_eof_check' => true, 'package_eof' => "\r\n"]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $i = 230; $data = "hello world-" . str_repeat('A', $i) . '- BB'; $package_length = pack('N', strlen($data)); $array = str_split($package_length . $data, 100); foreach ($array as $value) { $client->send($value); } $data = $client->recv(); Assert::assert($data); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'open_length_check' => true, 'package_max_length' => 2097152, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'log_file' => TEST_LOG_FILE, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $_k = unpack('Nlen', substr($data, 0, 4)); $len = $_k['len']; $body = substr($data, 4); $serv->send($fd, json_encode(['len' => $len, 'body' => $body]) . "\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/bug_aio.phpt ================================================ --TEST-- swoole_server: bug aio --SKIPIF-- --FILE-- setWaitTimeout(0); $pm->parentFunc = function () { }; $pm->childFunc = function () use ($pm) { go(function () { Assert::same(Co::readFile(__FILE__), __FILE_CONTENTS__); // will be discarded }); Swoole\Event::wait(); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { echo 'read file' . PHP_EOL; Assert::same(Co::readFile(__FILE__), __FILE_CONTENTS__); echo 'read file ok' . PHP_EOL; $pm->wakeup(); usleep(100000); $server->shutdown(); }); $server->on('Receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- read file read file ok ================================================ FILE: tests/swoole_server/check_callback.phpt ================================================ --TEST-- swoole_server: check callback --SKIPIF-- --FILE-- start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require on'.ucfirst($callback).' callback'); Assert::eq($result['code'], 255); } test_create_server(Swoole\Server::class, Constant::EVENT_RECEIVE); test_create_server(Swoole\Http\Server::class, Constant::EVENT_REQUEST); test_create_server(Swoole\WebSocket\Server::class, Constant::EVENT_MESSAGE); $proc = new Process(function () { $server = new Swoole\Server('127.0.0.1', 0, SWOOLE_BASE, SWOOLE_SOCK_UDP); $server->start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require onPacket callback'); Assert::eq($result['code'], 255); $proc = new Process(function () { $server = new Swoole\Server('127.0.0.1', 0, SWOOLE_BASE, SWOOLE_SOCK_UDP); $server->on(Constant::EVENT_PACKET, function () {}); $server->addlistener('127.0.0.1', 0, SWOOLE_SOCK_TCP); $server->start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require onReceive callback'); Assert::eq($result['code'], 255); $proc = new Process(function () { $server = new Swoole\Server('127.0.0.1', 0, SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on(Constant::EVENT_RECEIVE, function () {}); $server->addlistener('127.0.0.1', 0, SWOOLE_SOCK_UDP); $server->start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require onPacket callback'); Assert::eq($result['code'], 255); $proc = new Process(function () { $server = new Swoole\Http\Server('127.0.0.1', 0, SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on(Constant::EVENT_REQUEST, function () {}); $server->addlistener('127.0.0.1', 0, SWOOLE_SOCK_UDP); $server->start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require onPacket callback'); Assert::eq($result['code'], 255); $proc = new Process(function () { $server = new Swoole\Http\Server('127.0.0.1', 0, SWOOLE_BASE, SWOOLE_SOCK_TCP); $server->on(Constant::EVENT_REQUEST, function () {}); $port = $server->addlistener('127.0.0.1', 0, SWOOLE_SOCK_TCP); $port->set(['open_http_protocol' => false]); $server->start(); }, true, SOCK_STREAM, false); $proc->start(); $result = Process::wait(); Assert::contains($proc->read(), 'require onReceive callback'); Assert::eq($result['code'], 255); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/check_chunk_total_size.phpt ================================================ --TEST-- swoole_server: check chunk total size --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = str_repeat('A', 1 * 1024 * 1024) . "\r\n"; $chunk_size = 2048; $len = strlen($data); $chunk_num = intval($len / $chunk_size) + 1; for ($i = 0; $i < $chunk_num; $i++) { if ($len < ($i + 1) * $chunk_size) { $sendn = $len - ($i * $chunk_size); } else { $sendn = $chunk_size; } $client->send(substr($data, $i * $chunk_size, $sendn)); } $recv_data = $client->recv(); Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'package_eof' => "\r\n", 'open_eof_check' => true, "worker_num" => 1, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { Assert::length($data, 1 * 1024 * 1024 + 2); $serv->send($fd, "shutdown"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/client_close_in_writable_event.phpt ================================================ --TEST-- swoole_server: client close in writable event --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 128 * 1024]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("begin"); $pm->wait(); usleep(100000); $client->close(); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'kernel_socket_send_buffer_size' => 128 * 1024, 'buffer_output_size' => 4 * 1024 * 1024, ]); $serv->on(Constant::EVENT_WORKER_START, function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_CONNECT, function (Server $serv, $fd, $reactor_id) { echo "CONNECT $fd\n"; }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) use ($pm) { $n = 8; $serv->pause($fd); while ($n--) { $serv->send($fd, str_repeat('A', 2 * 1024 * 1024) . "\r\n"); } $pm->wakeup(); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) use ($pm) { echo "CLOSE $fd\n"; $pm->wakeup(); }); $serv->start(); }; $pm->childFirst(); $pm->run(); echo 'DONE'.PHP_EOL; ?> --EXPECT-- CONNECT 1 CLOSE 1 DONE ================================================ FILE: tests/swoole_server/close_force.phpt ================================================ --TEST-- swoole_server: force to close --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 128*1024]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); Co::sleep(2); $data = ''; while(true) { $ret = $client->recv(); if (empty($ret)) { break; } $data .= $ret; if (substr($ret, -2, 2) == "\r\n") { break; } } Assert::greaterThan(strlen($data), 8192); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, 'kernel_socket_send_buffer_size' => 128*1024, 'buffer_output_size' => 4*1024*1024, 'heartbeat_idle_time' => 2, 'heartbeat_check_interval' => 1, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->send($fd, str_repeat('A', 2 * 1024 * 1024)."\r\n"); $serv->close($fd); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_in_connect_callback.phpt ================================================ --TEST-- swoole_server: close in onConnect callback --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::eq($data, ""); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'log_file' => TEST_LOG_FILE, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { }); $serv->on(Constant::EVENT_CONNECT, function (Server $serv, $fd, $reactor_id) { $serv->close($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_in_manager.phpt ================================================ --TEST-- swoole_server: close in manager process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { swoole_loop_n(N, function () use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::eq($data, ""); }); }); }); echo "ALL DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'log_file' => TEST_LOG_FILE, ]); $serv->on(Constant::EVENT_MANAGER_START, function ($serv) use ($pm) { Timer::after(200, function () use ($serv) { foreach ($serv->connections as $fd) { $serv->close($fd); } }); $pm->wakeup(); }); $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $reactor_id, $data) { }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { Assert::assert(posix_getpid() != $serv->manager_pid); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ALL DONE ================================================ FILE: tests/swoole_server/close_in_master.phpt ================================================ --TEST-- swoole_server: close in master process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::eq($data, ""); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, ]); $serv->on('start', function ($serv) { Timer::tick(100, function ($timer) use ($serv) { if ($serv->exists(1)) { $serv->close(1); Timer::clear($timer); } }); }); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_in_non_current_worker.phpt ================================================ --TEST-- swoole_server: close in non-current worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::string($data); Assert::length($data, 0); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); global $socket; $result[] = fgets($socket[1]); $result[] = fgets($socket[1]); Assert::eq($result[0], $result[1]); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'log_file' => TEST_LOG_FILE, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { global $socket; fwrite($socket[0], $serv->worker_id."\n"); $serv->sendMessage(['close_fd' => $fd, 'worker_id' => $serv->worker_id], 1 - $serv->worker_id); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { global $socket; fwrite($socket[0], $serv->worker_id . "\n"); }); $serv->on(Constant::EVENT_PIPE_MESSAGE, function (Server $serv, $workerId, $msg) { Assert::true($serv->close($msg['close_fd'])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_in_other_worker_with_base.phpt ================================================ --TEST-- swoole_server: close in another process with base mode --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $data = base64_encode(random_bytes(128)); $cli->send($data); Assert::same($cli->recv(), ''); }); $pm->kill(); Assert::eq($GLOBALS['atomic']->get(), 1); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 2, 'log_file' => TEST_LOG_FILE, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_PIPE_MESSAGE, function ($serv, $workerId, $msg) { Assert::assert($serv->close($msg['fd'])); Assert::false($serv->close(99999)); Assert::eq($serv->getLastError(), SWOOLE_ERROR_SESSION_NOT_EXIST); }); $serv->on(Constant::EVENT_RECEIVE, function (Swoole\Server $serv, $fd, $rid, $data) { $serv->sendMessage(['data' => $data, 'fd' => $fd], 1 - $serv->getWorkerId()); }); $serv->on(Constant::EVENT_CLOSE, function ($serv, $fd, $tid) { $GLOBALS['atomic']->set($fd); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/close_in_task_worker.phpt ================================================ --TEST-- swoole_server: close in task worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::string($data); Assert::length($data, 0); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); global $socket; $result[] = fgets($socket[1]); $result[] = fgets($socket[1]); Assert::eq($result[0], $result[1]); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => TEST_LOG_FILE, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { global $socket; fwrite($socket[0], $serv->worker_id . "\n"); $serv->task(['close_fd' => $fd, 'worker_id' => $serv->worker_id]); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { global $socket; fwrite($socket[0], $serv->worker_id . "\n"); }); $serv->on(Constant::EVENT_TASK, function (Server $serv, $task_id, $worker_id, $msg) { Assert::true($serv->close($msg['close_fd'])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_max_fd.phpt ================================================ --TEST-- swoole_server: close_max_fd --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { go(function() use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send('test 1')); $client->recv(); Co::sleep(1); $client->send('ping 1'); Co::sleep(1); $pm->kill(); }); go(function() use ($pm) { $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); }); go(function() use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); $client->send('test 2'); Co::sleep(1); }); }); }; $pm->childFunc = function () use ($pm) { $log_file = __DIR__ . '/close_max_fd.log'; $fp = fopen($log_file, 'a'); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'log_level' => SWOOLE_LOG_ERROR, ]); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) use ($fp) { fputs($fp, "recv: $data\n"); fputs($fp, 'getClientList: ' . implode(';', $serv->getClientList()) . "\n"); Assert::true(!empty($serv->getClientList())); foreach ($serv->connections as $_fd) { fputs($fp, "foreach: fd-{$_fd}\n"); } $serv->send($fd, "Server: " . $data); }); $server->on('close', function ($server, $fd) use ($fp) { fputs($fp, "close: fd-{$fd}\n"); }); $server->start(); fclose($fp); $cnt = file_get_contents($log_file); Assert::eq(substr_count($cnt, 'recv: test 1'), 1); Assert::eq(substr_count($cnt, 'recv: test 2'), 1); Assert::eq(substr_count($cnt, 'recv: ping 1'), 1); Assert::eq(substr_count($cnt, 'close: fd-2'), 1); Assert::eq(substr_count($cnt, 'close: fd-1'), 1); Assert::eq(substr_count($cnt, 'foreach: fd-1'), 3); Assert::eq(substr_count($cnt, 'foreach: fd-2'), 2); Assert::eq(substr_count($cnt, 'getClientList: 1;2'), 2); Assert::eq(substr_count($cnt, "getClientList: 1\n"), 1); unlink($log_file); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/close_queued.phpt ================================================ --TEST-- swoole_server: close queued --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 128*1024]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); Co::sleep(1); $data = ''; while(true) { $ret = $client->recv(); if (empty($ret)) { break; } $data .= $ret; if (substr($ret, -2, 2) == "\r\n") { break; } } Assert::eq(strlen($data), 2*1024*1024 +2); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, 'kernel_socket_send_buffer_size' => 128*1024, 'buffer_output_size' => 4*1024*1024, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->send($fd, str_repeat('A', 2 * 1024 * 1024)."\r\n"); $serv->close($fd); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/close_reset.phpt ================================================ --TEST-- swoole_server: close with reset --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set(['socket_buffer_size' => 128 * 1024]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); Co::sleep(1); $data = ''; while (true) { $ret = $client->recv(); if (empty($ret)) { break; } $data .= $ret; if (substr($ret, -2, 2) == "\r\n") { break; } } Assert::lessThan(strlen($data), N); echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, 'kernel_socket_send_buffer_size' => 128 * 1024, 'socket_buffer_size' => 8 * 1024 * 1024, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->send($fd, str_repeat('A', N) . "\r\n"); Assert::eq($serv->stats()['connection_num'], 1); phpt_var_dump("close[0]"); Assert::true($serv->close($fd)); usleep(50000); phpt_var_dump("close[1]"); Assert::false($serv->close($fd)); Assert::eq(swoole_last_error(), SWOOLE_ERROR_SESSION_CLOSED); Assert::eq($serv->stats()['connection_num'], 1); Timer::after(100, function () use ($fd, $serv) { phpt_var_dump("close[2]"); $serv->close($fd, true); usleep(50000); Assert::eq($serv->stats()['connection_num'], 0); }); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/command.phpt ================================================ --TEST-- swoole_server: command [1] --SKIPIF-- --FILE-- addCommand('test_getpid', SWOOLE_SERVER_COMMAND_MASTER | SWOOLE_SERVER_COMMAND_EVENT_WORKER, function ($server) { return json_encode(['pid' => posix_getpid()]); }); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 2, ]); $server->on('start', function (Server $serv) { $result = $serv->command('test_getpid', 0, SWOOLE_SERVER_COMMAND_MASTER, ['type' => 'master']); Assert::eq($result['pid'], $serv->getMasterPid()); $result = $serv->command('test_getpid', 1, SWOOLE_SERVER_COMMAND_EVENT_WORKER, ['type' => 'worker']); Assert::eq($result['pid'], $serv->getWorkerPid(1)); $result = $serv->command('test_not_found', 1, SWOOLE_SERVER_COMMAND_EVENT_WORKER, ['type' => 'worker']); Assert::false($result); $serv->shutdown(); }); $server->on('request', function (Request $request, Response $response) { }); $server->start(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/connections.phpt ================================================ --TEST-- swoole_server: connection iterator --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { global $count; for ($i = 0; $i < N; $i++) { go(function () use ($port) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); $data = $cli->recv(); Assert::same($data, "OK"); global $count; $count++; $cli->close(); }); } Event::wait(); Assert::same($count, N); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Server $serv, $fd, $rid) { global $count; $count++; if ($count == N) { Swoole\Event::defer(function () use ($serv) { foreach ($serv->connections as $fd) { $serv->send($fd, "OK"); } }); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/discard_timeout_packet.phpt ================================================ --TEST-- swoole_server: discard timeout packet --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $n = 2; while($n--) { $client = new Client(SWOOLE_SOCK_TCP); $client->set([ 'open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "\r\n", ]); $client->connect('127.0.0.1', $pm->getFreePort()); $client->send("Swoole\r\nhello world\r\nphp\r\njava\r\n"); $client->close(); } Co::sleep(0.8); }); $pm->kill(); Assert::eq(substr_count(file_get_contents(TMP_LOG_FILE), 'Worker_discard_data() (ERRNO 1007): [2] ignore data'), 8); unlink(TMP_LOG_FILE); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "package_eof" => "\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => 3, 'discard_timeout_request' => true, "worker_num" => 1, 'log_file' => TMP_LOG_FILE, ]); $serv->on('workerStart', function (Server $serv) use ($pm) { $pm->wakeup(); Co::sleep(0.5); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, "hello {$data}\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/dispatch_fallback.phpt ================================================ --TEST-- swoole_server: dispatch_fallback --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { global $count, $stats; for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($port) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, ]); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); for ($i = 0; $i < MAX_REQUESTS; $i++) { $cli->send("hello world\r\n\r\n"); } $cli->count = 0; for ($i = 0; $i < MAX_REQUESTS; $i++) { $data = $cli->recv(); global $stats; $wid = trim($data); if (isset($stats[$wid])) { $stats[$wid]++; } else { $stats[$wid] = 1; } $cli->count++; if ($cli->count == MAX_REQUESTS) { $cli->close(); } } }); } Event::wait(); Swoole\Process::kill($pid); phpt_var_dump($stats); foreach ($stats as $s) { Assert::same($s, MAX_REQUESTS * MAX_CONCURRENCY_MID / WORKER_N); } echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $port) { $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set(array( "worker_num" => WORKER_N, 'dispatch_mode' => 1, 'dispatch_func' => function ($serv, $fd, $type, $data) { return SWOOLE_DISPATCH_RESULT_USERFUNC_FALLBACK; }, 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, $serv->worker_id . "\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_func.phpt ================================================ --TEST-- swoole_server: dispatch_func --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_UDP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send($data = get_safe_random())); Assert::same($client->recv(), $data); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { function dispatch_packet($server, $fd, $type) { return $fd % $server->setting['worker_num']; } $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set([ 'worker_num' => rand(4, 8), 'log_file' => '/dev/null', 'dispatch_func' => 'dispatch_packet' ]); $server->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('packet', function (Server $server, $data, $client) { $fd = unpack('L', pack('N', ip2long($client['address'])))[1]; Assert::same($fd % $server->setting['worker_num'], $server->worker_id); $server->sendto($client['address'], $client['port'], $data); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_func_discard.phpt ================================================ --TEST-- swoole_server: dispatch_func [discard] --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort(), 0.2)); $data = str_repeat('A', 65534)."\r\n\r\n"; Assert::assert($client->send($data)); Assert::same(false, @$client->recv()); }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { function dispatch_packet($server, $fd, $type) { return -1; } $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'open_eof_check' => true, 'package_eof' => "\r\n\r\n", 'dispatch_func' => 'dispatch_packet', ]); $server->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $server, $fd, $threadId, $data) { $server->send($fd, $data); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_func_memory_leak.phpt ================================================ --TEST-- swoole_server: dispatch_func_memory_leak --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send(str_repeat(get_safe_random(1024), 32))); }); } Swoole\Event::wait(); sleep(1); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $mem_size = 0; function dispatch_packet($server, $fd, $type, $data) { global $mem_size; if ($mem_size) { Assert::assert($mem_size + 128 * 1024 > memory_get_usage()); } else { $mem_size = memory_get_usage(); } return str_repeat('0', 1024 * 1024); } $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $server->set([ 'worker_num' => rand(4, 8), 'log_file' => '/dev/null', 'reactor_num' => 1, 'dispatch_func' => 'dispatch_packet', 'trace_flags' => SWOOLE_TRACE_EVENT, 'log_level' => 0, ]); $server->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Server $server, $data, $client) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_mode_1.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 1 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { global $count, $stats; for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($port) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, ]); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); for ($i = 0; $i < MAX_REQUESTS; $i++) { $cli->send("hello world\r\n\r\n"); } $cli->count = 0; for ($i = 0; $i < MAX_REQUESTS; $i++) { $data = $cli->recv(); global $stats; $wid = trim($data); if (isset($stats[$wid])) { $stats[$wid]++; } else { $stats[$wid] = 1; } $cli->count++; if ($cli->count == MAX_REQUESTS) { $cli->close(); } } }); } Event::wait(); Swoole\Process::kill($pid); phpt_var_dump($stats); foreach ($stats as $s) { Assert::same($s, MAX_REQUESTS * MAX_CONCURRENCY_MID / WORKER_N); } echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $port, $barrier) { $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set(array( "worker_num" => WORKER_N, 'dispatch_mode' => 1, 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, 'log_file' => '/dev/null', )); $serv->on('WorkerStart', function (Server $serv) use ($pm, $barrier) { if ($barrier->add() == WORKER_N) { $pm->wakeup(); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, $serv->worker_id . "\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_mode_10.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 10 [concurrent lb] --SKIPIF-- --FILE-- column('count', Table::TYPE_INT); $table->create(); const N = IS_MAC_OS ? 256 : 1024; const EOF = "\r\n\r\n"; $pm = new SwooleTest\ProcessManager; $pm->magic_code = rand(10000000, 90000000); $pm->parentFunc = function ($pid) use ($pm, $table) { run(function () use ($pm, $table) { $rand = rand(1, 4); $data = array( 'name' => __FILE__, 'sid' => $pm->magic_code, 'content' => str_repeat('A', 1024 * $rand), ); $_serialize_data = serialize($data) . EOF; $n = N; while ($n--) { for ($i = 0; $i < 16; $i++) { go(function () use ($pm, $_serialize_data) { $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::true($client->post('/', $_serialize_data)); Assert::eq($client->getStatusCode(), 200); Assert::eq($client->getBody(), "SUCCESS" . EOF); }); } System::sleep(0.002); } }); $pm->kill(); $array = array_column(iterator_to_array($table), 'count'); $standard_deviation = sqrt(swoole_get_variance(swoole_get_average($array), $array)); Assert::greaterThan($standard_deviation, 1); Assert::lessThan($standard_deviation, 5); echo 'DONE' . PHP_EOL; }; $pm->childFunc = function () use ($pm, $table) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'dispatch_mode' => SWOOLE_DISPATCH_CONCURRENT_LB, 'worker_num' => 4, 'log_file' => '/dev/null', )); $serv->on(Constant::EVENT_WORKER_START, function (Server $serv, $worker_id) use ($pm) { if ($worker_id == 0) { $pm->wakeup(); } }); $serv->on(Constant::EVENT_REQUEST, function ($req, $resp) use ($pm, $table, $serv) { $table->incr($serv->getWorkerId(), 'count'); if (rand(1000, 9999) % 10 == 0) { System::sleep(0.5); } $_data = unserialize(rtrim($req->getContent())); if ($_data and is_array($_data) and $_data['sid'] == $pm->magic_code) { $resp->end("SUCCESS" . EOF); } else { $resp->end("ERROR" . EOF); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_mode_3.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 3 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { global $count, $stats; for ($i = 0; $i < MAX_CONCURRENCY; $i++) { go(function () use ($port) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, ]); $r = $cli->connect(TCP_SERVER_HOST, $port, 1); Assert::assert($r); for ($i = 0; $i < MAX_REQUESTS; $i++) { $cli->send("hello world\r\n\r\n"); } $cli->count = 0; for ($i = 0; $i < MAX_REQUESTS; $i++) { $data = $cli->recv(); global $stats; $wid = trim($data); if (isset($stats[$wid])) { $stats[$wid]++; } else { $stats[$wid] = 1; } $cli->count++; if ($cli->count == MAX_REQUESTS) { $cli->close(); } } }); } Event::wait(); Process::kill($pid); phpt_var_dump($stats); Assert::eq(count($stats), WORKER_N); Assert::lessThan($stats[5], MAX_REQUESTS); Assert::lessThan($stats[10], MAX_REQUESTS); Assert::same(array_sum($stats), MAX_REQUESTS * MAX_CONCURRENCY); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $port, $barrier) { $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set([ 'worker_num' => WORKER_N, 'dispatch_mode' => 3, 'package_eof' => "\r\n\r\n", 'enable_coroutine' => false, 'open_eof_split' => true, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm, $barrier) { if ($barrier->add() == WORKER_N) { $pm->wakeup(); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { if ($serv->worker_id == 10 or $serv->worker_id == 5) { usleep(5000); } $serv->send($fd, $serv->worker_id . "\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_mode_7.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 7 [stream] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = array( 'name' => __FILE__, 'sid' => 1000236, 'content' => str_repeat('A', 8192 * rand(1, 3)), ); $_serialize_data = serialize($data) . "\r\n\r\n"; $chunk_size = 2048; $len = strlen($_serialize_data); $chunk_num = intval($len / $chunk_size) + 1; for ($i = 0; $i < $chunk_num; $i++) { if ($len < ($i + 1) * $chunk_size) { $sendn = $len - ($i * $chunk_size); } else { $sendn = $chunk_size; } $client->send(substr($_serialize_data, $i * $chunk_size, $sendn)); usleep(rand(100, 1000)); } echo $client->recv(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => 7, 'package_max_length' => 1024 * 1024 * 2, //2M 'worker_num' => 1, 'log_file' => '/dev/null', "reload_async" => true, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $_data = unserialize(rtrim($data)); if ($_data and is_array($_data) and $_data['sid'] == 1000236) { $serv->send($fd, "SUCCESS\n"); } else { $serv->send($fd, "ERROR\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/dispatch_mode_8.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 8 [co conn lb] --SKIPIF-- --FILE-- column('count', Table::TYPE_INT); $table->create(); $pm = new SwooleTest\ProcessManager; $pm->magic_code = rand(10000000, 90000000); $pm->parentFunc = function ($pid) use ($pm, $table) { run(function () use ($pm, $table) { $n = 200; while ($n--) { go(function () use ($pm, $table) { $client = new Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = array( 'name' => __FILE__, 'sid' => $pm->magic_code, 'content' => str_repeat('A', 8192 * rand(1, 3)), ); $_serialize_data = serialize($data) . "\r\n\r\n"; $client->send($_serialize_data); Assert::eq($client->recv(), "SUCCESS\n"); }); } }); $pm->kill(); $array = array_column(iterator_to_array($table), 'count'); $standard_deviation = sqrt(swoole_get_variance(swoole_get_average($array), $array)); Assert::greaterThan($standard_deviation, 1); Assert::lessThan($standard_deviation, 5); echo 'DONE' . PHP_EOL; }; $pm->childFunc = function () use ($pm, $table) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => SWOOLE_DISPATCH_CO_CONN_LB, 'package_max_length' => 1024 * 1024 * 2, "worker_num" => 4, 'log_file' => '/dev/null', "reload_async" => true, )); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id == 0) { $pm->wakeup(); } }); $serv->on('connect', function (Server $serv, $fd, $rid) use ($table) { $table->incr($serv->getWorkerId(), 'count'); if (rand(1000, 9999) % 4 == 0) { System::sleep(0.5); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm) { Assert::eq($serv->getClientInfo($fd)['worker_id'], $serv->getWorkerId()); $_data = unserialize(rtrim($data)); if ($_data and is_array($_data) and $_data['sid'] == $pm->magic_code) { $serv->send($fd, "SUCCESS\n"); } else { $serv->send($fd, "ERROR\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/dispatch_mode_9.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 9 [co req lb] --SKIPIF-- --FILE-- column('count', Table::TYPE_INT); $table->create(); const N = 1024; const EOF = "\r\n\r\n"; $pm = new SwooleTest\ProcessManager; $pm->magic_code = rand(10000000, 90000000); $pm->parentFunc = function ($pid) use ($pm, $table) { run(function () use ($pm, $table) { $client = new Client(SWOOLE_SOCK_TCP); $client->set(array( 'package_eof' => EOF, 'open_eof_check' => true, 'open_eof_split' => true, )); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $rand = rand(1, 4); $data = array( 'name' => __FILE__, 'sid' => $pm->magic_code, 'content' => str_repeat('A', 1024 * $rand), ); $_serialize_data = serialize($data) . EOF; go(function () use ($client) { $n = N; while ($n--) { Assert::eq($client->recv(), "SUCCESS" . EOF); } $client->close(); }); $n = N; while ($n--) { $client->send($_serialize_data); if ($n % 10 == 1) { System::sleep(0.002); } } }); $pm->kill(); $array = array_column(iterator_to_array($table), 'count'); $standard_deviation = sqrt(swoole_get_variance(swoole_get_average($array), $array)); Assert::greaterThan($standard_deviation, 1); Assert::lessThan($standard_deviation, 5); echo 'DONE' . PHP_EOL; }; $pm->childFunc = function () use ($pm, $table) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => SWOOLE_DISPATCH_CO_REQ_LB, 'package_max_length' => 1024 * 1024 * 2, "worker_num" => 4, 'log_file' => '/dev/null', "reload_async" => true, )); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id == 0) { $pm->wakeup(); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm, $table) { $table->incr($serv->getWorkerId(), 'count'); if (rand(1000, 9999) % 10 == 0) { System::sleep(0.5); } $_data = unserialize(rtrim($data)); if ($_data and is_array($_data) and $_data['sid'] == $pm->magic_code) { $serv->send($fd, "SUCCESS".EOF); } else { $serv->send($fd, "ERROR".EOF); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/duplicate_registered.phpt ================================================ --TEST-- swoole_server: duplicate registered --SKIPIF-- --FILE-- on('start', function () { }); Assert::same(true, !!'load Assert'); $mem = null; for ($n = 1000; $n--;) { $server->on('start', function () { }); if ($mem === null) { $mem = memory_get_usage(); } Assert::same(memory_get_usage(), $mem); } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/enable_coroutine.phpt ================================================ --TEST-- swoole_server: reload with enable_coroutine --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $list = []; go(function () use (&$list, $pm) { for ($i = 2; $i--;) { for ($n = 5; $n--;) { echo 'cid-' . httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/task?n={$n}") . "\n"; } if ($i == 1) { Process::kill(file_get_contents(TEST_PID_FILE), SIGUSR1); $pm->wait(); } } }); Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => TEST_LOG_FILE, 'pid_file' => TEST_PID_FILE, 'worker_num' => 1, ]); $server->on('WorkerStart', function (Server $server, int $worker_id) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Request $request, Response $response) { System::sleep(0.01); $response->end(co::getuid()); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- cid-2 cid-3 cid-4 cid-5 cid-6 cid-2 cid-3 cid-4 cid-5 cid-6 DONE ================================================ FILE: tests/swoole_server/enable_delay_receive.phpt ================================================ --TEST-- swoole_server: enable_delay_receive --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send('world')); $s = microtime(true); Assert::eq($client->recv(), "hello world"); time_approximate(0.3, microtime(true) - $s, 0.5); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'enable_delay_receive' => true, 'log_file' => '/dev/null' ]); $server->on('workerStart', function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('connect', function (Swoole\Server $serv, int $fd, int $rid) { Co::sleep(0.3); $serv->confirm($fd); }); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { $serv->send($fd, "hello {$data}"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/enable_reuse_port.phpt ================================================ --TEST-- swoole_server: enable_reuse_port --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $c = new Swoole\Coroutine\Scheduler(); $workerCounter = []; $c->parallel( N, function () use ($pm, &$workerCounter) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = base64_decode(random_bytes(rand(1024, 8192))) . "\r\n\r\n";; $client->send($data); $data = $client->recv(); Assert::assert($data); $json = json_decode($data); if (!isset($workerCounter[$json->worker])) { $workerCounter[$json->worker] = 0; } $workerCounter[$json->worker]++; } ); $c->start(); $pm->kill(); foreach ($workerCounter as $c) { Assert::greaterThan($c, N / W / 2); } }; $pm->childFunc = function () use ($pm, $count) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set( array( 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'enable_reuse_port' => true, 'package_max_length' => 1024 * 1024 * 2, //2M "worker_num" => W, 'log_file' => '/dev/null', ) ); $serv->on( "workerStart", function (Server $serv) use ($pm, $count) { $count->add(1); if ($count->get() == $serv->setting['worker_num']) { $pm->wakeup(); } } ); $serv->on( 'receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, json_encode(['worker' => $serv->getWorkerId()])); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/eof_protocol.phpt ================================================ --TEST-- swoole_server: (eof protocol) recv 100k packet --SKIPIF-- --FILE-- self::$PKG_NUM) { echo "invalid index #{$pkg['index']}\n"; } $this->index[$pkg['index']] = true; } function onWorkerStart($serv, $wid) { global $pm; $pm->wakeup(); } } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 2.0)) { exit("connect failed\n"); } $bytes = 0; $pkg_bytes = 0; for ($i = 0; $i < TestServer::$PKG_NUM; $i++) { $len = rand(1000, 1024 * 128 - 8); $sid = rand(10000, 99999); $array['index'] = $i; $array['sid'] = $sid; $array['len'] = $len; $array['data'] = str_repeat('A', $len); $pkt = serialize($array) . "\r\n\r\n"; $pkg_bytes += strlen($pkt); // if ($i % 1000 == 0 or $i > 99000) // { // echo "#{$i} send package. sid={$sid}, length=" . ($len + 10) . ", total bytes={$pkg_bytes}\n"; // } if (!$client->send($pkt)) { break; } $bytes += strlen($pkt); } $recv = $client->recv(); echo $recv; // echo "send ".MAX_PACKET_NUM." packet sucess, send $bytes bytes\n"; $client->close(); usleep(1); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new EofServer($pm->getFreePort(), true); $serv->set([ // 'log_file' => '/dev/null', 'enable_coroutine' => false, 'package_eof' => "\r\n\r\n", 'open_eof_split' => true, 'worker_num' => 1, 'package_max_length' => 1024 * 1024 * 2, ]); $serv->start(); }; $pm->childFirst(); //$pm->runParentFunc(); $pm->run(); ?> --EXPECTREGEX-- end Total count=\d+, bytes=\d+ ================================================ FILE: tests/swoole_server/eof_server.phpt ================================================ --TEST-- swoole_server: eof server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { echo 'Over flow. errno=' . $client->errCode; exit("\n"); } $data = [ 'name' => __FILE__, 'sid' => 1000236, 'content' => str_repeat('A', 8192 * rand(1, 3)), ]; $_serialize_data = serialize($data) . "\r\n\r\n"; $chunk_size = 2048; $len = strlen($_serialize_data); $chunk_num = intval($len / $chunk_size) + 1; for ($i = 0; $i < $chunk_num; $i++) { if ($len < ($i + 1) * $chunk_size) { $sendn = $len - ($i * $chunk_size); } else { $sendn = $chunk_size; } $client->send(substr($_serialize_data, $i * $chunk_size, $sendn)); usleep(10000); } echo $client->recv(); Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => 3, 'package_max_length' => 1024 * 1024 * 2, // 2M 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $_data = unserialize(rtrim($data)); if ($_data and is_array($_data) and $_data['sid'] == 1000236) { $serv->send($fd, 'SUCCESS'); } else { $serv->send($fd, 'ERROR'); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/event/before_shutdown.phpt ================================================ --TEST-- swoole_server/event: onBeforeShutdown --SKIPIF-- --FILE-- setLogFile(FILE); $pm->parentFunc = function ($pid) use ($pm) { }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on("start", function (Server $serv) use ($pm) { $pm->writeLog('master start'); Event::add(STDIN, function ($fp) { echo fread($fp, 8192); }); $serv->shutdown(); }); $serv->on("BeforeShutdown", function (Server $serv) use ($pm) { $pm->writeLog('before master shutdown'); Event::del(STDIN); }); $serv->on("shutdown", function (Server $serv) use ($pm) { $pm->writeLog('master shutdown'); $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); echo file_get_contents(FILE); unlink(FILE); ?> --EXPECT-- master start before master shutdown master shutdown ================================================ FILE: tests/swoole_server/event/manager_start.phpt ================================================ --TEST-- swoole_server/event: onManagerStart --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { echo file_get_contents(FILE); $pm->kill(); unlink(FILE); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("ManagerStart", function (Server $serv) use ($pm) { file_put_contents(FILE, 'manager start'.PHP_EOL); $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- manager start ================================================ FILE: tests/swoole_server/event/manager_stop.phpt ================================================ --TEST-- swoole_server/event: onManagerStop --SKIPIF-- --FILE-- setWaitTimeout(5); const FILE = __DIR__ . '/tmp_result.txt'; $atomic = new Atomic(); $pm->parentFunc = function () use ($pm) { echo "done\n"; }; $pm->childFunc = function () use ($pm, $atomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null' ]); $serv->on("start", function (Server $serv) use ($atomic) { if ($atomic->add() == 2) { $serv->shutdown(); } }); $serv->on("ManagerStart", function (Server $serv) use ($atomic) { if ($atomic->add() == 2) { $serv->shutdown(); } }); $serv->on("ManagerStop", function (Server $serv) use ($pm) { file_put_contents(FILE, 'manager stop' . PHP_EOL); $pm->wakeup(); }); $serv->on("Receive", function () { }); $serv->start(); }; $pm->childFirst(); $pm->run(); echo file_get_contents(FILE); unlink(FILE); ?> --EXPECT-- done manager stop ================================================ FILE: tests/swoole_server/event/shutdown.phpt ================================================ --TEST-- swoole_server/event: onShutdown --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("start", function (Server $serv) { $serv->shutdown(); }); $serv->on("shutdown", function (Server $serv) use ($pm) { file_put_contents(FILE, 'master shutdown' . PHP_EOL); $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); echo file_get_contents(FILE); unlink(FILE); ?> --EXPECT-- master shutdown ================================================ FILE: tests/swoole_server/event/start.phpt ================================================ --TEST-- swoole_server/event: onStart --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { echo file_get_contents(FILE); $pm->kill(); unlink(FILE); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("start", function (Server $serv) use ($pm) { file_put_contents(FILE, 'master start'.PHP_EOL); $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- master start ================================================ FILE: tests/swoole_server/event/worker_exit.phpt ================================================ --TEST-- swoole_server/event: onWorkerExit --SKIPIF-- --FILE-- setWaitTimeout(5); const FILE = __DIR__ . '/tmp_result.txt'; $atomic = new Atomic(); $pm->setWaitTimeout(5); $pm->setLogFile(FILE); $pm->parentFunc = function () use ($pm) { usleep(10000); Process::kill($pm->getChildPid(), SIGUSR1); echo "done\n"; }; $pm->childFunc = function () use ($pm, $atomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('start', function (Server $serv) use ($atomic, $pm) { $pm->writeLog('master start'); }); $serv->on(Constant::EVENT_MANAGER_START, function (Server $serv) use ($atomic, $pm) { usleep(10000); $pm->writeLog('manager start'); }); $serv->on(Constant::EVENT_WORKER_START, function (Server $serv) use ($atomic, $pm) { if ($atomic->get() == 0) { usleep(20000); } $pm->writeLog('worker start, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus()); if ($atomic->add() == 2) { usleep(10000); $serv->shutdown(); } else { $GLOBALS['timer'] = Timer::tick(100, function () use ($serv, $pm) { $pm->writeLog( 'tick, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus() ); $pm->wakeup(); }); } }); $serv->on(Constant::EVENT_WORKER_EXIT, function (Server $serv) use ($atomic, $pm) { $pm->writeLog( 'worker exit, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus() ); Timer::clear($GLOBALS['timer']); }); $serv->on(Constant::EVENT_WORKER_STOP, function (Server $serv) use ($pm) { $pm->writeLog('worker stop'); }); $serv->on('Receive', function () {}); $serv->start(); }; $pm->childFirst(); $pm->run(); echo file_get_contents(FILE); unlink(FILE); ?> --EXPECT-- done master start manager start worker start, id=0, status=2 tick, id=0, status=2 worker exit, id=0, status=3 worker stop worker start, id=0, status=2 worker stop ================================================ FILE: tests/swoole_server/exist.phpt ================================================ --TEST-- swoole_server: exist --SKIPIF-- --FILE-- send(opcode_encode("exist", [2])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::true($data); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/force_reload.phpt ================================================ --TEST-- swoole_server: force reload in process mode --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $n = WORKER_NUM; $clients = []; while ($n--) { $client = new Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send('hello world'); $clients[] = $client; } switch_process(); // reload Process::kill($pid, SIGUSR1); sleep(3); $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => WORKER_NUM, 'max_wait_time' => 1, 'enable_coroutine' => false, ]); $server->on('workerStart', function (Server $server, $worker_id) use ($pm, $atomic) { $atomic->add(1); if ($atomic->get() === WORKER_NUM) { $pm->wakeup(); } }); $server->on('receive', function ($serv, $fd, $tid, $data) { while (true) { sleep(100); } }); $server->start(); }; $pm->childFirst(); $pm->run(); Assert::eq($atomic->get(), WORKER_NUM * 2); ?> --EXPECTF-- [%s] INFO Server is reloading all workers now [%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) [%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) [%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) [%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) [%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 [%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 [%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 [%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 ================================================ FILE: tests/swoole_server/force_reload2.phpt ================================================ --TEST-- swoole_server: force reload in base mode --SKIPIF-- --FILE-- setWaitTimeout(1000); $pm->parentFunc = function ($pid) use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 2, 'max_wait_time' => 1, 'enable_coroutine' => false, ]); $serv->on('WorkerStart', function (Server $server, $worker_id) use ($pm, $atomic) { echo "{$worker_id} [" . $server->worker_pid . "] start\n"; if ($worker_id == 0) { if ($atomic->get() == 1) { while (true) { sleep(10); } } else { $pm->wakeup(); } } if ($worker_id == 1 and $atomic->get() == 1) { Timer::after(1, function () use ($server, $worker_id, $atomic) { $atomic->add(1); echo "{$worker_id} [" . $server->worker_pid . "] reload\n"; $server->reload(); }); } }); $serv->on('WorkerStop', function (Server $server, $worker_id) use ($pm, $atomic) { echo "{$worker_id} [" . $server->worker_pid . "] stop\n"; }); $serv->on('receive', function ($serv, $fd, $tid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %d [%d] start %d [%d] start %d [%d] reload [%s] INFO Server is reloading all workers now %d [%d] stop %d [%d] start [%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) [%s] WARNING Worker::report_error(): worker(pid=%d, id=0) abnormal exit, status=0, signal=9 %d [%d] start %d [%d] stop %d [%d] stop ================================================ FILE: tests/swoole_server/force_reload3.phpt ================================================ --TEST-- swoole_server: force reload --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm,$argv) { sleep(2); $script_name = $argv[0]; $ret = shell_exec("ps aux | grep $script_name | grep -v 'grep'"); Assert::assert($ret != ""); $pm->kill(); }; $pm->childFunc = function () use ($pm,$atomic) { $flag = 0; $flag1 = 0; $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'log_file' => TEST_LOG_FILE, "worker_num" => 2, "task_worker_num" => 1, "max_wait_time" => 1 ]); $serv->on("WorkerStart", function (Server $server, $worker_id) use ($pm, $atomic) { $pm->wakeup(); Timer::after(50,function() use ($server, $worker_id, $atomic){ if ($atomic->get() == 1) { $atomic->add(1); $server->reload(); } }); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { }); $serv->on('task', function ($serv,$task_id, $reactor_id, $params) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/force_reload4.phpt ================================================ --TEST-- swoole_server: force reload (timer) --SKIPIF-- --FILE-- setWaitTimeout(30); $pm->parentFunc = function () use ($pm) { echo "OK\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'reload_async' => true, 'task_enable_coroutine' => true, 'max_wait_time' => 2, ]); $server->on("shutdown", function () use ($pm) { $pm->wakeup(); }); $server->on('workerStart', function (Swoole\Server $server, int $wid) use ($pm) { if ($wid === 0) { Timer::tick(5000, function () { echo 'tick'; }); Timer::after(500, function () use ($server) { $server->shutdown(); }); } }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] WARNING %s (ERRNO 9101): worker exit timeout, forced termination OK ================================================ FILE: tests/swoole_server/getCallback.phpt ================================================ --TEST-- swoole_server: getCallback --SKIPIF-- --FILE-- on('start', function ($server) { }); $server->on('shutdown', function ($server) { }); Assert::isInstanceOf($server->getCallback('start'), Closure::class); Assert::assert(is_callable($server->getCallback('start'))); $cb = $server->getCallback('start'); Assert::same($cb, $server->getCallback('start')); Assert::same($server->getCallback('Receive'), null); $server->on('receive', function () { }); Assert::isInstanceOf($server->getCallback('receive'), Closure::class); echo "DONE\n" ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/getClientInfo.phpt ================================================ --TEST-- swoole_server: get client info --SKIPIF-- --FILE-- send(opcode_encode("getClientInfo", [2])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::assert(is_array($data) && $data); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/getClientInfo_in_callback_of_close.phpt ================================================ --TEST-- swoole_server: getClientInfo in callback of close event --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("close"); $data = $client->recv(); Assert::eq($data, ""); echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, ]); $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->close($fd); }); $serv->on('close', function (Server $serv, $fd, $wid) { $info = $serv->getClientInfo($fd); Assert::isArray($info); Assert::eq($info['close_errno'], 0); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/getClientInfo_in_callback_of_close_2.phpt ================================================ --TEST-- swoole_server: getClientInfo in callback of close event [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->close(); echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, ]); $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $serv->close($fd); }); $serv->on('close', function (Server $serv, $fd, $wid) { $info = $serv->getClientInfo($fd); Assert::isArray($info); Assert::eq($info['close_errno'], 0); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/getClientList.phpt ================================================ --TEST-- swoole_server: get client list --SKIPIF-- --FILE-- send(opcode_encode("getClientList", [])); Assert::assert($r !== false); }, function(Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::assert(is_array($data) && count($data) === 1); echo "SUCCESS\n"; $cli->close(); Timer::clear($timer); }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/getLastError.phpt ================================================ --TEST-- swoole_server: get last error --SKIPIF-- --FILE-- send(opcode_encode("getLastError", [])); Assert::assert($r !== false); }, function (Client $cli, $recv) use($timer) { list($op, $data) = opcode_decode($recv); Assert::same($data, 0); $cli->close(); Swoole\Timer::clear($timer); echo "SUCCESS\n"; }); Swoole\Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/getSocket.phpt ================================================ --TEST-- swoole_server: getSocket --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); Assert::assert($r); $cli->send("test"); $data = $cli->recv(); Assert::same($data, 'Socket'); $cli->send('shutdown'); $cli->close(); echo "SUCCESS\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); $socket = $serv->getSocket(); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) use ($socket) { if (trim($data) == 'shutdown') { $serv->shutdown(); return; } else { if (PHP_VERSION_ID > 80000) { $serv->send($fd, get_class($socket)); } else { $serv->send($fd, get_resource_type($socket)); } } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/getWorkerStatus.phpt ================================================ --TEST-- swoole_server: getWorkerStatus --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send('world')); $data = $client->recv(); Assert::assert($data); $json = json_decode($data); Assert::assert($json); Assert::eq($json->current_worker, SWOOLE_WORKER_BUSY); Assert::eq($json->another_worker, SWOOLE_WORKER_IDLE); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 2, 'log_file' => '/dev/null' ]); $server->on('workerStart', function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { $serv->send($fd, json_encode([ 'current_worker' => $serv->getWorkerStatus(), 'another_worker' => $serv->getWorkerStatus(1-$serv->getWorkerId()), ])); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/heartbeat.phpt ================================================ --TEST-- swoole_server: heart beat false --SKIPIF-- --FILE-- send(opcode_encode("heartbeat", [false])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/heartbeat_true.phpt ================================================ --TEST-- swoole_server: heart beat (true) --SKIPIF-- --FILE-- send(opcode_encode("heartbeat", [true])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/heartbeat_with_base.phpt ================================================ --TEST-- swoole_server: heart beat with SWOOLE_BASE --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $s1 = time(); Assert::same($client->recv(), ''); $s2 = time(); Assert::assert($s2 - $s1 > 1); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'heartbeat_check_interval' => 1, 'heartbeat_idle_time' => 2, 'worker_num' => 1, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/http_protocol.phpt ================================================ --TEST-- swoole_server: http request & response --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $httpClient = new Client(HTTP_SERVER_HOST, $pm->getFreePort(), false); $httpClient->setMethod("POST"); $httpClient->setData("HELLO"); $ok = $httpClient->execute("/rawcookie?hello=world&value=1"); Assert::assert($ok); Assert::same($httpClient->statusCode, 200); Assert::same($httpClient->errCode, 0); Assert::eq($httpClient->getHeaders()['x-server'], 'swoole'); Assert::same($httpClient->getBody(), GREETER); echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_http_protocol' => true, ]); $serv->on("Start", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { $req = Request::create(); Assert::eq($req->parse($data), strlen($data)); $resp = Response::create([$serv, $req], $fd); $resp->header('X-Server', 'swoole'); $resp->end(GREETER); Assert::eq($resp->fd, $fd); Assert::eq($req->fd, $fd); }); $serv->on(Constant::EVENT_CLOSE, function (Server $serv, $fd, $reactor_id) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/idle_worekr_num.phpt ================================================ --TEST-- swoole_server: idle_worker_num --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); $data = $client->recv(); Assert::assert($data); $json = json_decode($data); Assert::eq($json->idle_worker_num, 2); $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ 'worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'enable_coroutine' => false, ]); $serv->on("workerStart", function (Server $serv, $wid) use ($pm, $counter) { if ($counter->add(1) == $serv->setting['worker_num']) { Swoole\Event::defer(function () use ($pm) { $pm->wakeup(); }); } }); $serv->on('receive', function (Server $serv, $fd, $tid, $data) { usleep(10000); $serv->send($fd, json_encode($serv->stats())); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/invalid_fd.phpt ================================================ --TEST-- swoole_server: invalid fd --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::notEmpty($client->send("TEST" . EOF)); Assert::notEmpty($client->recv()); switch_process(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_eof_split' => true, 'package_eof' => EOF, ]); $server->on('workerStart', function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { Assert::false($serv->send(null, "hello {$fd}")); Assert::false($serv->send(-1, "hello {$fd}")); Assert::false($serv->send(100, "hello {$fd}")); Assert::false($serv->send(PHP_INT_MAX, "hello {$fd}")); Assert::true($serv->send($fd, "DONE\n")); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Server::send(): fd can not be null in %s/tests/swoole_server/invalid_fd.php on line %d Warning: Swoole\Server::send(): invalid fd[-1] in %s/tests/swoole_server/invalid_fd.php on line %d ================================================ FILE: tests/swoole_server/invalid_option.phpt ================================================ --TEST-- swoole_server: invalid option --SKIPIF-- --FILE-- 1, 'backlog' => 128, 'invalid_option' => true, ]; try { $serv->set($options); } catch (\Swoole\Exception $e) { echo $e->getMessage(); } ?> --EXPECTF-- Warning: unsupported option [invalid_option] in @swoole/library/core/Server/Helper.php on line %d %A %A %A ================================================ FILE: tests/swoole_server/kill_user_process_01.phpt ================================================ --TEST-- swoole_server: kill user process [SWOOLE_PROCESS] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 1; $i++) { //杀死进程 kill_process_by_name(WORKER_PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(WORKER_PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); // $serv->set( // ['log_file' => TEST_LOG_FILE,] // ); $process2 = new Swoole\Process(function ($worker) use ($serv, $pm) { global $argv; swoole_set_process_name(WORKER_PROC_NAME); Swoole\Process::signal(SIGTERM, function () { Swoole\Event::exit(); }); Swoole\Timer::after(200000, function ($interval) use ($worker, $serv) { echo "OK\n"; }); $pm->wakeup(); }, false); $serv->addProcess($process2); $serv->set(["worker_num" => 2, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/kill_user_process_02.phpt ================================================ --TEST-- swoole_server: kill user process [SWOOLE_BASE] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 1; $i++) { //杀死进程 kill_process_by_name(WORKER_PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(WORKER_PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); // $serv->set( // ['log_file' => TEST_LOG_FILE,] // ); $process2 = new Swoole\Process(function ($worker) use ($serv, $pm) { global $argv; swoole_set_process_name(WORKER_PROC_NAME); Swoole\Process::signal(SIGTERM, function () { Swoole\Event::exit(); }); Swoole\Timer::after(200000, function ($interval) use ($worker, $serv) { echo "OK\n"; }); $pm->wakeup(); }, false); $serv->addProcess($process2); $serv->set(["worker_num" => 2, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/kill_worker_01.phpt ================================================ --TEST-- swoole_server: kill worker [SWOOLE_BASE] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { //杀死进程 kill_process_by_name(WORKER_PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(WORKER_PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(["worker_num" => 2, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id == 0) { swoole_set_process_name(WORKER_PROC_NAME); $pm->wakeup(); } }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/kill_worker_02.phpt ================================================ --TEST-- swoole_server: kill worker [SWOOLE_PROCESS] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { //杀死进程 kill_process_by_name(WORKER_PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(WORKER_PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(["worker_num" => 2, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id == 0) { swoole_set_process_name(WORKER_PROC_NAME); $pm->wakeup(); } }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/last_time.phpt ================================================ --TEST-- swoole_server: last time --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); Assert::assert($r); $cli->send("test1\r\ntest2\r\n"); $result = [json_decode($cli->recv(), true), json_decode($cli->recv(), true)]; Assert::lessThanEq($result[0]['php_time'] - $result[0]['last_dispatch_time'], 0.01); Assert::greaterThanEq($result[1]['php_time'] - $result[1]['last_dispatch_time'], 0.3); $cli->close(); echo "SUCCESS\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort()); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_eof_split' => true, 'package_eof' => "\r\n", ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) { $info = $serv->getClientInfo($fd); $info ['php_time'] = microtime(true); $serv->send($fd, json_encode($info)); if (trim($data) == 'test1') { usleep(300000); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/length/00.phpt ================================================ --TEST-- swoole_server/length: (length protocol) recv 100k packet --SKIPIF-- --FILE-- recv_bytes}\n"; } if ($header['index'] > self::$PKG_NUM) { echo "invalid index #{$header['index']}\n"; } $this->index[$header['index']] = true; } function onWorkerStart($serv, $wid) { global $pm; $pm->wakeup(); } } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $bytes = 0; $pkg_bytes = 0; for ($i = 0; $i < TestServer::$PKG_NUM; $i++) { $len = rand(1000, 1024 * 128 - 8); $sid = rand(10000, 99999); $pkt = pack('NNN', $len + 8, $i, $sid); $pkt .= str_repeat('A', $len); $pkg_bytes += strlen($pkt); // if ($i % 1000 == 0) // { // echo "#{$i} send package. sid={$sid}, length=" . ($len + 10) . ", total bytes={$pkg_bytes}\n"; // } if (!$client->send($pkt)) { break; } $bytes += strlen($pkt); } $recv = $client->recv(); echo $recv; //echo "send ".TestServer::$PKG_NUM." packet sucess, send $bytes bytes\n"; $client->close(); usleep(1); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new PkgServer($pm->getFreePort(), true); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'task_worker_num' => 0 ]); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTREGEX-- end Total count=\d+, bytes=\d+ ================================================ FILE: tests/swoole_server/length/01.phpt ================================================ --TEST-- swoole_server/length: big packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port, $pm, $size) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect(TCP_SERVER_HOST, $port, 1); $data = str_repeat('A', $size); $cli->send(pack('N', strlen($data)) . $data); $recv_data = $cli->recv(); echo $recv_data; $pm->kill(); }; $pm->childFunc = function () use ($pm, $port, $size) { $serv = new Server(TCP_SERVER_HOST, $port, SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 16 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("receive", function ($serv, $fd, $rid, $data) use ($size) { Assert::assert(strlen($data) == $size + 4); $serv->send($fd, "OK\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/length/02.phpt ================================================ --TEST-- swoole_server/length: (length protocol) no body --SKIPIF-- --FILE-- recv_bytes}\n"; } if ($index > self::$PKG_NUM) { echo "invalid index #{$index}\n"; } $this->index[$index] = true; $index++; } function onWorkerStart($serv, $wid) { global $pm; $pm->wakeup(); } } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $bytes = 0; $pkg_bytes = 0; for ($i = 0; $i < TestServer::$PKG_NUM; $i++) { // if ($i % 1000 == 0) // { // echo "#{$i} send package. sid={$sid}, length=" . ($len + 10) . ", total bytes={$pkg_bytes}\n"; // } if (!$client->send(pack('n', 2))) { break; } $bytes += 2; } $recv = $client->recv(); echo $recv; //echo "send ".TestServer::$PKG_NUM." packet sucess, send $bytes bytes\n"; $client->close(); usleep(1); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new PkgServer_2($pm->getFreePort(), true); $serv->set([ 'worker_num' => 1, //'dispatch_mode' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 1024 * 1024, 'package_length_type' => 'n', 'package_length_offset' => 0, 'package_body_offset' => 0, 'task_worker_num' => 0 ]); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTREGEX-- end Total count=\d+, bytes=\d+ ================================================ FILE: tests/swoole_server/length/03.phpt ================================================ --TEST-- swoole_server/length: 8M packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port, $pm, $size, $_g_data) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect(TCP_SERVER_HOST, $port, 1); $cli->send(pack('N', strlen($_g_data)) . $_g_data); $recv_data = $cli->recv(); echo $recv_data; $pm->kill(); }; $pm->childFunc = function () use ($pm, $port, $size, $_g_data) { $serv = new Server(TCP_SERVER_HOST, $port, SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 16 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("receive", function ($serv, $fd, $rid, $data) use ($size, $_g_data) { Assert::eq(strlen($data), $size + 4); Assert::eq($_g_data, substr($data, 4)); $serv->send($fd, "OK\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/length/length_func.phpt ================================================ --TEST-- swoole_server/length: package_length_func --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); $data = str_repeat('A', LEN * COUNT); $cli->send($data); $data = ''; while (1) { $recv_data = $cli->recv(); if (!$recv_data) { break; } $data .= $recv_data; if (strlen($data) >= LEN * COUNT) { break; } } Assert::eq($data, str_repeat("OK\n", 12)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_body_offset' => 4, 'package_length_func' => function(){ return 3; } ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("receive", function ($serv, $fd, $rid, $data) { Assert::assert(strlen($data) == LEN); $serv->send($fd, "OK\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/listen_fail.phpt ================================================ --TEST-- swoole_server: listen fail --SKIPIF-- --FILE-- SWOOLE_LOG_NONE]); try { $serv = new Swoole\Server('192.0.0.1', 80, SWOOLE_PROCESS); } catch (Swoole\Exception $e) { Assert::same($e->getCode(), SOCKET_EADDRNOTAVAIL); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/max_idle_time_1.phpt ================================================ --TEST-- swoole_server: max_idle_time --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $time1, $time2) { $client = new Client(SWOOLE_SOCK_UNIX_STREAM, SWOOLE_SOCK_SYNC); if (!$client->connect(SOCK_FILE, 0, 0.5)) { exit("connect failed\n"); } $socket = $client->getSocket(); socket_set_option($socket, SOL_SOCKET, SO_SNDBUF, 65536); socket_set_option($socket, SOL_SOCKET, SO_RCVBUF, 65536); $client->send('hello world'); $s = microtime(true); sleep(1); usleep(200000); Assert::greaterThanEq($time2->get() - $time1->get(), 1000); $result = ''; while(true) { $data = $client->recv(); if (empty($data)) { break; } $result .= $data; } Assert::greaterThanEq(strlen($result), 8192); $pm->kill(); }; $pm->childFunc = function () use ($pm, $time1, $time2) { $serv = new Server(SOCK_FILE, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_STREAM); $serv->set([ 'worker_num' => 1, 'kernel_socket_send_buffer_size' => 65536, 'log_file' => '/dev/null', 'max_idle_time' => 1, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('Receive', function ($serv, $fd, $tid, $data) use ($time1) { $time1->set(microtime(true) * 1000); $serv->send($fd, str_repeat('A', 1024 * 1024 * 4)); }); $serv->on('close', function ($serv, $fd, $tid) use ($time2) { $time2->set(microtime(true) * 1000); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/max_idle_time_2.phpt ================================================ --TEST-- swoole_server: max_idle_time [recv] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $time1, $time2) { $client = new Client(SWOOLE_SOCK_UNIX_STREAM, SWOOLE_SOCK_SYNC); if (!$client->connect(SOCK_FILE, 0, 0.5)) { exit("connect failed\n"); } sleep(1); usleep(200000); Assert::greaterThanEq($time2->get() - $time1->get(), 1000); $data = $client->recv(); Assert::isEmpty($data); $pm->kill(); }; $pm->childFunc = function () use ($pm, $time1, $time2) { $serv = new Server(SOCK_FILE, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_STREAM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'max_idle_time' => 1, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_CONNECT, function ($serv, $fd, $tid) use ($time1) { $time1->set(microtime(true) * 1000); }); $serv->on('Receive', function ($serv, $fd, $tid, $data) use ($time1) { $serv->send($fd, str_repeat('A', 1024 * 1024)); }); $serv->on(Constant::EVENT_CLOSE, function ($serv, $fd, $tid) use ($time2) { $time2->set(microtime(true) * 1000); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/max_queued_bytes.phpt ================================================ --TEST-- swoole_server: max_queued_bytes --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { echo "FAILED\n"; $pm->kill(); return; } $bytes = 0; while ($bytes < N) { $write_n = $client->send(random_bytes(rand(1000, 80000))); if ($write_n == false) { break; } else { $bytes += $write_n; phpt_echo("Client sent {$bytes} bytes\n"); } } Assert::assert($bytes > N); $pm->wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'max_queued_bytes' => 1024 * 1024, ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('receive', static function ($serv, $fd, $reactor_id, $data) use ($pm) { static $bytes; $bytes += strlen($data); phpt_echo("Server received {$bytes} bytes\n"); usleep(1000); if ($bytes > N) { $pm->wakeup(); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/max_request.phpt ================================================ --TEST-- swoole_server: max_request --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set([ "open_eof_check" => true, "open_eof_split" => true, "package_eof" => "\r\n\r\n", ]); $r = $client->connect('127.0.0.1', $pm->getFreePort(), -1); if ($r === false) { echo "ERROR"; exit; } for ($i = 0; $i < 4000; $i++) { $data = "PKG-$i" . str_repeat('A', rand(100, 20000)) . "\r\n\r\n"; if ($client->send($data) === false) { echo "send error\n"; break; } $ret = $client->recv(); Assert::same(strlen($ret), strlen($data) + 8); } $client->close(); }); Event::wait(); global $counter; Assert::assert($counter->get() > 10); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 4, 'dispatch_mode' => 1, "open_eof_split" => true, "package_eof" => "\r\n\r\n", 'max_request' => 200, 'log_file' => '/dev/null', ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { global $counter; $counter->add(1); $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { $serv->send($fd, "Server: $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/max_request_grace_disabled.phpt ================================================ --TEST-- swoole_server: max_request_grace disabled --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_eof_check' => true, 'package_eof' => "\n", ]); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort(), -1)); for ($i = 0; $i < 48; $i++) { $client->send("request $i\n"); echo $client->recv(); } $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'dispatch_mode' => 1, 'max_request' => 12, 'max_request_grace' => 0, 'open_eof_check' => true, 'package_eof' => "\n", 'log_file' => '/dev/null', ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $count = 0; $serv->on('receive', function (Swoole\Server $serv, $fd, $reactorId, $data) use (&$count) { $count++; $serv->send($fd, "Worker $serv->worker_id served $count request(s) since start\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Worker 0 served 1 request(s) since start Worker 1 served 1 request(s) since start Worker 0 served 2 request(s) since start Worker 1 served 2 request(s) since start Worker 0 served 3 request(s) since start Worker 1 served 3 request(s) since start Worker 0 served 4 request(s) since start Worker 1 served 4 request(s) since start Worker 0 served 5 request(s) since start Worker 1 served 5 request(s) since start Worker 0 served 6 request(s) since start Worker 1 served 6 request(s) since start Worker 0 served 7 request(s) since start Worker 1 served 7 request(s) since start Worker 0 served 8 request(s) since start Worker 1 served 8 request(s) since start Worker 0 served 9 request(s) since start Worker 1 served 9 request(s) since start Worker 0 served 10 request(s) since start Worker 1 served 10 request(s) since start Worker 0 served 11 request(s) since start Worker 1 served 11 request(s) since start Worker 0 served 12 request(s) since start Worker 1 served 12 request(s) since start Worker 0 served 1 request(s) since start Worker 1 served 1 request(s) since start Worker 0 served 2 request(s) since start Worker 1 served 2 request(s) since start Worker 0 served 3 request(s) since start Worker 1 served 3 request(s) since start Worker 0 served 4 request(s) since start Worker 1 served 4 request(s) since start Worker 0 served 5 request(s) since start Worker 1 served 5 request(s) since start Worker 0 served 6 request(s) since start Worker 1 served 6 request(s) since start Worker 0 served 7 request(s) since start Worker 1 served 7 request(s) since start Worker 0 served 8 request(s) since start Worker 1 served 8 request(s) since start Worker 0 served 9 request(s) since start Worker 1 served 9 request(s) since start Worker 0 served 10 request(s) since start Worker 1 served 10 request(s) since start Worker 0 served 11 request(s) since start Worker 1 served 11 request(s) since start Worker 0 served 12 request(s) since start Worker 1 served 12 request(s) since start ================================================ FILE: tests/swoole_server/max_request_grace_enabled.phpt ================================================ --TEST-- swoole_server: max_request_grace enabled --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_eof_check' => true, 'package_eof' => "\n", ]); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort(), -1)); for ($i = 0; $i < 16; $i++) { $client->send("request $i\n"); echo $client->recv(); } $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'dispatch_mode' => 1, 'max_request' => 4, 'max_request_grace' => 1000000000, 'open_eof_check' => true, 'package_eof' => "\n", 'log_file' => '/dev/null', ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $count = 0; $serv->on('receive', function (Swoole\Server $serv, $fd, $reactorId, $data) use (&$count) { $count++; $serv->send($fd, "Worker $serv->worker_id served $count request(s) since start\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Worker 0 served 1 request(s) since start Worker 1 served 1 request(s) since start Worker 0 served 2 request(s) since start Worker 1 served 2 request(s) since start Worker 0 served 3 request(s) since start Worker 1 served 3 request(s) since start Worker 0 served 4 request(s) since start Worker 1 served 4 request(s) since start Worker 0 served 5 request(s) since start Worker 1 served 5 request(s) since start Worker 0 served 6 request(s) since start Worker 1 served 6 request(s) since start Worker 0 served 7 request(s) since start Worker 1 served 7 request(s) since start Worker 0 served 8 request(s) since start Worker 1 served 8 request(s) since start ================================================ FILE: tests/swoole_server/max_request_threshold.phpt ================================================ --TEST-- swoole_server: max_request threshold --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_eof_check' => true, 'package_eof' => "\n", ]); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort(), -1)); for ($i = 0; $i < 40; $i++) { $client->send("request $i\n"); echo $client->recv(); } $client->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'dispatch_mode' => 1, 'max_request' => 10, 'open_eof_check' => true, 'package_eof' => "\n", 'log_file' => '/dev/null', ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $count = 0; $serv->on('receive', function (Swoole\Server $serv, $fd, $reactorId, $data) use (&$count) { $count++; $serv->send($fd, "Worker $serv->worker_id served $count request(s) since start\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Worker 0 served 1 request(s) since start Worker 1 served 1 request(s) since start Worker 0 served 2 request(s) since start Worker 1 served 2 request(s) since start Worker 0 served 3 request(s) since start Worker 1 served 3 request(s) since start Worker 0 served 4 request(s) since start Worker 1 served 4 request(s) since start Worker 0 served 5 request(s) since start Worker 1 served 5 request(s) since start Worker 0 served 6 request(s) since start Worker 1 served 6 request(s) since start Worker 0 served 7 request(s) since start Worker 1 served 7 request(s) since start Worker 0 served 8 request(s) since start Worker 1 served 8 request(s) since start Worker 0 served 9 request(s) since start Worker 1 served 9 request(s) since start Worker 0 served 10 request(s) since start Worker 1 served 10 request(s) since start Worker 0 served 1 request(s) since start Worker 1 served 1 request(s) since start Worker 0 served 2 request(s) since start Worker 1 served 2 request(s) since start Worker 0 served 3 request(s) since start Worker 1 served 3 request(s) since start Worker 0 served 4 request(s) since start Worker 1 served 4 request(s) since start Worker 0 served 5 request(s) since start Worker 1 served 5 request(s) since start Worker 0 served 6 request(s) since start Worker 1 served 6 request(s) since start Worker 0 served 7 request(s) since start Worker 1 served 7 request(s) since start Worker 0 served 8 request(s) since start Worker 1 served 8 request(s) since start Worker 0 served 9 request(s) since start Worker 1 served 9 request(s) since start Worker 0 served 10 request(s) since start Worker 1 served 10 request(s) since start ================================================ FILE: tests/swoole_server/memory_leak/length.phpt ================================================ --TEST-- swoole_server/memory_leak: length --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm, $chunks, $total) { $clients = []; for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i, $chunks, &$clients, $total) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $count = 0; foreach ($chunks as $data) { $count += $cli->send($data); usleep(10); } Assert::eq($count, $total); $clients[] = $cli; }); } Swoole\Event::wait(); $pm->wait(); $pm->kill(); }; phpt_var_dump( 'total all: ' . number_format(MAX_CONCURRENCY_MID * $total) . ', n packets: ' . MAX_REQUESTS . ', n clients: ' . MAX_CONCURRENCY_MID . ', total: ' . number_format($total) ); $pm->childFunc = function () use ($pm, $counter1, $total, $counter2) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Server $serv, $fd, $rid) { $GLOBALS['bytes_' . $fd] = 0; $GLOBALS['count_' . $fd] = 0; }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm, $counter1, $total, $counter2) { if ($counter1->get() == 0) { $GLOBALS['memory_usage_1'] = memory_get_usage(); } $counter1->add(strlen($data)); $counter2->add(); $GLOBALS['bytes_' . $fd] += strlen($data); $GLOBALS['count_' . $fd]++; if ($GLOBALS['count_' . $fd] == MAX_REQUESTS) { phpt_var_dump( 'bytes: ' . number_format($counter1->get()) . ', count: ' . $counter2->get() . ', data: ' . strlen($data) . ', client bytes: ' . number_format($GLOBALS['bytes_' . $fd]) . ', client count: ' . $GLOBALS['count_' . $fd] ); } if ($counter1->get() == MAX_CONCURRENCY_MID * $total) { $pm->wakeup(); } }); $serv->on('close', function (Server $serv, $fd, $rid) { }); $serv->on('WorkerStop', function () use ($total, $counter2) { $GLOBALS['memory_usage_2'] = memory_get_usage(); Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); Assert::eq($counter2->get(), MAX_CONCURRENCY_MID * MAX_REQUESTS); echo "DONE\n"; }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/memory_leak/pipe_message.phpt ================================================ --TEST-- swoole_server/memory_leak: task --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm, $chunks) { $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($counter1, $counter2, $pm, $total, $chunks) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(array( 'worker_num' => 2, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv, $wid) use ($pm, $chunks) { $GLOBALS['memory_usage_1'] = memory_get_usage(); foreach ($chunks as $ch) { Assert::greaterThan($serv->sendMessage($ch, 1 - $wid), 0); usleep(10); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $_data) use ($chunks) { }); $serv->on('pipeMessage', function (Server $serv, $wid, $data) use ($counter2, $counter1, $pm, $total, $chunks) { $counter1->add(); $counter2->add(strlen($data)); if ($counter2->get() == $total * 2) { $pm->wakeup(); } }); $serv->on('WorkerStop', function (Server $serv) use ($counter2, $total) { $GLOBALS['memory_usage_2'] = memory_get_usage(); Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/memory_leak/task.phpt ================================================ --TEST-- swoole_server/memory_leak: task --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm, $chunks) { go(function () use ($pm, $chunks) { $cli = new Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $cli->send("start\n"); }); Swoole\Event::wait(); $pm->wait(); $pm->kill(); }; $GLOBALS['test_fn'] = function ($taskId, $data, $chunks) { if ($GLOBALS['counter1'] == 0) { $GLOBALS['memory_usage_1'] = memory_get_usage(); } $GLOBALS['counter1']++; $GLOBALS['counter2'] += (strlen($data)); Assert::eq($chunks[$taskId], $data); }; $pm->childFunc = function () use ($pm, $total, $chunks) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(array( 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on('WorkerStart', function (Server $serv, $wid) use ($pm) { if ($wid == 0) { $pm->wakeup(); } $GLOBALS['atomic']->add(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $_data) use ($chunks) { foreach ($chunks as $ch) { Assert::greaterThanEq($serv->task($ch), 0); usleep(100); } }); $serv->on('finish', function (Server $serv, $taskId, $data) use ($pm, $total, $chunks) { $GLOBALS['test_fn']($taskId, $data, $chunks); if ($GLOBALS['counter2'] == $total) { $pm->wakeup(); } }); $serv->on('task', function (Server $serv, $taskId, $srcWorkerId, $data) use ($pm, $total, $chunks) { $GLOBALS['test_fn']($taskId, $data, $chunks); return $data; }); $serv->on('WorkerStop', function (Server $serv) use ($total) { $GLOBALS['memory_usage_2'] = memory_get_usage(); Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); Assert::eq($GLOBALS['counter2'], $total); $GLOBALS['atomic']->add(); echo "DONE\n"; }); $serv->start(); }; $pm->childFirst(); $pm->run(); Assert::eq($GLOBALS['atomic']->get(), 4); ?> --EXPECT-- DONE DONE ================================================ FILE: tests/swoole_server/memory_leak/tcp.phpt ================================================ --TEST-- swoole_server/memory_leak: tcp --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $chunks) { $clients = []; for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i, &$total, $chunks, &$clients) { $cli = new Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } foreach ($chunks as $data) { $cli->send($data); usleep(100); } $clients[] = $cli; }); } Swoole\Event::wait(); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter, $total) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Server $serv, $fd, $rid) { }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm, $counter, $total) { if ($counter->get() == 0) { $GLOBALS['memory_usage_1'] = memory_get_usage(); } if ($counter->add(strlen($data)) == MAX_CONCURRENCY_MID * $total) { $pm->wakeup(); } }); $serv->on('close', function (Server $serv, $fd, $rid) { }); $serv->on('WorkerStop', function () use ($total) { $GLOBALS['memory_usage_2'] = memory_get_usage(); Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); echo "DONE\n"; }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/mqtt/length_offset.phpt ================================================ --TEST-- swoole_server/mqtt: length_offset --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send(Helper::encodePublish([ 'cmd' => 3, 'topic' => 'swoole/mqtt/test', 'content' => '{"name":"swoole", "type":"mqtt", "data":'. str_repeat("swoole", 100) .'}'])) ); echo $client->recv(); $client->close(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_mqtt_protocol' => 1, ]); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { $header = Helper::getHeader($data); Assert::eq($header['type'], 3); Assert::eq(strlen($data), 662); $serv->send($fd, strlen($data)); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 662 ================================================ FILE: tests/swoole_server/mqtt/recv_fail.phpt ================================================ --TEST-- swoole_server/mqtt: recv_fail --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); $client->set(['open_mqtt_protocol' => true]); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); $buffer = Helper::encodePing(12); // PINGREQ // $client->send($buffer); $client->send($buffer[0]); sleep(1); $client->send($buffer[1]); $response = $client->recv(); $header = Helper::getHeader($response); var_dump($header); Assert::eq($header['type'], 13); // PINGRESP $client->close(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_mqtt_protocol' => 1, ]); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { $header = Helper::getHeader($data); Assert::eq($header['type'], 12); $serv->send($fd, Helper::encodePing(13)); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- array(4) { ["type"]=> int(13) ["dup"]=> int(0) ["qos"]=> int(0) ["retain"]=> int(0) } ================================================ FILE: tests/swoole_server/mqtt/send_big_pack.phpt ================================================ --TEST-- swoole_server/mqtt: send_big_pack --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); $client->set(['open_mqtt_protocol' => true, 'package_max_length' => 5 * 1024 *1024]); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send(Helper::encodePublish([ 'cmd' => 3, 'topic' => 'swoole/mqtt/test', 'content' => '{"name":"swoole", "type":"mqtt", "data":'. str_repeat("sw", 2 * 1024 * 1024 ) .'}'])) ); var_dump(Helper::getHeader($client->recv())); $client->close(); $pm->kill(); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'open_mqtt_protocol' => 1, 'package_max_length' => 5 * 1024 *1024 ]); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { $header = Helper::getHeader($data); Assert::eq($header['type'], 3); Assert::eq(strlen($data), 4194368); $serv->send($fd, Helper::encodePing(13)); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- array(4) { ["type"]=> int(13) ["dup"]=> int(0) ["qos"]=> int(0) ["retain"]=> int(0) } ================================================ FILE: tests/swoole_server/new_twice.phpt ================================================ --TEST-- swoole_server: new twice --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $fn = function () use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.5) or die("ERROR"); $cli->send("signal") or die("ERROR"); }; $fn(); $pm->wait(); $fn(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $n = 2; while ($n--) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { echo "WorkerStart\n"; $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->shutdown(); }); $serv->start(); } }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- WorkerStart WorkerStart ================================================ FILE: tests/swoole_server/object/event.phpt ================================================ --TEST-- swoole_server/object: event object --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = base64_encode(random_bytes(rand(1024, 8192))) . "\r\n\r\n";; $client->send($data); $recv_data = $client->recv(); Assert::assert($recv_data); $json = json_decode($recv_data); Assert::eq($json->data, $data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set( array( "worker_num" => 1, 'event_object' => true, 'log_file' => '/dev/null', ) ); $serv->on( 'WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); } ); $serv->on( 'Connect', function (Server $serv, Event $object) { Assert::eq($object->fd, 1); } ); $serv->on( 'Close', function (Server $serv, Event $object) { Assert::eq($object->fd, 1); } ); $serv->on( 'receive', function (Server $serv, Event $object) { $serv->send($object->fd, json_encode(['worker' => $serv->getWorkerId(), 'data' => $object->data])); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/getManagerPid.phpt ================================================ --TEST-- swoole_server/object: getMasterPid --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $json = json_decode(httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/")); Assert::assert($json->result); $pm->kill(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $atomic = new \Swoole\Atomic(0); $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'log_level' => SWOOLE_LOG_ERROR, )); $serv->on(\Swoole\Constant::EVENT_MANAGER_START, function (Swoole\Server $serv) use ($pm, $atomic) { $pm->wakeup(); $atomic->set(posix_getpid()); }); $serv->on('Request', function ($req, $resp) use ($serv, $atomic) { $resp->end(json_encode(['result' => $atomic->get() == $serv->getManagerPid()])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/getMasterPid.phpt ================================================ --TEST-- swoole_server/object: getMasterPid --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $json = json_decode(httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/")); Assert::assert($json->result); $pm->kill(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $atomic = new \Swoole\Atomic(0); $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'log_level' => SWOOLE_LOG_ERROR, )); $serv->on("start", function (Swoole\Server $serv) use ($pm, $atomic) { $pm->wakeup(); $atomic->set(posix_getpid()); }); $serv->on('Request', function ($req, $resp) use ($serv, $atomic) { $resp->end(json_encode(['result' => $atomic->get() == $serv->getMasterPid()])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/getWorkerId.phpt ================================================ --TEST-- swoole_server/object: getWorkerId() --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $json = json_decode(httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/")); Assert::assert($json->result); $pm->kill(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'log_level' => SWOOLE_LOG_ERROR, )); $serv->on("WorkerStart", function (Swoole\Server $serv, $workerId) use ($pm) { $pm->wakeup(); $GLOBALS['pid'] = $workerId; }); $serv->on('Request', function ($req, $resp) use ($serv) { $resp->end(json_encode(['result' => $GLOBALS['pid'] == $serv->getWorkerId()])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/getWorkerPid.phpt ================================================ --TEST-- swoole_server/object: getWorkerPid() --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $json = json_decode(httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/")); Assert::assert($json->result); $pm->kill(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'log_level' => SWOOLE_LOG_ERROR, )); $serv->on("WorkerStart", function (Swoole\Server $serv, $workerId) use ($pm) { $pm->wakeup(); $GLOBALS['pid'] = posix_getpid(); }); $serv->on('Request', function ($req, $resp) use ($serv) { $resp->end(json_encode(['result' => $GLOBALS['pid'] == $serv->getWorkerPid()])); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/packet.phpt ================================================ --TEST-- swoole_server/object: packet object --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $client = new Client(SWOOLE_SOCK_UDP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { echo 'Over flow. errno=' . $client->errCode; exit("\n"); } $data = base64_encode(random_bytes(rand(1024, DGRAM_MAX_SIZE))) . "\r\n\r\n"; $client->send($data); $recv_data = $client->recv(); Assert::eq($recv_data, $data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set( [ 'worker_num' => 1, 'event_object' => true, 'log_file' => '/dev/null', ] ); $serv->on( 'WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); } ); $serv->on( 'packet', function (Server $serv, Packet $object) { $serv->sendto($object->address, $object->port, $object->data, $object->server_socket); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/pipe_message.phpt ================================================ --TEST-- swoole_server/object: pipe message --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $data = base64_encode(random_bytes(rand(1024, 8192))) . "\r\n\r\n";; $client->send($data); $recv_data = $client->recv(); Assert::eq($recv_data, $data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $serv->set( array( "worker_num" => 2, 'event_object' => true, 'log_file' => '/dev/null', ) ); $serv->on( 'WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); } ); $serv->on('pipeMessage', function (Server $serv, PipeMessage $msg) { Assert::eq($msg->worker_id, 1 - $serv->getWorkerId()); Assert::eq($msg->source_worker_id, 1 - $serv->getWorkerId()); $object = $msg->data; $serv->sendto($object->address, $object->port, $object->data, $object->server_socket); }); $serv->on( 'packet', function (Server $serv, Packet $object) { $serv->sendMessage($object, 1 - $serv->getWorkerId()); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/object/status_info.phpt ================================================ --TEST-- swoole_server/object: status info --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or die("ERROR"); $cli->send("task-01") or die("ERROR"); Assert::same($cli->recv(), "hello world"); $cli->close(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'event_object' => true, 'enable_coroutine' => false, ]); $serv->on("ManagerStart", function (Server $serv) use ($pm) { $pm->wakeup(); Timer::after(100, function () use ($serv) { Process::kill($serv->getWorkerPid(0), SIGKILL); }); Timer::after(200, function () use ($serv) { $serv->sendMessage("exit", 0); Timer::after(100, function () use ($serv) { $serv->send(1, 'hello world'); }); }); }); $serv->on(Constant::EVENT_PIPE_MESSAGE, function (Server $serv, $msg) { if ($msg->data == 'exit') { throw new RuntimeException("error"); } }); $serv->on("Receive", function (Server $serv, $event) { }); $serv->on(Constant::EVENT_WORKER_ERROR, function (Server $serv, StatusInfo $info) { static $count = 0; $count++; if ($count == 1) { Assert::eq($info->signal, SIGKILL); Assert::eq($info->exit_code, 0); } elseif ($count == 2) { Assert::eq($info->signal, 0); Assert::eq($info->exit_code, 255); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: Uncaught RuntimeException: error in %s:%d Stack trace: #0 [internal function]: {closure%S}(Object(Swoole\Server), Object(Swoole\Server\PipeMessage)) #1 %s(%d): Swoole\Server->start() #2 [internal function]: {closure%S}() #3 %s(%d): call_user_func(Object(Closure)) #4 %s(%d): SwooleTest\ProcessManager->runChildFunc() #5 [internal function]: SwooleTest\ProcessManager->%s(Object(Swoole\Process)) #6 %s(%d): Swoole\Process->start() #7 %s(%d): SwooleTest\ProcessManager->run() #8 {main} thrown in %s on line %d DONE ================================================ FILE: tests/swoole_server/object/task_result.phpt ================================================ --TEST-- swoole_server/object: task result --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or die("ERROR"); $cli->send("task-01") or die("ERROR"); Assert::same($cli->recv(), "hello world"); $cli->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_level' => SWOOLE_LOG_NOTICE, 'task_worker_num' => 4, 'worker_num' => 1, 'event_object' => true, 'enable_coroutine' => false, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Server $server, $object) { $n = N; while ($n--) { $server->task($object); } }); $server->on('task', function ($server, $task) { $server->finish(['data' => str_repeat('A', 8000), 'task' => $task->data]); }); $server->on('finish', function ($server, $result) { static $count = 0; $count++; if ($count == N) { $server->send($result->data['task']->fd, 'hello world'); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/onReload.phpt ================================================ --TEST-- swoole_server: server beforeRelaod and afterReload event --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm,$argv) { sleep(2); $script_name = $argv[0]; $ret = shell_exec("ps aux | grep $script_name | grep -v 'grep'"); Assert::assert($ret != ""); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $WorkerStartAtomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'log_file' => TEST_LOG_FILE, "worker_num" => 2, "task_worker_num" => 2, "max_wait_time" => 1, ]); $serv->on("BeforeReload", function (Server $serv) { var_dump("BeforeReload"); }); $serv->on("AfterReload", function (Server $serv) { var_dump("AfterReload"); }); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm, $WorkerStartAtomic) { $WorkerStartAtomic->add(1); if ($WorkerStartAtomic->get() === 4) { $serv->reload(); $pm->wakeup(); } }); $serv->on('Receive', function ($serv, $fd, $tid, $data) { }); $serv->on('Task', function ($serv,$task_id, $reactor_id, $params) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- string(12) "BeforeReload" string(11) "AfterReload" DONE ================================================ FILE: tests/swoole_server/parse_option_to_size.phpt ================================================ --TEST-- swoole_server: parse option value to size --SKIPIF-- --FILE-- set([ 'buffer_output_size' => '2M', ]); $server->set([ 'buffer_output_size' => 2 * 1024 * 1024, ]); $server->set([ 'buffer_output_size' => 'xxx--2M', ]); ?> --EXPECTF-- Fatal error: Swoole\Server::set(): failed to parse 'xxx--2M' to size, Error: Invalid quantity "xxx--2M": no valid leading digits, interpreting as "0" for backwards compatibility in %s on line %d --EXPECTF_85-- Fatal error: Swoole\Server::set(): failed to parse 'xxx--2M' to size, Error: Invalid quantity "xxx--2M": no valid leading digits, interpreting as "0" for backwards compatibility in %s on line %d Stack trace: #0 %s(%d): Swoole\Server->set(Array) #1 {main} ================================================ FILE: tests/swoole_server/pid_file.phpt ================================================ --TEST-- swoole_server: pid_file --SKIPIF-- --FILE-- parentFunc = function ($pid) { Assert::assert(is_file(PID_FILE)); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'pid_file' => PID_FILE, 'log_file' => '/dev/null', )); $serv->on("Start", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); clearstatcache(); Assert::assert(!is_file(PID_FILE)); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/protect.phpt ================================================ --TEST-- swoole_server: protect --SKIPIF-- --FILE-- send(opcode_encode("protect", [2])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::true($data); $cli->close(); Timer::clear($timer); echo "SUCCESS"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/protect_false.phpt ================================================ --TEST-- swoole_server: protect($fd, false) --SKIPIF-- --FILE-- send(opcode_encode("protect", [2, false])); Assert::assert($r !== false); }, function (Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::true($data); $cli->close(); Timer::clear($timer); echo "SUCCESS\n"; }); \Swoole\Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/reload.phpt ================================================ --TEST-- swoole_server: reload --SKIPIF-- --FILE-- send(opcode_encode("reload", [2])); Assert::assert($r !== false); }, function(Client $cli, $recv) use ($timer) { list($op, $data) = opcode_decode($recv); Assert::true($data); $cli->close(); Timer::clear($timer); echo "SUCCESS"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/reload_async.phpt ================================================ --TEST-- swoole_server: reload async --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function () use ($pm) { $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $reloaded, $workerCounter) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'worker_num' => rand(2, swoole_cpu_num() * 2), 'max_wait_time' => 10, 'reload_async' => true, 'enable_coroutine' => false, ]); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm, $reloaded, $workerCounter) { $workerCounter->add(1); if ($worker_id === 0 and $reloaded->get() != 1) { $reloaded->set(1); while ($workerCounter->get() < $server->setting['worker_num']) { usleep(10000); } go(function () use ($pm) { for ($n = 1; $n <= 5; $n++) { Co::sleep(0.1); echo "{$n}\n"; } echo "RELOADED\n"; $pm->wakeup(); }); echo "RELOAD\n"; Assert::assert($server->reload()); } }); $server->on('Receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- RELOAD 1 2 3 4 5 RELOADED DONE ================================================ FILE: tests/swoole_server/reload_process.phpt ================================================ --TEST-- swoole_server: reload in process mode --SKIPIF-- --FILE-- new Swoole\Atomic(), 'task_worker' => new Swoole\Atomic() ]; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function () use ($pm) { global $counter, $worker_num, $task_worker_num; while (!file_exists(TEST_PID_FILE)) { usleep(100 * 1000); } $pid = file_get_contents(TEST_PID_FILE); $random = mt_rand(1, 4); usleep(100 * 1000); for ($n = $random; $n--;) { Swoole\Process::kill($pid, SIGUSR1); usleep(500 * 1000); Swoole\Process::kill($pid, SIGUSR2); usleep(500 * 1000); } /**@var $counter Swoole\Atomic[] */ $total = $counter['worker']->get() - $worker_num; $expect = $random * $worker_num; Assert::same($total, $expect, "[worker reload {$total} but expect {$expect}]"); $total = $counter['task_worker']->get() - $task_worker_num; $expect = $random * $task_worker_num * 2; Assert::same($total, $expect, "[task worker reload {$total} but expect {$expect}]"); $log = file_get_contents(TEST_LOG_FILE); $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); if (!Assert::assert(empty($log))){ var_dump($log); } $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { global $worker_num, $task_worker_num; @unlink(TEST_LOG_FILE); @unlink(TEST_PID_FILE); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => TEST_LOG_FILE, 'pid_file' => TEST_PID_FILE, 'worker_num' => $worker_num, 'task_worker_num' => $task_worker_num ]); $server->on('ManagerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { /**@var $counter Swoole\Atomic[] */ global $counter; $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; $atomic->add(1); }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); $server->on('Task', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/sendMessage_1.phpt ================================================ --TEST-- swoole_server: send message [1] --SKIPIF-- --FILE-- send(opcode_encode("sendMessage", ["SUCCESS", 1])); Assert::assert($r !== false); }, function(Client $cli, $recv) { list($op, $msg) = opcode_decode($recv); echo $msg; global $timer; $cli->close(); Timer::clear($timer); }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/sendMessage_2.phpt ================================================ --TEST-- swoole_server: send message [02] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'package_eof' => "\r\n", 'open_eof_check' => true, 'open_eof_split' => true, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $list = []; for ($i = 0; $i < 7; $i++) { $data = $client->recv(); if ($data === false or $data === '') { echo "ERROR\n"; break; } $list[] = intval($data); } sort($list); Assert::same($list, range(0, 6)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', 'worker_num' => 4, 'task_worker_num' => 3, ]); $process = new \Swoole\Process(function ($process) use ($serv) { while (true) { $r = $process->read(); if (!$r) { continue; } $cmd = json_decode($r, true); for ($i = 0; $i < ($serv->setting['worker_num'] + $serv->setting['task_worker_num']); $i++) { $serv->sendMessage(['worker_id' => $i, 'fd' => $cmd['fd']], $i); } } }); $serv->addProcess($process); $serv->on("workerStart", function ($serv, $wid) use ($pm) { if ($wid == 0) { $pm->wakeup(); } }); $serv->on('connect', function (Swoole\Server $serv, $fd) use ($process) { $process->write(json_encode(["fd" => $fd])); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { $serv->send($data['fd'], $data['worker_id'] . "\r\n"); }); $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sendMessage_3.phpt ================================================ --TEST-- swoole_server: send message [3] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'package_eof' => "\r\n", 'open_eof_check' => true, 'open_eof_split' => true, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } echo $client->recv(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', 'worker_num' => 2, ]); $serv->on("workerStart", function ($serv, $wid) use ($pm) { if ($wid == 0) { $pm->wakeup(); } }); $serv->on('connect', function (Swoole\Server $serv, $fd) { $wid = $serv->getWorkerId(); $serv->sendMessage([ 'fd' => $fd, 'worker_id' => $wid, 'data' => random_bytes(random_int(1024 * 1024, 2 * 1024 * 1024)), ], 1 - $wid); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { $serv->send($data['fd'], "OK\r\n"); }); $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/sendMessage_4.phpt ================================================ --TEST-- swoole_server: send message [4] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'package_eof' => "\r\n", 'open_eof_check' => true, 'open_eof_split' => true, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } echo $client->recv(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', 'worker_num' => 2, ]); $serv->on("workerStart", function ($serv, $wid) use ($pm) { if ($wid == 0) { $pm->wakeup(); } }); $serv->on('connect', function (Swoole\Server $serv, $fd) { $wid = $serv->getWorkerId(); $serv->sendMessage([ 'fd' => $fd, 'worker_id' => $wid, 'exception' => new \Exception(__METHOD__), ], 1 - $wid); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { $serv->send($data['fd'], "OK\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/sendMessage_in_manager.phpt ================================================ --TEST-- swoole_server: sendMessage in manager --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'package_eof' => "\r\n", 'open_eof_check' => true, 'open_eof_split' => true, ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $list = []; swoole_loop_n(N, function () use ($client, &$list) { $msg = $client->recv(); $list[] = $msg; Assert::contains($msg, 'msg-'); }); Assert::count($list, N); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', 'worker_num' => 2, 'task_worker_num' => 2, ]); $serv->on(Constant::EVENT_MANAGER_START, function (Server $serv) use ($pm) { $pm->wakeup(); $pm->wait(); usleep(10000); swoole_loop_n(N, function ($i) use ($serv) { $wid = rand(0, 3); $serv->sendMessage("msg-" . $i, $wid); }); }); $serv->on(Constant::EVENT_CONNECT, function ($serv, $fd, $reactor_id) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_RECEIVE, function ($serv, $fd, $reactor_id, $data) { }); $serv->on(Constant::EVENT_TASK, function ($serv, $fd, $reactor_id, $data) { }); $serv->on('pipeMessage', function (Server $serv, $worker_id, $data) { foreach ($serv->connections as $fd) { $serv->send($fd, $data . "\r\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/send_2.phpt ================================================ --TEST-- swoole_server: send big packet [2] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $total = 0; for ($i = 0; $i < CONCURRENCY; $i++) { go(function () use ($pm, $i, &$total) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $n = SEND_N; while ($n--) { $data = $cli->recv(); Assert::assert($data); $char = chr(ord('A') + $n % 10); $info = unpack('Nlen', substr($data, 0, 4)); if (VERBOSE) { echo "[Client] c=$i, n=$n, len={$info['len']}\n---------------------------------------------------------------------\n"; } Assert::same($info['len'], strlen($data) - 4); Assert::same(str_repeat($char, 1024), substr($data, rand(4, $info['len'] - 1024 - 4), 1024)); $total += strlen($data); } }); } Swoole\Event::wait(); echo $total . " bytes\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'send_yield' => true, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd, $rid) { if (VERBOSE) { echo "new client, fd=$fd\n"; } $n = SEND_N; while ($n--) { $len = rand(PKT_MIN_SIZE, PKT_MAX_SIZE); $send_data = str_repeat(chr(ord('A') + $n % 10), $len); if (VERBOSE) { echo "[Server] c=$fd, n=$n, len=" . (strlen($send_data) + 4) . "\n---------------------------------------------------------------------\n"; } $retval = $serv->send($fd, pack('N', $len) . $send_data); if ($retval === false) { echo "send error, code=" . swoole_last_error() . "\n"; } } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %d bytes ================================================ FILE: tests/swoole_server/send_2m_in_task_worker.phpt ================================================ --TEST-- swoole_server: send 2M data in task worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $c = IS_MAC_OS ? 8 : MAX_CONCURRENCY_MID; for ($i = 0; $i < $c; $i++) { go(function () use ($pm, $i) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 2) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < MAX_REQUESTS; $i++) { $sid = strval(rand(10000000, 99999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recv(); Assert::same(strlen($data), SIZE); Assert::same($sid, substr($data, -8, 8)); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 2, 'log_level' => SWOOLE_LOG_ERROR, 'task_worker_num' => 2, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'task_use_object' => true, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->task(['fd' => $fd, 'data' => $data]); }); $serv->on('task', function (Server $serv, Server\Task $task) { $send_data = str_repeat('A', SIZE - 12) . substr($task->data['data'], -8, 8); Assert::true($serv->send($task->data['fd'], pack('N', strlen($send_data)) . $send_data)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/send_2m_in_user_process.phpt ================================================ --TEST-- swoole_server: send 2M data in user process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 2) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < MAX_REQUESTS; $i++) { $sid = strval(rand(10000000, 99999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recv(); Assert::same(strlen($data), SIZE); Assert::same($sid, substr($data, -8, 8)); } }); } Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 2, 'task_worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); $proc = new Process(function ($process) use ($serv) { while (true) { $pkt = $process->read(); if (!$pkt) { break; } $data = unserialize($pkt); $send_data = str_repeat('A', SIZE - 12) . substr($data['data'], -8, 8); $serv->send($data['fd'], pack('N', strlen($send_data)) . $send_data); } }, false, 2); $serv->addProcess($proc); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($proc) { $proc->write(serialize(['fd' => $fd, 'data' => $data])); }); $serv->on('task', function (Server $serv, Server\Task $task) { echo "ERROR\n"; }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/send_3.phpt ================================================ --TEST-- swoole_server: send big packet [3] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $total = 0; for ($i = 0; $i < CONCURRENCY; $i++) { go(function () use ($pm, $i, &$total) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $n = SEND_N; while ($n--) { $data = $cli->recv(); Assert::assert($data); $char = chr(ord('A') + $n % 10); $info = unpack('Nlen', substr($data, 0, 4)); // echo "c=$i, n=$n, len={$info['len']}\n---------------------------------------------------------------------\n"; Assert::same($info['len'], strlen($data) - 4); Assert::same(str_repeat($char, 1024), substr($data, rand(4, $info['len'] - 1024 - 4), 1024)); $total += strlen($data); } }); } Swoole\Event::wait(); echo $total . " bytes\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'reactor_num' => 1, "worker_num" => 4, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'send_yield' => true, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd, $rid) { $n = SEND_N; while ($n--) { $len = rand(PKT_MIN_SIZE, PKT_MAX_SIZE); $send_data = str_repeat(chr(ord('A') + $n % 10), $len); $retval = $serv->send($fd, pack('N', $len) . $send_data); if ($retval === false) { echo "send error, code=".swoole_last_error()."\n"; } } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- %d bytes ================================================ FILE: tests/swoole_server/send_big_packet.phpt ================================================ --TEST-- swoole_server: send big packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < MAX_CONCURRENCY_LOW; $i++) { go(function () use ($pm, $i) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 2) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < MAX_REQUESTS_LOW; $i++) { $sid = strval(rand(10000000, 99999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recv(); Assert::same(strlen($data), SIZE); Assert::same($sid, substr($data, -8, 8)); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 4, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $send_data = str_repeat('A', SIZE - 12) . substr($data, -8, 8); $serv->send($fd, pack('N', strlen($send_data)) . $send_data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/send_in_master.phpt ================================================ --TEST-- swoole_server: send data in master process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $cli->send($GLOBALS['data']); Assert::same($cli->recv(), 'Swoole: '.$GLOBALS['data']); }); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 2, 'log_file' => TEST_LOG_FILE, )); $serv->on(Constant::EVENT_START, function (Server $server) { Timer::tick(50, function ($timer) use ($server) { if ($GLOBALS['atomic']->get() == 0) { return; } foreach ($server->connections as $fd) { $server->send($fd, 'Swoole: ' . $GLOBALS['data']); } Timer::clear($timer); }); }); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) { $GLOBALS['atomic']->set(1); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/send_in_other_worker_with_base.phpt ================================================ --TEST-- swoole_server: send data in another process with base mode --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { echo "ERROR\n"; return; } $data = base64_encode(random_bytes(128)); $cli->send($data); Assert::same($cli->recv(), 'Swoole: '.$data); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 2, 'log_file' => TEST_LOG_FILE, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on(Constant::EVENT_PIPE_MESSAGE, function ($serv, $workerId, $msg) { $serv->send($msg['fd'], 'Swoole: '.$msg['data']); }); $serv->on(Constant::EVENT_RECEIVE, function (Swoole\Server $serv, $fd, $rid, $data) { $serv->sendMessage(['data' => $data, 'fd' => $fd], 1 - $serv->getWorkerId()); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sendfile.phpt ================================================ --TEST-- swoole_server: sendfile --SKIPIF-- --FILE-- send(opcode_encode("sendfile", [2, __FILE__])); Assert::assert($r !== false); }, function (Client $cli, $recv) { $len = unpack("N", substr($recv, 0, 4))[1]; Assert::same($len - 4, strlen(substr($recv, 4))); global $timer; $cli->close(); Timer::clear($timer); echo "SUCCESS"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/sendfile_02.phpt ================================================ --TEST-- swoole_server: sendfile [02] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm){ $client = new Swoole\Client(SWOOLE_SOCK_TCP , SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $socket = $client->getSocket(); socket_set_option($socket, SOL_SOCKET, SO_SNDBUF, 65536); socket_set_option($socket, SOL_SOCKET, SO_RCVBUF, 65536); $N = filesize(TEST_IMAGE); $bytes = 0; while($bytes < $N) { $n = rand(8192, 65536); $r = $client->recv($n); if (!$r) { break; } usleep(10000); $bytes += strlen($r); } Assert::same($bytes, $N); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ 'log_file' => '/dev/null', 'kernel_socket_send_buffer_size' => 65536, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd) { Assert::true($serv->sendfile($fd, TEST_IMAGE)); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sendfile_ssl.phpt ================================================ --TEST-- swoole_server: sendfile with SSL --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $socket = $client->getSocket(); socket_set_option($socket, SOL_SOCKET, SO_SNDBUF, 65536); socket_set_option($socket, SOL_SOCKET, SO_RCVBUF, 65536); $N = filesize(TEST_IMAGE); $bytes = 0; $data = ''; while ($bytes < $N) { $n = rand(8192, 65536); $r = $client->recv($n); if (!$r) { break; } usleep(10000); $bytes += strlen($r); $data .= $r; } Assert::same($bytes, $N); Assert::same(md5_file(TEST_IMAGE), md5($data)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ //'log_file' => '/dev/null', 'kernel_socket_send_buffer_size' => 65536, 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd) { Assert::true($serv->sendfile($fd, TEST_IMAGE)); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sendto_timeout.phpt ================================================ --TEST-- swoole_server: sendto timeout --SKIPIF-- --FILE-- 0.5]); $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_UNIX_DGRAM, SWOOLE_SOCK_SYNC); if (!$client->connect(SOCK_FILE, 0, 0.5)) { exit("connect failed\n"); } $client->send(str_repeat('A', N)); $s = microtime(true); $pm->wait(); Assert::lessThan(microtime(true) - $s, 0.6); $data = $client->recv(); Assert::same(strlen($data), N); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server(SOCK_FILE, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_DGRAM); $serv->set(['worker_num' => 1, 'log_file' => '/dev/null']); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('packet', function ($serv, $data, $client) use ($pm) { while (true) { $re = $serv->send($client['address'], str_repeat('B', strlen($data))); if ($re == false) { break; } } $pm->wakeup(); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/shutdown.phpt ================================================ --TEST-- swoole_server: shutdown --SKIPIF-- --FILE-- send(opcode_encode("shutdown", [2])); Assert::assert($r !== false); }, function(Client $cli, $recv) { list($op, $data) = opcode_decode($recv); Assert::true($data); global $timer; $cli->close(); Timer::clear($timer); echo "SUCCESS"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/shutdown_in_master.phpt ================================================ --TEST-- swoole_server: shutdown in master process --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { }; $pm->childFunc = function () use ($pm) { $mode = SERVER_MODE_RANDOM; $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), $mode); $server->set([ 'worker_num' => mt_rand(1, 4), 'log_file' => '/dev/null', ]); $server->on('start', function (Swoole\Server $server) use ($pm, $mode) { echo "START [$mode]\n"; $pm->wakeup(); $server->shutdown(); }); $server->on('receive', function () { }); $server->on('shutdown', function () { echo "SHUTDOWN\n"; }); $server->start(); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(0); ?> --EXPECTF-- START [%d] SHUTDOWN ================================================ FILE: tests/swoole_server/shutdown_in_process.phpt ================================================ --TEST-- swoole_server: shutdown in process mode --SKIPIF-- --FILE-- initRandomData(1); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($client->send($pm->getRandomData()) > 0); }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set(['worker_num' => mt_rand(1, 4), 'log_file' => '/dev/null']); $server->on('start', function () use ($pm) { echo "START\n"; $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { Assert::same($data, $pm->getRandomData()); $server->shutdown(); }); $server->on('shutdown', function () { echo "SHUTDOWN\n"; }); $server->start(); }; $pm->childFirst(); $pm->run(); $pm->expectExitCode(0); ?> --EXPECT-- START SHUTDOWN ================================================ FILE: tests/swoole_server/shutdown_with_base_mode.phpt ================================================ --TEST-- swoole_server: dispatch_mode = 7 [stream] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'log_file' => '/dev/null', "reload_async" => true, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sigint_with_base.phpt ================================================ --TEST-- swoole_server: register sigint handler with base mode --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Process::kill($pid, SIGINT); usleep(10000); echo file_get_contents(TEST_LOG_FILE); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 1, ]); $server->on('workerStart', function (Server $server) use ($pm) { Process::signal(SIGINT, function () use ($server) { file_put_contents(TEST_LOG_FILE, 'SIGINT, SHUTDOWN' . PHP_EOL); $server->shutdown(); }); $pm->wakeup(); }); $server->on('Receive', function (Server $server, $fd, $reactorId, $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); @unlink(TEST_LOG_FILE); ?> --EXPECT-- SIGINT, SHUTDOWN ================================================ FILE: tests/swoole_server/sigint_with_process.phpt ================================================ --TEST-- swoole_server: register sigint handler with process mode --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Swoole\Process::kill($pid, SIGINT); usleep(10000); echo file_get_contents(TEST_LOG_FILE); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 1, ]); $server->on('start', function (Swoole\Server $server) use ($pm) { \Swoole\Process::signal(SIGINT, function () use ($server) { file_put_contents(TEST_LOG_FILE, 'SIGINT, SHUTDOWN' . PHP_EOL); $server->shutdown(); }); $pm->wakeup(); }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactorId, $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); @unlink(TEST_LOG_FILE); ?> --EXPECT-- SIGINT, SHUTDOWN ================================================ FILE: tests/swoole_server/single_thread/heartbeat.phpt ================================================ --TEST-- swoole_server/single_thread: heartbeat --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Assert::isEmpty(@file_get_contents('http://127.0.0.1:' . $pm->getFreePort() . '/')); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'single_thread' => true, 'worker_num' => 1, 'heartbeat_idle_time' => 1, 'heartbeat_check_interval' => 1, ]); $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { $pm->wakeup(); }); $http->on('Request', function ($request, $response) use ($http) { sleep(3); Assert::false($response->end('hello')); Assert::eq($http->getLastError(), SWOOLE_ERROR_SESSION_NOT_EXIST); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/single_thread/large_packet.phpt ================================================ --TEST-- swoole_server/single_thread: large packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $url = 'http://127.0.0.1:' . $pm->getFreePort() . '/'; $filePath = tempnam('/tmp', 'swoole_test_'); $rdata = random_bytes(1024 * 1024); file_put_contents($filePath, $rdata); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: text/html', 'Content-Type: multipart/form-data' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'file' => new CURLFile($filePath, 'text/html') ]); $response = curl_exec($ch); if (curl_errno($ch)) { echo 'ERROR: ' . curl_error($ch); } else { Assert::eq($response, md5($rdata)); } curl_close($ch); unlink($filePath); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'single_thread' => true, 'worker_num' => 1, 'dispatch_mode' => 10, 'package_max_length' => '128m', ]); $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { $pm->wakeup(); }); $http->on('Request', function ($request, $response) { $response->end(md5_file($request->files['file']['tmp_name'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/single_thread/user_setting.phpt ================================================ --TEST-- swoole_server/single_thread: user setting --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $log_file) { $url = 'http://127.0.0.1:' . $pm->getFreePort() . '/'; posix_kill($pid, SIGUSR1); sleep(1); $output = file_get_contents($log_file); Assert::contains($output, 'reloading all workers'); $pm->kill(); unlink($log_file); }; $pm->childFunc = function () use ($pm, $log_file) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'single_thread' => true, 'worker_num' => 1, 'user' => 'www-data', 'group' => 'www-data', 'log_file' => $log_file, ]); $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { $pm->wakeup(); }); $http->on('Request', function ($request, $response) { $response->end('hello'); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/sleep.phpt ================================================ --TEST-- swoole_server: sleep --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co::set([Constant::OPTION_HOOK_FLAGS => SWOOLE_HOOK_ALL]); run(function () use ($pm) { $n = N; $s = microtime(true); $list = []; while($n--) { $list[] = go(function() use ($pm) { $s = microtime(true); $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { return strlen($strHeader); }); $output = curl_exec($ch); Assert::eq($output, "Hello World\n".$code); if ($output === false) { echo "CURL Error:" . curl_error($ch); } curl_close($ch); }); } Co::join($list); Assert::lessThan(microtime(true) - $s, 0.5); echo "Done\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', Constant::OPTION_ENABLE_COROUTINE => true, Constant::OPTION_HOOK_FLAGS => SWOOLE_HOOK_ALL, ]); $http->on("start", function ($server) use ($pm) { $pm->wakeup(); }); $http->on("request", function (Request $request, Response $response) { usleep(300000); $response->end("Hello World\n".$request->get['code']); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Done ================================================ FILE: tests/swoole_server/slow_client.phpt ================================================ --TEST-- swoole_server: send big pipe message --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $port)) { exit("connect failed\n"); } $socket = $client->getSocket(); socket_set_option($socket, SOL_SOCKET, SO_SNDBUF, 65536); socket_set_option($socket, SOL_SOCKET, SO_RCVBUF, 65536); $bytes = 0; while($bytes < N) { $n = rand(8192, 65536); $r = $client->recv($n); if (!$r) { break; } usleep(10000); $bytes += strlen($r); } Assert::same($bytes, N); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'kernel_socket_send_buffer_size' => 65536, ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd) { $_send_data = str_repeat("A", N); $serv->send($fd, $_send_data); }); $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/slow_master.phpt ================================================ --TEST-- swoole_server: slow master --SKIPIF-- --FILE-- add(strlen($data)); } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm, $counter_server, $counter_client, $data_chunks) { $cli = new Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 5); Assert::assert($r); $cli->send('hello world'); $n = $counter_client->get(); $data = ''; while (strlen($data) < $n) { $_recv = $cli->recv(); if (empty($_recv)) { break; } $data .= $_recv; } Assert::eq($data, implode('', $data_chunks)); $cli->close(); $pm->kill(); Assert::greaterThanEq($counter_server->get(), 5); echo "DONE\n"; }; $pm->childFunc = function () use ($pm, $counter_server, $counter_client, $data_chunks) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', 'single_thread' => true, )); $serv->on(Constant::EVENT_START, function () use ($pm) { Timer::after(5, function (){ usleep(300000); }); $pm->wakeup(); }); $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) use ($counter_server, $counter_client, $data_chunks) { $serv->timer = Timer::tick(50, function () use ($counter_server) { $counter_server->add(1); }); foreach ($data_chunks as $chunk) { $serv->send($fd, $chunk); } }); $serv->on(Constant::EVENT_CLOSE, function ($serv) use ($pm) { Timer::clear($serv->timer); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/slow_worker.phpt ================================================ --TEST-- swoole_server: slow worker --SKIPIF-- --FILE-- add(strlen($data)); } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm, $counter_server, $counter_client, $data_chunks) { $cli = new Client(SWOOLE_SOCK_TCP); $r = $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 5); Assert::assert($r); Assert::eq($cli->recv(), 'CONNECT OK'.PHP_EOL); foreach ($data_chunks as $chunk) { $cli->send($chunk); } $cli2 = new Client(SWOOLE_SOCK_TCP); $r = $cli2->connect(TCP_SERVER_HOST, $pm->getFreePort(), 5); Assert::assert($r); Assert::eq($cli2->recv(), 'CONNECT OK'.PHP_EOL); echo $cli->recv(); Assert::eq($counter_server->get(), $counter_client->get()); $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter_server, $counter_client) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'package_max_length' => 16 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); usleep(100000); }); $serv->on(Swoole\Constant::EVENT_CONNECT, function (Server $serv, $fd, $rid) { $serv->send($fd, 'CONNECT OK'.PHP_EOL); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($counter_server, $counter_client) { $counter_server->add(strlen($data)); if ($counter_server->get() == $counter_client->get()) { $serv->send($fd, "DONE\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/ssl/00.phpt ================================================ --TEST-- swoole_server/ssl: basic --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); Assert::same($client->recv(), "Swoole hello world"); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/bad_client.phpt ================================================ --TEST-- swoole_server/ssl: ssl bad client --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $rdata) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send($rdata); Assert::notEq($client->recv(), "Swoole $rdata"); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set( [ 'log_file' => ERROR_FILE, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ] ); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); readfile(ERROR_FILE); unlink(ERROR_FILE); ?> --EXPECTF-- [%s] WARNING Socket::ssl_accept(): bad SSL client[127.0.0.1:%d], reason=%d, error_string=%s ================================================ FILE: tests/swoole_server/ssl/code/client.go ================================================ package main import ( "crypto/tls" "fmt" "io" "log" "os" ) func main() { conf := &tls.Config{ InsecureSkipVerify: true, } url := fmt.Sprintf("127.0.0.1:%s", os.Args[1]); conn, err := tls.Dial("tcp", url, conf) if err != nil { log.Fatalln(err.Error()) } buf := make([]byte, 1024) _, err = io.WriteString(conn, "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n") if err != nil { log.Fatalln(err.Error()) } len, err := conn.Read(buf) if err != nil { fmt.Println(err.Error()) } else { fmt.Println("Receive From Server:", string(buf[:len])) } } ================================================ FILE: tests/swoole_server/ssl/code/client.js ================================================ const tls = require('tls'); const options = { rejectUnauthorized: false, }; var socket = tls.connect(process.argv[2], '127.0.0.1', options, () => { socket.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'); }); socket.setEncoding('utf8'); socket.on('data', (data) => { console.log(data); }); socket.on('end', () => { console.log('Ended') }); ================================================ FILE: tests/swoole_server/ssl/dtls.phpt ================================================ --TEST-- swoole_server/ssl: dtls --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); Assert::same($client->recv(), "Swoole hello world"); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->on('packet', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/dtls_big_packet.phpt ================================================ --TEST-- swoole_server/ssl: big dtls packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); // 同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } // TLS max record size = 16K $client->send('hello world' . str_repeat('A', PKT_LEN)); Assert::same($client->recv(65535), 'Swoole hello world' . str_repeat('A', PKT_LEN)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole {$data}"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/dtls_with_length_protocol.phpt ================================================ --TEST-- swoole_server/ssl: dtls with length protocol --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $req, $resp) { $cli = new Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); $cli->set([ 'open_length_check' => true, 'package_max_length' => 16 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'socket_buffer_size' => 1024 * 1024, ]); $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 5); $cli->send(pack('N', strlen($req)) . $req); $data = $cli->recv(); Assert::eq(bin2hex($resp), bin2hex(substr($data, 4))); $pm->kill(); }; $pm->childFunc = function () use ($pm, $size, $req, $resp) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', 'open_length_check' => true, 'package_max_length' => 16 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'socket_buffer_size' => 1024 * 1024, ]); $serv->on( 'WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); } ); $serv->on( 'connect', function ($serv, $fd, $rid) { // echo "connect\n"; } ); $serv->on( 'receive', function ($serv, $fd, $rid, $data) use ($req, $resp) { Assert::eq(bin2hex($req), bin2hex(substr($data, 4))); Assert::assert($serv->send($fd, pack('N', strlen($resp)) . $resp)); } ); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/golang.phpt ================================================ --TEST-- swoole_server/ssl: golang client --SKIPIF-- /dev/null 2>&1`; skip_if_file_not_exist( __DIR__.'/code/go_client'); ?> --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $result = Co::exec(GO_CLIENT.' '.$pm->getFreePort()); Assert::eq($result['code'], 0); Assert::contains($result['output'], 'swoole-http-server'); }); $pm->kill(); unlink(GO_CLIENT); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on("request", function ($request, $response) { $response->header("Content-Type", "text/plain"); $response->end("Hello World\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/heartbeat_1.phpt ================================================ --TEST-- swoole_server/ssl: heartbeat with bad client --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { echo 'Over flow. errno=' . $client->errCode; exit("\n"); } $s1 = time(); Assert::same($client->recv(), ''); $s2 = time(); Assert::assert($s2 - $s1 > 1); Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'heartbeat_check_interval' => 1, 'log_file' => '/dev/null', 'heartbeat_idle_time' => 1, 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', ]); $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/heartbeat_2.phpt ================================================ --TEST-- swoole_server/ssl: heartbeat normal --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } $s1 = microtime(true); Assert::same($client->recv(), ''); $s2 = microtime(true); Assert::assert($s2 - $s1 > 1); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set(array( 'heartbeat_check_interval' => 1, 'log_file' => '/dev/null', 'heartbeat_idle_time' => 1, 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', )); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/nodejs.phpt ================================================ --TEST-- swoole_server/ssl: nodejs client --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $result = Co::exec('node '.__DIR__.'/code/client.js '.$pm->getFreePort()); Assert::eq($result['code'], 0); Assert::contains($result['output'], 'swoole-http-server'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on("request", function ($request, $response) { $response->header("Content-Type", "text/plain"); $response->end("Hello World\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/ssl/ssl_send_wait.phpt ================================================ --TEST-- swoole_server/ssl: send_wait support ssl --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort(), true); $ret = $client->get('/'); Assert::eq('swoole', $client->body); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $server = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $server->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', 'ssl_ciphers' => 'ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP', 'ssl_protocols' => defined('SWOOLE_SSL_SSLv3') ? SWOOLE_SSL_SSLv3 : 0, 'ssl_verify_peer' => false, 'ssl_allow_self_signed' => true, ]); $server->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $server->on('Receive', function (Server $s, int $clientId, int $threadId, string $data) use ($server) { $message = "HTTP/1.1 200 OK\r\nContent-Length:6\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nswoole"; $server->sendwait($clientId, $message); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/ssl/verify_01.phpt ================================================ --TEST-- swoole_server/ssl: server verify client success --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem', ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); Assert::same($client->recv(), "Swoole hello world"); }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/ssl/verify_02.phpt ================================================ --TEST-- swoole_server/ssl: server verify client failed --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); $client->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/client-expired.crt', 'ssl_key_file' => SSL_FILE_DIR . '/client-expired.key', ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); usleep(100 * 1000); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca.crt', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] NOTICE Socket::ssl_verify() (ERRNO %d): can not verify peer from fd#%d with error#%d: certificate has expired ================================================ FILE: tests/swoole_server/ssl/verify_03.phpt ================================================ --TEST-- swoole_server/ssl: server verify client failed --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } @$client->send("hello world"); Assert::assert(@$client->recv() == ""); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca.crt', ]); $serv->on("workerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $tid, $data) { $serv->send($fd, "Swoole $data"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/start_twice.phpt ================================================ --TEST-- swoole_server: start twice --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.5) or die("ERROR"); $cli->send("signal") or die("ERROR"); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->shutdown(); }); $n = 2; while ($n--) { $serv->start(); } }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Warning: Swoole\Server::start(): The server have been shutdown, unable to execute Swoole\Server->start() in %s on line %d ================================================ FILE: tests/swoole_server/stats.phpt ================================================ --TEST-- swoole_server: stats --SKIPIF-- --FILE-- send(opcode_encode("stats", [])); Assert::assert($r !== false); }, function(Client $cli, $recv) { list($op, $data) = opcode_decode($recv); $cli->close(); global $timer; TImer::clear($timer); echo "SUCCESS"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/stats_file.phpt ================================================ --TEST-- swoole_server: stats_file --SKIPIF-- --FILE-- initFreePorts(1); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm, $pid) { httpRequest('http://127.0.0.1:' . $pm->getFreePort(0)); for ($i = 0; $i < 4; ++$i) { Co::sleep(0.5); $content = @file_get_contents(STATS_FILE); if ('' != $content) { $stats = []; swoole_string($content)->split("\n")->each(function ($value, $key) use (&$stats) { [$k, $v] = swoole_string($value)->split(":"); $stats[$k] = trim($v); }); Assert::keyExists($stats, 'connection_num'); Assert::keyExists($stats, 'request_count'); break; } } }); echo "\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $mode = SERVER_MODE_RANDOM; $worker_num = rand(1, 4); phpt_var_dump("mode: $mode\nworker_num: $worker_num\n"); $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), $mode); $server->set([ 'stats_file' => STATS_FILE, 'log_file' => DEV_NULL, 'worker_num' => $worker_num, ]); $server->on('ManagerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $server->on('request', function ($request, $response) { $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); $rm_fn(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/stats_file_json.phpt ================================================ --TEST-- swoole_server: stats_file [json] --SKIPIF-- --FILE-- initFreePorts(1); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm, $pid) { httpRequest('http://127.0.0.1:' . $pm->getFreePort(0)); for ($i = 0; $i < 4; ++$i) { Co::sleep(0.5); $content = @file_get_contents(STATS_FILE); if ('' != $content) { $stats = json_decode($content, true); assert_server_stats($stats); break; } } }); echo "\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $mode = SERVER_MODE_RANDOM; $worker_num = rand(1, 4); phpt_var_dump("mode: $mode\nworker_num: $worker_num\n"); $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), $mode); $server->set([ 'stats_file' => STATS_FILE, 'log_file' => DEV_NULL, 'worker_num' => $worker_num, ]); $server->on('ManagerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $server->on('request', function ($request, $response) { $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); $rm_fn(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/stats_file_php.phpt ================================================ --TEST-- swoole_server: stats_file [php] --SKIPIF-- --FILE-- initFreePorts(1); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm, $pid) { httpRequest('http://127.0.0.1:' . $pm->getFreePort(0)); for ($i = 0; $i < 4; ++$i) { Co::sleep(0.5); if (!is_file(STATS_FILE)) { continue; } $stats = include STATS_FILE; if (empty($stats)) { assert_server_stats($stats); break; } } }); echo "\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $mode = SERVER_MODE_RANDOM; $worker_num = rand(1, 4); phpt_var_dump("mode: $mode\nworker_num: $worker_num\n"); $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), $mode); $server->set([ 'stats_file' => STATS_FILE, 'log_file' => DEV_NULL, 'worker_num' => $worker_num, ]); $server->on('ManagerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $server->on('request', function ($request, $response) { $response->end("

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

"); }); $server->start(); }; $pm->childFirst(); $pm->run(); $rm_fn(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/stop.phpt ================================================ --TEST-- swoole_server: stop --SKIPIF-- --FILE-- send(opcode_encode("stop", [2])); Assert::assert($r !== false); }, function(Client $cli, $recv) { list($op, $data) = opcode_decode($recv); Assert::true($data); $cli->close(); global $timer; Timer::clear($timer); echo "SUCCESS\n"; }); Event::wait(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/stop_in_workerStart.phpt ================================================ --TEST-- swoole_server: stop worker in worker start --SKIPIF-- --FILE-- set([ 'worker_num' => 1 ]); $server->on('Receive', function (Server $server, int $fd, int $reactorId, string $data) {}); $server->on('start', function () use ($atomic) {}); $server->on('WorkerStart', function (Server $server, int $workerId) { usleep(50000); $server->stop(); }); $server->on('WorkerStop', function (Server $server, int $workerId) use ($atomic) { usleep(200000); if ($atomic->add(1) == 1) { $server->shutdown(); } }); $server->start(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_server/systemd_fds.phpt ================================================ --TEST-- swoole_server: systemd fds --SKIPIF-- --FILE-- initFreePorts(2); if (HAVE_IPV6) { $pm->initFreeIPv6Ports(2); } $pm->parentFunc = function ($pid) use ($pm) { $test_func = function ($type, $host, $port = 0) { $client = new Client($type); Assert::notEmpty($client->connect($host, $port)); $client->send("SUCCESS"); Assert::eq($client->recv(), 'SUCCESS' . PHP_EOL); $client->close(); }; $test_func(SWOOLE_SOCK_TCP, "127.0.0.1", $pm->getFreePort(0)); $test_func(SWOOLE_SOCK_UDP, "127.0.0.1", $pm->getFreePort(1)); if (HAVE_IPV6) { $test_func(SWOOLE_SOCK_TCP6, "::1", $pm->getFreePort(2)); $test_func(SWOOLE_SOCK_UDP6, "::1", $pm->getFreePort(3)); } $test_func(SWOOLE_SOCK_UNIX_STREAM, UNIX_SOCK_1); $test_func(SWOOLE_SOCK_UNIX_DGRAM, UNIX_SOCK_2); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $sockets = []; $start_fd = swoole_array(scandir('/proc/self/fd'))->sort()->last(); putenv('LISTEN_FDS_START=' . $start_fd); $sockets[] = stream_socket_server('tcp://127.0.0.1:' . $pm->getFreePort(0), $errno, $errstr); $sockets[] = stream_socket_server('udp://0.0.0.0:' . $pm->getFreePort(1), $errno, $errstr, STREAM_SERVER_BIND); if (HAVE_IPV6) { $sockets[] = stream_socket_server('tcp://[::1]:' . $pm->getFreePort(2), $errno, $errstr); $sockets[] = stream_socket_server('udp://[::]:' . $pm->getFreePort(3), $errno, $errstr, STREAM_SERVER_BIND); } $sockets[] = stream_socket_server('unix://' . UNIX_SOCK_1, $errno, $errstr); $sockets[] = stream_socket_server('udg://' . UNIX_SOCK_2, $errno, $errstr, STREAM_SERVER_BIND); putenv('LISTEN_PID=' . posix_getpid()); putenv('LISTEN_FDS=' . count($sockets)); $serv = new Server('SYSTEMD', 0, SWOOLE_BASE); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("packet", function (Server $serv, $data, $addr) { // var_dump($addr); $serv->sendto($addr['address'], $addr['port'] ?? 0, 'SUCCESS' . PHP_EOL); }); $serv->on("receive", function (Server $serv, $fd, $tid, $data) { $serv->send($fd, 'SUCCESS' . PHP_EOL); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/base.phpt ================================================ --TEST-- swoole_server/task: task & finish --SKIPIF-- --FILE-- connect(TCP_SERVER_HOST, $port); Assert::true($r); $cli->send("Test swoole_server::task Interface."); $data = $cli->recv(); Assert::same($data, "OK"); $cli->close(); Assert::false($cli->isConnected()); echo "SUCCESS\n"; }); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/bug_2585.phpt ================================================ --TEST-- swoole_server/task: bug Github#2585 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $ret = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); Assert::same($ret, 'Hello Swoole!'); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'task_worker_num' => 4, 'enable_coroutine' => false, 'task_enable_coroutine' => true ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); if (mt_rand(0, 1)) { $http->task($response->fd); } else { $http->task($response->fd, -1, function ($server, $taskId, $data) { list($fd, $data) = $data; $response = Swoole\Http\Response::create($fd); $response->end($data); }); } }); $http->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) { $fd = $task->data; if (mt_rand(0, 1)) { $task->finish([$fd, 'Hello Swoole!']); } else { $response = Swoole\Http\Response::create($fd); $pdo = new PDO( "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", MYSQL_SERVER_USER, MYSQL_SERVER_PWD ); $stmt = $pdo->query('SELECT "Hello Swoole!"'); Assert::assert($stmt->execute()); $ret = $stmt->fetchAll(PDO::FETCH_COLUMN)[0]; $response->end($ret); } }); $http->on('finish', function ($server, $taskId, $data) { list($fd, $ret) = $data; $response = Swoole\Http\Response::create($fd); $response->end($ret); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/callback_is_null.phpt ================================================ --TEST-- swoole_server/task: task callback is null --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.5) or die("ERROR"); $cli->send("task-01") or die("ERROR"); $res = json_decode(trim($cli->recv()), true); Assert::same($res['code'], 0); Assert::same($res['message'], 'hello world'); echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->task(['type' => 'array', 'value' => $data, 'fd' => $fd], -1, null); }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { return array("code" => 0, 'message' => 'hello world', 'sid' => uniqid(), 'fd' => $data['fd']); }); $serv->on('finish', function (Server $serv, $task_id, $data) { $serv->send($data['fd'], json_encode($data) . "\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/enable_coroutine.phpt ================================================ --TEST-- swoole_server/task: task [enable_coroutine] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($n = MAX_REQUESTS; $n--;) { go(function () use ($pm, $n) { $c = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $c->set(['timeout' => 5,]); Assert::assert($c->get('/task?n='.$n)); Assert::same($c->body, "OK"); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $randoms) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'task_worker_num' => 1, 'task_enable_coroutine' => true, ]); $server->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server, $randoms) { $n = $request->get['n']; list($ret_n, $ret_random) = $server->taskwait($n, 1); if ($ret_n !== $n) { $response->end("ERROR MATCH {$ret_n} with {$n}"); return; } elseif ($ret_random !== $randoms[$n]) { $response->end("ERROR EQUAL {$ret_n}(" . strlen($ret_random) . ") with {$n}(" . strlen($randoms[$n]) . ")"); return; } $response->end('OK'); }); $server->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) use ($pm, $randoms) { Assert::same($task->worker_id, 0); Assert::assert($task->flags > 0); Assert::assert($task->id >= 0); $n = $task->data; co::sleep(0.002); $task->finish([$n, $randoms[$n]]); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/finish_timeout.phpt ================================================ --TEST-- swoole_server/task: finish timeout --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or exit('ERROR'); $cli->send('task-01') or exit('ERROR'); Assert::same($cli->recv(), 'hello world'); $cli->close(); usleep(1000000); echo file_get_contents(TMP_LOG_FILE); $pm->kill(); }; $pm->childFunc = function () use ($pm) { swoole_ignore_error(SWOOLE_ERROR_SERVER_NO_IDLE_WORKER); $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => TMP_LOG_FILE, 'log_level' => SWOOLE_LOG_NOTICE, 'task_worker_num' => 1, 'socket_send_timeout' => 1.0, 'socket_buffer_size' => 128 * 1024, 'worker_num' => 1, 'enable_coroutine' => false, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Server $server, $fd, $tid, $data) { $server->task($fd); usleep(1100000); }); $server->on('task', function ($server, $task_id, $worker_id, string $fd) { $n = 200; $size = IS_MAC_OS ? 2000 : 8000; while ($n--) { if (!$server->finish(str_repeat('A', $size))) { break; } } $server->send($fd, 'hello world'); }); $server->on('finish', function ($server, $task_id, $data) { }); $server->on('close', function () {}); $server->start(); }; $pm->childFirst(); $pm->run(); unlink(TMP_LOG_FILE); ?> --EXPECTF-- [%s] WARNING Server::finish() (ERRNO %d): send result to worker timed out ================================================ FILE: tests/swoole_server/task/huge_data.phpt ================================================ --TEST-- swoole_server/task: huge data --SKIPIF-- --FILE-- setRandomFunc('get_big_random'); $pm->initRandomData(MAX_REQUESTS_LOW); $pm->parentFunc = function (int $pid) use ($pm) { $uri = "http://127.0.0.1:{$pm->getFreePort()}"; go(function () use ($pm, $uri) { for ($c = MAX_REQUESTS_LOW; $c--;) { $data = $pm->getRandomData(); $body = httpGetBody($uri, ['data' => $data]); Assert::same($body, $data); } }); Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'task_worker_num' => 4 ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); $scope = IS_IN_CI ? [4, 16] : [16, 64]; $repeat = mt_rand(...$scope); $http->task([ 'fd' => $response->fd, 'repeat' => $repeat, 'data' => str_repeat($request->rawContent(), $repeat) ]); }); $http->on('task', function ($a, $b, $c, array $info) { $response = Swoole\Http\Response::create($info['fd']); $response->end(substr($info['data'], 0, strlen($info['data']) / $info['repeat'])); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/idle_worekr_num.phpt ================================================ --TEST-- swoole_server/task: idle_worker_num --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); $data = $client->recv(); Assert::assert($data); $json = json_decode($data); Assert::eq($json->task_idle_worker_num, 1); $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'task_use_object' => true, ]); $serv->on("workerStart", function (Server $serv, $wid) use ($pm, $atomic) { if ($atomic->add() == $serv->setting['worker_num'] + $serv->setting['task_worker_num']) { Timer::after(1, function () use ($pm) { $pm->wakeup(); }); } }); $serv->on('receive', function (Server $serv, $fd, $tid, $data) { $serv->task(['sleep' => 1,]); $serv->task(['fd' => $fd,]); }); $serv->on('Task', function (Server $server, Server\Task $task) { if (isset($task->data['sleep'])) { sleep($task->data['sleep']); } else { $server->send($task->data['fd'], json_encode($server->stats())); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/invalid_packet.phpt ================================================ --TEST-- swoole_server/task: invalid packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $data = '{"tid":17732683638813521,"out_trade_no":"dm5601993521","runMethod":"\Action\Mpay\Uni\UniApiV3Act:jsonDrive"}'; $queueId = msg_get_queue(MSGQ_KEY); if ($queueId === false) { throw new Exception('msg_get_queue() failed.'); } Assert::true(msg_send($queueId, 1, str_repeat('\n', 64) . 'hello' . $data)); Assert::true(msg_send($queueId, 1, Task::pack($data), false)); $pm->wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $file, $result) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'worker_num' => 2, 'task_worker_num' => 1, 'task_ipc_mode' => 3, 'message_queue_key' => MSGQ_KEY, 'log_file' => $file, ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) {}); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) use ($pm, $result) { $pm->wakeup(); $result->add(1); }); $serv->start(); }; $pm->childFirst(); $pm->run(); usleep(100000); // echo file_get_contents($file); Assert::true(swoole_string(file_get_contents($file))->contains('bad task packet')); unlink($file); Assert::eq($result->get(), 1); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/kill_01.phpt ================================================ --TEST-- swoole_server/task: kill task worker [SWOOLE_BASE] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { // 杀死进程 kill_process_by_name(PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(PROC_NAME) > 0); } $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or die("ERROR"); $cli->send("task-01") or die("ERROR"); Assert::same($cli->recv(), "task-01"); $cli->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(["worker_num" => 1, 'log_file' => TEST_LOG_FILE, 'task_worker_num' => 1,]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id = 1) { swoole_set_process_name(PROC_NAME); $pm->wakeup(); } }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { $serv->task(['fd' => $fd, 'data' => $data]); }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { $serv->send($data['fd'], $data['data']); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/kill_02.phpt ================================================ --TEST-- swoole_server/task: kill task worker 01 [SWOOLE_BASE] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { //杀死进程 kill_process_by_name(PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(["worker_num" => 1, 'log_file' => TEST_LOG_FILE, 'task_worker_num' => 1,]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id = 1) { swoole_set_process_name(PROC_NAME); $pm->wakeup(); } }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { return array("code" => 0, 'message' => 'hello world', 'sid' => uniqid()); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/kill_task_worker_02.phpt ================================================ --TEST-- swoole_server/task: kill task worker [SWOOLE_PROCESS] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < 5; $i++) { //杀死进程 kill_process_by_name(PROC_NAME); usleep(10000); //判断进程是否存在 Assert::assert(get_process_pid_by_name(PROC_NAME) > 0); } $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null', 'task_worker_num' => 1,]); $serv->on("WorkerStart", function (Server $serv, $worker_id) use ($pm) { if ($worker_id = 1) { swoole_set_process_name(PROC_NAME); $pm->wakeup(); } }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { return array("code" => 0, 'message' => 'hello world', 'sid' => uniqid()); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/scheduler_warning.phpt ================================================ --TEST-- swoole_server/task: scheduler warning --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $client = new Client(SWOOLE_SOCK_UDP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 2)) { exit("connect failed\n"); } $client->send("ping"); echo $client->recv(); sleep(2); $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter) { $serv = new Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 2, 'log_file' => LOG_FILE, 'log_level' => SWOOLE_LOG_NOTICE, ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { if ($serv->taskworker) { $pm->wakeup(); } }); $serv->on('Packet', function (Server $serv, string $data, array $clientInfo) { $n = N; while ($n--) { $serv->task(['data' => $data, 'client' => $clientInfo]); usleep(10000); } }); $serv->on('Task', function (Server $serv, $taskId, int $workerId, $data) use ($pm, $counter) { static $sleep = false; if (!$sleep) { $sleep = true; sleep(1); } if ($counter->add() == N) { $serv->sendto($data['client']['address'], $data['client']['port'], "DONE\n"); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); echo file_get_contents(LOG_FILE); unlink(LOG_FILE); ?> --EXPECTF-- DONE [%s] WARNING Server::timer_callback() (ERRNO %d): No idle task worker is available ================================================ FILE: tests/swoole_server/task/task_callback.phpt ================================================ --TEST-- swoole_server/task: task callback --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); // $cli->set(['open_eof_split' => true, 'package_eof' => "\r\n\r\n"]); $cli->connect('127.0.0.1', $pm->getFreePort(), 0.5) or die("ERROR"); $cli->send("task-01") or die("ERROR"); $res = json_decode(trim($cli->recv()), true); Assert::same($res['code'], 0); Assert::same($res['message'], 'hello world'); echo "SUCCESS\n"; $res = json_decode(trim($cli->recv()), true); Assert::same($res['code'], 0); Assert::same($res['message'], 'hello world'); echo "SUCCESS\n"; Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->task(['type' => 'array', 'value' => $data], -1, function ($serv, $taskId, $data) use ($fd) { $serv->send($fd, json_encode($data)."\r\n\r\n"); }); $serv->task(['type' => 'array', 'value' => $data, 'worker_id' => 0], 0, function ($serv, $taskId, $data) use ($fd) { $serv->send($fd, json_encode($data)."\r\n\r\n"); }); }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { return array("code" => 0, 'message' => 'hello world', 'sid' => uniqid()); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS SUCCESS ================================================ FILE: tests/swoole_server/task/task_co.phpt ================================================ --TEST-- swoole_server/task: task_co --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { for ($n = MAX_REQUESTS; $n--;) { if (!Assert::assert(($res = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/task?n={$n}")) === 'OK')) { echo "{$res}\n"; break; } } }); Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $randoms) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 4, 'task_worker_num' => 4, 'task_enable_coroutine' => true ]); $server->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server, $randoms) { $n = $request->get['n']; switch ($request->server['path_info']) { case '/task': { list($ret_n, $ret_random) = $server->taskCo([$n], 1)[0]; if ($ret_n !== $n) { $response->end("ERROR MATCH {$ret_n} with {$n}"); return; } elseif ($ret_random !== $randoms[$n]) { $response->end("ERROR EQUAL {$ret_n}(" . strlen($ret_random) . ") with {$n}(" . strlen($randoms[$n]) . ")"); return; } $response->end('OK'); break; } case '/random': { $response->end($randoms[$n]); break; } } }); $server->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) use ($pm) { $task->finish([$task->data, httpGetBody('http://127.0.0.1:' . $pm->getFreePort() . "/random?n={$task->data}")]); }); $server->on('finish', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/task_enable_coroutine.phpt ================================================ --TEST-- swoole_server/task: pdo in task and http response detach --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $ret = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); Assert::same($ret, 'Hello Swoole!'); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'task_worker_num' => 4, 'task_enable_coroutine' => true ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); if (mt_rand(0, 1)) { $http->task($response->fd); } else { $http->task($response->fd, -1, function ($server, $taskId, $data) { list($fd, $data) = $data; $response = Swoole\Http\Response::create($fd); $response->end($data); }); } }); $http->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) { $fd = $task->data; if (mt_rand(0, 1)) { $task->finish([$fd, 'Hello Swoole!']); } else { $response = Swoole\Http\Response::create($fd); $pdo = new PDO( "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", MYSQL_SERVER_USER, MYSQL_SERVER_PWD ); $stmt = $pdo->query('SELECT "Hello Swoole!"'); Assert::assert($stmt->execute()); $ret = $stmt->fetchAll(PDO::FETCH_COLUMN)[0]; $response->end($ret); } }); $http->on('finish', function ($server, $taskId, $data) { list($fd, $ret) = $data; $response = Swoole\Http\Response::create($fd); $response->end($ret); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/task_enable_coroutine_return.phpt ================================================ --TEST-- swoole_server/task: task enable coroutine with return value to finish --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $ret = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); Assert::assert($ret === 'Hello Swoole!'); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'task_worker_num' => 4, 'task_enable_coroutine' => true ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); $http->task($response->fd, -1, function ($server, $taskId, $data) { list($fd, $data) = $data; $response = Swoole\Http\Response::create($fd); $response->end($data); }); }); $http->on('task', function (Swoole\Http\Server $server, Swoole\Server\Task $task) { defer(function ($data) { if (!empty($data)) { list($task, $result) = $data; $task->finish($result); } }); $fd = $task->data; $pdo = new PDO( "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", MYSQL_SERVER_USER, MYSQL_SERVER_PWD ); $stmt = $pdo->query('SELECT "Hello Swoole!"'); Assert::assert($stmt->execute()); $ret = $stmt->fetchAll(PDO::FETCH_COLUMN)[0]; return [$task, [$fd, $ret]]; }); $http->on('finish', function ($server, $taskId, $data) { list($fd, $ret) = $data; $response = Swoole\Http\Response::create($fd); $response->end($ret); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/task_in_manager.phpt ================================================ --TEST-- swoole_server/task: task in manager --SKIPIF-- --FILE-- setWaitTimeout(60); $pm->parentFunc = function ($pid) use ($pm) { echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', )); $serv->on("managerStart", function (Server $serv) use ($pm) { $serv->task(['type' => 'array', 'value' => 'manager']); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) use($pm) { Assert::false($serv->finish("OK")); $pm->wakeup(); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/task_in_master.phpt ================================================ --TEST-- swoole_server/task: task in master --SKIPIF-- --FILE-- setWaitTimeout(60); $pm->parentFunc = function ($pid) use ($pm) { echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', )); $serv->on("start", function (Server $serv) use ($pm) { $serv->task(['type' => 'array', 'value' => 'master']); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) use($pm) { Assert::false($serv->finish("OK")); $pm->wakeup(); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/task_in_task_worker.phpt ================================================ --TEST-- swoole_server/task: task in task worker --SKIPIF-- --FILE-- setWaitTimeout(60); $pm->parentFunc = function ($pid) use ($pm) { echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv, $wid) use ($pm) { if ($serv->taskworker) { Assert::false(@$serv->task(['type' => 'array', 'value' => 'task worker'])); $pm->wakeup(); } }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) use($pm) { }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/task_in_user_process.phpt ================================================ --TEST-- swoole_server/task: task in user process --SKIPIF-- --FILE-- setWaitTimeout(60); $pm->parentFunc = function ($pid) use ($pm) { echo "SUCCESS\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 2, 'log_file' => '/dev/null', )); $process = new \Swoole\Process(function ($process) use ($serv) { $serv->task(['type' => 'array', 'value' => 'user process']); sleep(60); }); $serv->addProcess($process); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) use($pm) { Assert::false($serv->finish("OK")); $pm->wakeup(); }); $serv->on('finish', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/task/task_ipc_mode_2.phpt ================================================ --TEST-- swoole_server/task: task_ipc_mode = 2 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; $pm->childFunc = function () use ($pm, $atomic) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'log_file' => '/dev/null', 'open_tcp_nodelay' => true, 'task_worker_num' => 4, 'worker_num' => 2, 'task_ipc_mode' => 2, 'dispatch_mode' => 2 ]); $server->on('workerStart', function () use ($pm, $atomic) { if ($atomic->add() == 6) { $pm->wakeup(); } }); $server->on('request', function (Request $request, Response $response) use ($server) { $response->detach(); $server->task($response->fd); }); $server->on('task', function ($server, $task_id, $worker_id, string $fd) { $response = Response::create($fd); $response->end("Hello Swoole!\n"); }); $server->on('finish', function () { }); $server->on('close', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Hello Swoole! ================================================ FILE: tests/swoole_server/task/task_ipc_mode_3.phpt ================================================ --TEST-- swoole_server/task: task_ipc_mode = 3 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'open_tcp_nodelay' => true, 'worker_num' => 3, 'task_worker_num' => 4, 'task_ipc_mode' => 3, 'dispatch_mode' => 2 ]); $server->on('workerStart', function () use ($pm, $atomic) { if ($atomic->add() == 7) { $pm->wakeup(); } }); $server->on('request', function (Request $request, Response $response) use ($server) { $response->detach(); $server->task($response->fd); }); $server->on('task', function ($server, $task_id, $worker_id, string $fd) { $response = Response::create($server, $fd); $response->end("Hello Swoole!\n"); }); $server->on('finish', function () { }); $server->on('close', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Hello Swoole! ================================================ FILE: tests/swoole_server/task/task_max_request.phpt ================================================ --TEST-- swoole_server/task: task_max_request --SKIPIF-- --FILE-- set([ 'worker_num' => 1, 'task_max_request' => 200, 'task_worker_num' => 4, 'log_file' => TEST_LOG_FILE, ]); $serv->on('WorkerStart', function (Server $serv, $worker_id) { if (!$serv->taskworker) { for ($i = 0; $i < N; $i++) { $serv->task(['type' => 'php', 'data' => RandStr::gen(100)]); } } else { global $counter3; $counter3->add(1); } }); $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { $serv->send($fd, "Server: {$data}"); }); $serv->on('Task', function (Server $serv, $task_id, $workerId, $data) { global $counter1; $counter1->add(1); return json_encode($data); }); $serv->on('Finish', function (Server $swooleServer, $workerId, $task_data) { global $counter2; $counter2->add(1); if ($counter2->get() == N) { $swooleServer->shutdown(); } }); $serv->start(); }, false, false); $process->start(); Process::wait(); Assert::same($counter1->get(), 4000); Assert::same($counter2->get(), 4000); Assert::assert($counter3->get() > 15); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/task_pack.phpt ================================================ --TEST-- swoole_server/task: task pack --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $task = new class(MSGQ_KEY, 0) { protected $queueId; protected $workerId; function __construct($key, $workerId) { $this->queueId = msg_get_queue($key); if ($this->queueId === false) { throw new \Swoole\Exception("msg_get_queue() failed."); } $this->workerId = $workerId; } function dispatch($data) { if (!msg_send($this->queueId, $this->workerId + 1, Swoole\Server\Task::pack($data), false)) { return false; } else { return true; } } }; //数组 $task->dispatch(array('data' => str_repeat('A', 1024), 'type' => 1)); //大包 $task->dispatch(array('data' => str_repeat('B', 1024 * 32), 'type' => 2)); //普通字符串 $task->dispatch(str_repeat('C', 512)); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 1, 'task_ipc_mode' => 3, 'message_queue_key' => MSGQ_KEY, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { global $result; switch ($task_id) { case 0: Assert::isArray($data); Assert::eq($data['type'], 1); Assert::length($data['data'], 1024); $result->add(1); break; case 1: Assert::isArray($data); Assert::eq($data['type'], 2); Assert::length($data['data'], 1024 * 32); $result->add(1); break; case 2: Assert::assert(is_string($data)); Assert::length($data, 512); $result->add(1); $serv->shutdown(); break; default: break; } }); $serv->start(); }; $pm->childFirst(); $pm->run(); Assert::eq($result->get(), 3); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/task_queue.phpt ================================================ --TEST-- swoole_server/task: task queue --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $pm->getFreePort(), 30) or die("ERROR"); $cli->send("task-01") or die("ERROR"); echo $cli->recv(); $cli->close(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { for ($i = 0; $i < 2048; $i++) { $data = array('id' => $i, 'fd' => $fd, 'data' => RandStr::getBytes(rand(2048, 4096))); if ($serv->task($data) === false) { $serv->send($fd, "ERROR\n"); return; } } }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { if ($task_id == 0) { sleep(1); } if ($task_id != $data['id']) { echo "ERROR, $task_id, {$data['id']}\n"; } if ($data['id'] == N - 1) { $serv->send($data['fd'], "OK"); } }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server/task/task_wait.phpt ================================================ --TEST-- swoole_server/task: taskwait in coroutine --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($n = MAX_REQUESTS; $n--;) { go(function () use ($pm, $n) { $c = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $c->set(['timeout' => 5,]); Assert::assert($c->get('/task?n='.$n)); Assert::same($c->body, "OK"); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm, $randoms) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'task_worker_num' => 1, ]); $server->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server, $randoms) { $n = $request->get['n']; list($ret_n, $ret_random) = $server->taskwait($n, 1); if ($ret_n !== $n) { $response->end("ERROR MATCH {$ret_n} with {$n}"); return; } elseif ($ret_random !== $randoms[$n]) { $response->end("ERROR EQUAL {$ret_n}(" . strlen($ret_random) . ") with {$n}(" . strlen($randoms[$n]) . ")"); return; } $response->end('OK'); }); $server->on('task', function (Swoole\Http\Server $server, int $task_id, int $worker_id, string $n) use ($pm, $randoms) { return [$n, $randoms[$n]]; }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/task/timer.phpt ================================================ --TEST-- swoole_server/task: timer --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { run(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['websocket_compression' => true]); $cli->upgrade('/'); $cli->push('Hello Swoole'); $data = $cli->recv(5); Assert::eq($data->data, 'OK'); echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $http->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'event_object' => true, 'log_file' => '/dev/null', ]); $http->on('message', function (Server $server, Frame $frame) { $server->task(['fd' => $frame->fd]); }); $http->on('WorkerStart', function (Server $server, int $workerId) { if ($server->taskworker) { Timer::after(1, function () use ($server, $workerId) { var_dump('after1 : ' . time()); }); // never callback Timer::after(10000, function () use ($server, $workerId) { var_dump('after2 : ' . time()); }); } }); $http->on('task', function (Server $server, Task $task) { var_dump('begin : ' . time()); Timer::after(2000, function () use ($server, $task) { var_dump('end : ' . time()); Assert::true($server->push($task->data['fd'], 'OK')); }); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- string(19) "after1 : %d" string(18) "begin : %d" string(16) "end : %d" DONE ================================================ FILE: tests/swoole_server/task/unpack.phpt ================================================ --TEST-- swoole_server/task: unpack --SKIPIF-- --FILE-- random_bytes(random_int(16, 2000)), 'msg' => 'data 3', 'int' => random_int(1, 9999999), 'uniq' => uniqid(), ]; $packed3 = Task::pack($data3); Assert::same($data3, Task::unpack($packed3)); $data4 = [ 'data' => random_bytes(random_int(9000, 2 * 1024 * 1024)), 'msg' => 'data 4', 'int' => random_int(1, 9999999), 'uniq' => uniqid(), ]; $packed4 = Task::pack($data4); Assert::same($data4, Task::unpack($packed4)); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/task/without_onfinish.phpt ================================================ --TEST-- swoole_server/task: task & finish --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { $ret = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); Assert::same($ret, 'Hello Swoole!'); }); } Swoole\Event::wait(); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'log_file' => '/dev/null', 'task_worker_num' => 4 ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); $http->task($response->fd); }); $http->on('task', function ($a, $b, $c, string $fd) { $response = Swoole\Http\Response::create($fd); $response->end('Hello Swoole!'); return null; // no on finish? }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server/taskWaitMulti.phpt ================================================ --TEST-- swoole_server: taskWaitMulti --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $port, 0.5) or die("ERROR"); $cli->send("task-01") or die("ERROR"); Assert::same($cli->recv(), 'OK'); $cli->send("task-02") or die("ERROR"); Assert::same($cli->recv(), 'OK'); $cli->close(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'task_worker_num' => 1, 'enable_coroutine' => random_int(0, 100) > 50, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { if ($data == 'task-01') { $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $results = $serv->taskWaitMulti($tasks, 2); } else { $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = 0; $results = $serv->taskWaitMulti($tasks, 0.2); } if (count($results) == 4) { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { if ($data == 0) { usleep(300000); } return $data; }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/taskwait_01.phpt ================================================ --TEST-- swoole_server: taskwait [blocking] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $port, 0.5) or exit('ERROR'); $cli->send('array-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('array-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('string-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('string-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('timeout') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { if ($data == 'array-01') { $res = $serv->taskwait(['type' => 'array', 'value' => $data]); if (!empty($res['name'])) { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'array-02') { $res = $serv->taskwait(['type' => 'string', 'value' => $data]); if ($res == "hello world\n") { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'string-01') { $res = $serv->taskwait('array'); if (!empty($res['name'])) { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'string-02') { $res = $serv->taskwait('string'); if ($res == "hello world\n") { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'timeout') { $res = $serv->taskwait('timeout', 0.2); if ($res === false) { $res = $serv->taskwait('string', 0.2); if ($res === "hello world\n") { $serv->send($fd, 'OK'); return; } } $serv->send($fd, 'ERR'); } }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { if (is_array($data)) { if ($data['type'] == 'array') { return ['name' => 'rango', 'year' => 1987]; } return "hello world\n"; } if ($data == 'array') { return ['name' => 'rango', 'year' => 1987]; } if ($data == 'string') { return "hello world\n"; } if ($data == 'timeout') { usleep(300000); return "task timeout\n"; } }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/taskwait_02.phpt ================================================ --TEST-- swoole_server: taskwait [coroutine] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($port) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $port, 0.5) or exit('ERROR'); $cli->send('array-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('array-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('string-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('string-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); $cli->send('timeout') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); ini_set('display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', 'enable_coroutine' => true, ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { if ($data == 'array-01') { $res = $serv->taskwait(['type' => 'array', 'value' => $data]); if (!empty($res['name'])) { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'array-02') { $res = $serv->taskwait(['type' => 'string', 'value' => $data]); if ($res == "hello world\n") { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'string-01') { $res = $serv->taskwait('array'); if (!empty($res['name'])) { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'string-02') { $res = $serv->taskwait('string'); if ($res == "hello world\n") { $serv->send($fd, 'OK'); } else { $serv->send($fd, 'ERR'); } } elseif ($data == 'timeout') { $res = $serv->taskwait('timeout', 0.2); if ($res === false) { $res = $serv->taskwait('string', 0.2); if ($res === "hello world\n") { $serv->send($fd, 'OK'); return; } } $serv->send($fd, 'ERR'); } }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { if (is_array($data)) { if ($data['type'] == 'array') { return ['name' => 'rango', 'year' => 1987]; } return "hello world\n"; } if ($data == 'array') { return ['name' => 'rango', 'year' => 1987]; } if ($data == 'string') { return "hello world\n"; } if ($data == 'timeout') { usleep(300000); return "task timeout\n"; } }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/unregistered_signal.phpt ================================================ --TEST-- swoole_server: unregistered signal --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $pid = file_get_contents(TEST_PID_FILE); usleep(1000); Swoole\Process::kill($pid, SIGPIPE); usleep(50000); $pm->kill(); usleep(50000); $log = file_get_contents(TEST_LOG_FILE); echo $log, "\n"; }; $pm->childFunc = function () use ($pm) { @unlink(TEST_LOG_FILE); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => TEST_LOG_FILE, 'pid_file' => TEST_PID_FILE, 'worker_num' => 1, ]); $server->on('WorkerStart', function (Swoole\Server $server, $worker_id) use ($pm) { $pm->wakeup(); }); $server->on('Receive', function (Swoole\Server $server, $fd, $reactorId, $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- [%s] WARNING %s (ERRNO 721): Unable to find callback function for signal Broken pipe: 13 ================================================ FILE: tests/swoole_server/unsock_dgram.phpt ================================================ --TEST-- swoole_server: unix socket dgram server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_UNIX_DGRAM, SWOOLE_SOCK_SYNC); $r = $client->connect(UNIXSOCK_PATH, 0, -1); if ($r === false) { echo "ERROR"; exit; } $client->send("SUCCESS"); echo $client->recv(); $client->close(); @unlink(UNIXSOCK_PATH); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(UNIXSOCK_PATH, 0, SWOOLE_PROCESS, SWOOLE_SOCK_UNIX_DGRAM); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null',]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("packet", function (Server $serv, $data, $addr) { $serv->send($addr['address'], 'SUCCESS'.PHP_EOL); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/unsock_stream.phpt ================================================ --TEST-- swoole_server: unix socket stream server --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_UNIX_STREAM, SWOOLE_SOCK_SYNC); $r = $client->connect(UNIXSOCK_PATH, 0, -1); if ($r === false) { echo "ERROR"; exit; } $client->send("SUCCESS"); usleep(100 * 1000); echo $client->recv() . "\n"; $client->close(); @unlink(UNIXSOCK_PATH); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(UNIXSOCK_PATH, 0, SWOOLE_BASE, SWOOLE_SOCK_UNIX_STREAM); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) { $serv->send($fd, 'SUCCESS'); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_server/user_process.phpt ================================================ --TEST-- swoole_server: user process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set(["open_eof_check" => true, "package_eof" => "\r\n\r\n"]); $r = $client->connect('127.0.0.1', $pm->getFreePort(), -1); if ($r === false) { echo "ERROR"; exit; } $client->send("SUCCESS"); for($i=0; $i < 20; $i++) { $ret = $client->recv(); Assert::same(strlen($ret), SIZE +4); } $client->close(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); $proc = new Swoole\Process(function ($process) use ($serv){ //echo posix_getpid()."\n"; while(true) { $data = json_decode($process->read(), true); //var_dump(SIZE); for($i=0; $i < 10; $i++) { $serv->send($data['fd'], str_repeat('A', SIZE)."\r\n\r\n"); //echo "user process send ok\n"; } } }, false, true); $serv->addProcess($proc); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) use ($proc) { $proc->write(json_encode(['fd' => $fd])); for($i=0; $i < 10; $i++) { $serv->send($fd, str_repeat('A', SIZE)."\r\n\r\n"); //echo "worker send ok\n"; } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/user_process_2.phpt ================================================ --TEST-- swoole_server: user process [with coroutine] --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); $process = new \Swoole\Process(function ($process) use ($serv, $pm) { for ($i = 0; $i < 5; $i++) { co::sleep(0.02); echo "$i OK\n"; } $pm->wakeup(); //kill by SIGTERM co::sleep(100); }, false, 0, true); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', ]); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) use ($process) { }); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 0 OK 1 OK 2 OK 3 OK 4 OK ================================================ FILE: tests/swoole_server/user_process_force_exit.phpt ================================================ --TEST-- swoole_server: process force exit --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', get_one_free_port(), SWOOLE_PROCESS, SWOOLE_SOCK_UDP); $server->set([ Constant::OPTION_LOG_FILE => '/dev/null', Constant::OPTION_MAX_WAIT_TIME => 1, ]); $server->on('packet', function () { }); $server->addProcess(new Swoole\Process(function () { pcntl_signal(SIGTERM, function () { }); Swoole\Timer::tick(1000, function () { }); })); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_server/user_process_pid.phpt ================================================ --TEST-- swoole_server: user process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $atomic) { $url = "http://127.0.0.1:" . $pm->getFreePort() . "/"; Assert::eq(file_get_contents($url), $atomic->get()); $pm->wait(); Assert::eq(file_get_contents($url), $atomic->get()); usleep(100000); $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); $process = new Process(function ($process) use ($serv, $pm, $atomic) { $atomic->set(posix_getpid()); usleep(100000); $pm->wakeup(); }); $serv->set([ 'worker_num' => 2, 'task_worker_num' => 3, 'log_file' => '/dev/null', ]); $serv->on('Request', function ($req, $resp) use ($serv) { $resp->end($serv->getWorkerPid(5)); }); $serv->on('task', function (){}); $serv->addProcess($process); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server/wrong_eof_setting.phpt ================================================ --TEST-- swoole_server: wrong eof setting --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { switch_process(); go(function () use ($pm) { $client = new Client(SWOOLE_SOCK_TCP); $client->set([ 'open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "", ]); $client->connect('127.0.0.1', $pm->getFreePort()); $client->send("Swoole\r\n\r\n"); }); Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $pm->wakeup(); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ "package_eof" => "", 'open_eof_check' => true, 'open_eof_split' => true, "worker_num" => 1 ]); $serv->on('workerStart', function (Server $serv) use ($pm) { }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, "hello {$data}\r\n\r\n"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- Fatal error: %s: package_eof cannot be an empty string in %s on line %d Fatal error: %s: package_eof cannot be an empty string in %s on line %d --EXPECTF_85-- Fatal error: %s: package_eof cannot be an empty string in %s on line %d Stack trace: #0 [internal function]: Swoole\Server\Port->set(Array) #1 %s(%d): Swoole\Server->set(Array) #2 [internal function]: {closure:%s:%d}() #3 %s/tests/include/lib/src/ProcessManager.php(%d): call_user_func(Object(Closure)) #4 %s/tests/include/lib/src/ProcessManager.php(%d): SwooleTest\ProcessManager->runChildFunc() #5 [internal function]: SwooleTest\ProcessManager->{closure:SwooleTest\ProcessManager::run():298}(Object(Swoole\Process)) #6 %s/tests/include/lib/src/ProcessManager.php(%d): Swoole\Process->start() #7 %s(%d): SwooleTest\ProcessManager->run() #8 {main} Fatal error: %s: package_eof cannot be an empty string in %s on line %d Stack trace: #0 %s(%d): Swoole\Coroutine\Client->connect('127.0.0.1', %d) #1 [internal function]: {closure:{closure:%s:%d}:%d}() #2 {main} ================================================ FILE: tests/swoole_server/z_conn_10k.phpt ================================================ --TEST-- swoole_server: 10k connections --SKIPIF-- &1` < MAX_CONCURRENCY_MID * MAX_REQUESTS) { skip('ulimit -n failed'); } ?> --FILE-- parentFunc = function () use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm, $c) { for ($n = MAX_REQUESTS; $n--;) { global $count, $client_map; $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $client_map["{$c}.{$n}"] = $client; if ($client->connect('127.0.0.1', $pm->getFreePort(), -1)) { if (Assert::assert($client->recv() === 'Hello Swoole!')) { if (++$count === MAX_CONCURRENCY_MID * MAX_REQUESTS) { var_dump($count); echo "DONE\n"; $client_map = []; $pm->kill(); } continue; } } else { echo "ERROR\n"; $pm->kill(); exit; } } }); } register_shutdown_function(function () { global $client_map; foreach ($client_map as $client) { $client->close(); } }); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ "worker_num" => swoole_cpu_num() * 2, 'log_file' => '/dev/null', 'max_connection' => MAX_CONCURRENCY_MID * MAX_REQUESTS ]); $server->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('connect', function (Swoole\Server $server, int $fd) { global $count; $count++; $server->send($fd, 'Hello Swoole!'); }); $server->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- int(%d) DONE ================================================ FILE: tests/swoole_server_coro/length_1.phpt ================================================ --TEST-- swoole_server_coro: (length protocol) 1 --SKIPIF-- --FILE-- wakeup(); } function onClose() { parent::onClose(); $this->serv->shutdown(); } } TestServer_5::$random_bytes = true; TestServer_5::$pkg_num = IS_IN_CI ? 1000 : 10000; $pm = new ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $bytes = 0; $pkg_bytes = 0; for ($i = 0; $i < TestServer_5::$pkg_num; $i++) { // if ($i % 1000 == 0) // { // echo "#{$i} send package. sid={$sid}, length=" . ($len + 10) . ", total bytes={$pkg_bytes}\n"; // } if (!$client->send(TestServer_5::getPacket())) { echo "send [$i] failed.\n"; break; } $bytes += 2; } $recv = $client->recv(); echo $recv; //echo "send ".TestServer::$PKG_NUM." packet sucess, send $bytes bytes\n"; $client->close(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $serv = new TestServer_5($pm->getFreePort(), false); $serv->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- end Total count=%d, bytes=%d ================================================ FILE: tests/swoole_server_coro/random_port.phpt ================================================ --TEST-- swoole_server_coro: bind random port --SKIPIF-- --FILE-- port > 0 and $server->port < 65535); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_server_coro/reuse_port.phpt ================================================ --TEST-- swoole_server_coro: reuse port --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR [1]\n"; return; } if (!$cli->send("hello\n")) { echo "ERROR [2]\n"; return; } $ret = $cli->recv(); if (!$ret) { echo "ERROR [3]\n"; return; } $result = unserialize($ret); if (!$result) { echo "ERROR [4]\n"; return; } $pids[$result['wid']] = 1; }); $sch->start(); Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $atomic = new Atomic(); $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { $server = new Server('127.0.0.1', $pm->getFreePort(), false, true); if ($atomic->add() == 2) { $pm->wakeup(); } Process::signal(SIGTERM, function () use ($server) { $server->shutdown(); }); $server->handle(function (Connection $conn) { co::sleep(0.005); $data = $conn->recv(); if (empty($data)) { $conn->close(); } $conn->send(serialize(['wid' => posix_getpid()])); }); $server->start(); }); $pool->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_server_coro/ssl.phpt ================================================ --TEST-- swoole_server_coro: ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); Assert::same($client->recv(), "Swoole hello world"); $pm->kill(); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server('0.0.0.0', $pm->getFreePort(), true); $server->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', 'ssl_key_file' => SSL_FILE_DIR.'/server.key', ]); $server->handle(function (Connection $conn) use ($server) { $data = $conn->recv(); $conn->send("Swoole $data"); $server->shutdown(); }); $pm->wakeup(); $server->start(); }); Swoole\Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server_coro/tcp.phpt ================================================ --TEST-- swoole_server_coro: tcp --SKIPIF-- --FILE-- handle(function (Connection $conn) use ($server) { $data = $conn->recv(); $json = json_decode($data, true); Assert::same($json['data'] ?? '', 'hello'); $conn->send("world\n"); $conn->close(); $server->shutdown(); }); $server->start(); }); go(function () { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', 9601)); $conn->send(json_encode(['data' => 'hello'])); echo $conn->recv(); }); Swoole\Event::wait(); ?> --EXPECT-- world ================================================ FILE: tests/swoole_server_port/connections.phpt ================================================ --TEST-- swoole_server_port: connections --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { $sch = new Scheduler(); $conns_1 = []; $conns_2 = []; $sch->parallel( 3, function () use ($pm, &$conns_1) { $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); $conns_1[] = $c->recv()->data; $c->recv(); } ); $sch->parallel( 2, function () use ($pm, &$conns_2) { $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(1)); $c->upgrade('/'); $conns_2[] = $c->recv()->data; $c->recv(); } ); // all $sch->add( function () use ($pm, &$conns_1, &$conns_2) { $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); $conns_1[] = $c->recv()->data; $c->push('all'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 8); $list1 = array_arrange($json->list); $list2 = array_arrange(array_merge($conns_1, $conns_2)); Assert::eq($list1, $list2); } ); // port-0 $sch->add( function () use ($pm, &$conns_1) { $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); $conns_1[] = $c->recv()->data; $c->push('port-0'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 5); Assert::eq($json->list, $conns_1); } ); // port-1 $sch->add( function () use ($pm, &$conns_2) { $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(1)); $c->upgrade('/'); $conns_2[] = $c->recv()->data; $c->push('port-1'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 3); Assert::eq($json->list, $conns_2); } ); $sch->add( function () use ($pm) { System::sleep(.5); $pm->kill(); } ); $sch->start(); }; $pm->childFunc = function () use ($pm) { $server = new Server('0.0.0.0', $pm->getFreePort(0), SWOOLE_PROCESS); $server->set([ Constant::OPTION_LOG_FILE => '/dev/null', Constant::OPTION_WORKER_NUM => 1, ]); $server->on('open', function (Server $server, $request) { $server->push($request->fd, $request->fd); }); $server->on( Constant::EVENT_WORKER_START, function () use ($pm) { $pm->wakeup(); } ); $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $server->on( 'message', function (Server $server, $frame) { if ($frame->data == 'all') { $iterator = $server->connections; } elseif ($frame->data == 'port-0') { $iterator = $server->ports[0]->connections; } elseif ($frame->data == 'port-1') { $iterator = $server->ports[1]->connections; } else { return; } $data['count'] = count($iterator); $data['list'] = array_values(iterator_to_array($iterator)); $server->push($frame->fd, json_encode($data)); } ); $server->on('close', function ($ser, $fd) {}); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server_port/duplicate_registered.phpt ================================================ --TEST-- swoole_server_port: duplicate registered --SKIPIF-- --FILE-- on('receive', function () { }); Assert::same(true, !!'load Assert'); $mem = null; for ($n = 1000; $n--;) { $server->on('receive', function () { }); if ($mem === null) { $mem = memory_get_usage(); } Assert::same(memory_get_usage(), $mem); } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_server_port/heartbeat_1.phpt ================================================ --TEST-- swoole_server_port: heartbeat 1 --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort(0)); for ($i = 0; $i < 2; ++$i) { $cli->send('hello'); $data = $cli->recv(); if ($data != 'ok') { echo "ERROR\n"; } System::sleep(2); } System::sleep(3); $cli->send('hello'); $data = $cli->recv(); if ($data == 'ok') { echo "ERROR\n"; } }); go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort(1)); for ($i = 0; $i < 2; ++$i) { $cli->send('hello'); $data = $cli->recv(); if ($data != 'ok') { echo "ERROR\n"; } System::sleep(2); } System::sleep(3); $cli->send('hello'); $data = $cli->recv(); if ($data == 'ok') { echo "ERROR\n"; } else { echo "OK"; } }); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->set([ 'heartbeat_check_interval' => 1, 'heartbeat_idle_time' => 3 ]); $server->on('receive', function (Server $server, $fd, $reactorId, $data) { $server->send($fd, 'ok'); }); $server->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $port2 = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); // $port2->set([ // 'heartbeat_check_interval' => 1, // 'heartbeat_idle_time' => 5 // ]); $port2->on('receive', function (Server $server, $fd, $reactorId, $data) { $server->send($fd, 'ok'); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server_port/heartbeat_2.phpt ================================================ --TEST-- swoole_server_port: heartbeat 2 --SKIPIF-- --FILE-- initFreePorts(3); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $test_func = function ($port_index, $sleep_seconds) use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort($port_index)); System::sleep($sleep_seconds); return $cli->recv(0.01); }; go(function () use ($test_func) { Assert::same($test_func(0, 2), ''); echo "DONE 0\n"; }); go(function () use ($test_func) { Assert::same($test_func(1, 3), ''); echo "DONE 1\n"; }); go(function () use ($test_func) { Assert::same($test_func(2, 3.5), false); echo "DONE 2\n"; }); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->set([ 'heartbeat_check_interval' => 1, 'heartbeat_idle_time' => 1, ]); $server->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $server->on('receive', function ($server, $fd, $reactorId, $data) { $server->send($fd, 'ok'); }); $port2 = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set([ 'heartbeat_idle_time' => 2, ]); $port3 = $server->listen('127.0.0.1', $pm->getFreePort(2), SWOOLE_SOCK_TCP); $port3->set([ 'heartbeat_idle_time' => 10, ]); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE 0 DONE 1 DONE 2 ================================================ FILE: tests/swoole_server_port/heartbeat_3.phpt ================================================ --TEST-- swoole_server_port: heartbeat 3 --SKIPIF-- --FILE-- initFreePorts(3); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $test_func = function ($port_index, $sleep_seconds) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort($port_index)); System::sleep($sleep_seconds); return $cli->recv(0.01); }; go(function () use ($test_func) { Assert::same($test_func(0, 3), false); echo "DONE 0\n"; }); go(function () use ($test_func) { Assert::same($test_func(1, 2.3), ''); echo "DONE 1\n"; }); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->on('receive', function ($server, $fd, $reactorId, $data) { $server->send($fd, 'ok'); }); $server->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $port2 = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set([ 'heartbeat_idle_time' => 2, ]); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE 1 DONE 0 ================================================ FILE: tests/swoole_server_port/http.phpt ================================================ --TEST-- swoole_server_port: http and tcp --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); if (!$cli->connect('127.0.0.1', $pm->getFreePort(0), 0.5)) { fail: echo "ERROR 1\n"; return; } //no eof, should be timeout here if (!$cli->send("hello\r\n\r\n")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } echo "OK\n"; }); go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(1)); if ($cli->get("/")) { Assert::same($cli->body, TEST_STR); Assert::same($cli->statusCode, 200); } else { echo "ERROR 2\n"; } }); Swoole\Event::wait(); Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->set([ 'open_eof_check' => true, "package_eof" => "\r\n\r\n", 'log_file' => '/dev/null' ]); $server->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data\r\n\r\n"); }); $port2 = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set(['open_http_protocol' => true,]); $port2->on("request", function ($req, $resp) { $resp->end(TEST_STR); }); $server->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end("OK\n"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_server_port/multi_port.phpt ================================================ --TEST-- swoole_server_port: swoole server port --SKIPIF-- --FILE-- connect($host, $port, 1); Assert::assert($r); if ($onConnect) { $onConnect($cli); } $recv = $cli->recv(); if ($onReceive) { $onReceive($cli, $recv); } }); } $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm, $port1, $port2, $port3) { makeTcpClient_without_protocol(TCP_SERVER_HOST, $port1, function(Client $cli) use($port1) { $r = $cli->send("$port1\r\n"); Assert::assert($r !== false); }, function(Client $cli, $data) use($port1) { Assert::same((int)$data, $port1); $cli->close(); }); makeTcpClient_without_protocol(TCP_SERVER_HOST, $port2, function(Client $cli) use($port2) { $r = $cli->send("$port2\n"); Assert::assert($r !== false); }, function(Client $cli, $data) use($port2) { Assert::same((int)$data, $port2); $cli->close(); }); makeTcpClient_without_protocol(TCP_SERVER_HOST, $port3, function(Client $cli) use($port1, $port3) { $r = $cli->send("$port3\r"); Assert::assert($r !== false); }, function(Client $cli, $data) use($port1, $port3) { Assert::same((int)$data, $port1); $cli->close(); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm, $port1, $port2, $port3) { $server = new Server('127.0.0.1', $port1, SWOOLE_PROCESS); $server->set(array( 'log_file' => '/dev/null', 'worker_num' => 1, )); $p2 = $server->listen('127.0.0.1', $port2, SWOOLE_SOCK_TCP); $p2->on('receive', function ($serv, $fd, $tid, $data) use ($port2) { $serv->send($fd, $port2); }); $server->listen('127.0.0.1', $port3, SWOOLE_SOCK_TCP); $server->on('Receive', function (Server $serv, $fd, $rid, $data) use ($port1) { $serv->send($fd, "$port1"); }); $server->on("WorkerStart", function (Server $serv) { /** * @var $pm SwooleTest\ProcessManager */ global $pm; $pm->wakeup(); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_server_port/sub_handshake.phpt ================================================ --TEST-- swoole_server_port: sub server use websocket and handshake --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(1)); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); $cli->push('Hello~'); $ret = $cli->recv(); var_dump($ret); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $main_server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $main_server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->write('hello world'); $response->end(); }); $sub_server = $main_server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $sub_server->set([ 'open_http_protocol' => true, 'open_websocket_protocol' => true ]); $sub_server->on('handshake', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $secWebSocketKey = $request->header['sec-websocket-key']; $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) { $response->end(); return false; } $key = base64_encode(sha1( $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true )); $headers = [ 'Upgrade' => 'websocket', 'Connection' => 'Upgrade', 'Sec-WebSocket-Accept' => $key, 'Sec-WebSocket-Version' => '13' ]; // failed: Error during WebSocket handshake: // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket if (isset($request->header['sec-websocket-protocol'])) { $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol']; } foreach ($headers as $key => $val) { $response->header($key, $val); } $response->status(101); $response->end(); return true; }); $sub_server->on('message', function (Swoole\Http\Server $server, Swoole\WebSocket\Frame $frame) { var_dump($frame); $response = new Swoole\WebSocket\Frame; $response->data = 'OK'; $server->send($frame->fd, (string)$response); }); $main_server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECTF-- object(Swoole\WebSocket\Frame)#%d (5) { ["fd"]=> int(1) ["data"]=> string(6) "Hello~" ["opcode"]=> int(1) ["flags"]=> int(%d) ["finish"]=> bool(true) } object(Swoole\WebSocket\Frame)#%d (5) { ["fd"]=> int(%d) ["data"]=> string(2) "OK" ["opcode"]=> int(1) ["flags"]=> int(%d) ["finish"]=> bool(true) } ================================================ FILE: tests/swoole_server_port/tcp_eof.phpt ================================================ --TEST-- swoole_server_port: tcp port with eof --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); if (!$cli->connect('127.0.0.1', $pm->getFreePort(1), 0.5)) { fail: echo "ERROR\n"; return; } //no eof, should be timeout here if (!$cli->send("hello\r\n\r\n")) { goto fail; } $ret = $cli->recv(); if (!$ret) { goto fail; } echo "OK\n"; Swoole\Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $port2 = $http->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $port2->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data\r\n\r\n"); }); $http->set(array( //'log_file' => '/dev/null' )); $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end("OK\n"); }); $http->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_socket_coro/accept.phpt ================================================ --TEST-- swoole_socket_coro: accept --SKIPIF-- --FILE-- bind('127.0.0.1', 9601)); Assert::assert($sock->listen(512)); $conn = $sock->accept(); Assert::assert($conn); Assert::isInstanceOf($conn, Swoole\Coroutine\Socket::class); $data = $conn->recv(); $json = json_decode($data, true); Assert::same($json['data'] ?? '', 'hello'); $conn->send("world\n"); $conn->close(); }); go(function () { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', 9601)); $conn->send(json_encode(['data' => 'hello'])); echo $conn->recv(); }); ?> --EXPECT-- world ================================================ FILE: tests/swoole_socket_coro/all.phpt ================================================ --TEST-- swoole_socket_coro: recv/send all --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn_map = []; while ($conn = $server->accept()) { Assert::assert($conn instanceof Co\Socket); Assert::assert($conn->fd > 0); $conn_map[$conn->fd] = $conn; go(function () use ($conn) { Assert::assert($conn instanceof Swoole\Coroutine\Socket); Assert::assert($conn->recvAll(BIG_RANDOM_DATA_LENGTH) === BIG_RANDOM_DATA); Assert::assert($conn->sendAll(BIG_RANDOM_DATA) === BIG_RANDOM_DATA_LENGTH); }); } }); go(function () use ($server, $port) { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $port)); Assert::assert($conn->sendAll(BIG_RANDOM_DATA) === BIG_RANDOM_DATA_LENGTH); Assert::assert($conn->recvAll(BIG_RANDOM_DATA_LENGTH) === BIG_RANDOM_DATA); Assert::assert($server->close()); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/bound_error.phpt ================================================ --TEST-- swoole_socket_coro: bound error --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen()); }); go(function () use ($port) { $cli = new Client(SWOOLE_SOCK_TCP); $ret = $cli->connect('127.0.0.1', $port); Assert::true($ret); go(function () use ($cli) { $cli->recv(); }); $cli->recv(); }); Event::wait(); ?> --EXPECTF-- Fatal error: Uncaught Swoole\Error: Socket#%d has already been bound to another coroutine#%d, reading of the same socket in coroutine#%d at the same time is not allowed in %s:%d Stack trace: #0 %s(%d): Swoole\Coroutine\Client->recv() #1 [internal function]: {%s}() #2 {main} thrown in %s on line %d [Coroutine-3] Stack trace: ------------------------------------------------------------------- #0 %s(%d): Swoole\Coroutine\Client->recv() #1 [internal function]: {%s}() ================================================ FILE: tests/swoole_socket_coro/cancel.phpt ================================================ --TEST-- swoole_socket_coro: cancel --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); //Server go(function () use ($socket) { while (true) { $peer = null; $data = $socket->recvfrom($peer); Assert::assert(empty($data)); Assert::assert($socket->errCode == SOCKET_ECANCELED); break; } echo "DONE\n"; }); //Client go(function () use ($socket) { co::sleep(0.1); $socket->cancel(); }); Swoole\Event::wait(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/check_writev_readv_param_type.phpt ================================================ --TEST-- swoole_socket_coro: check writev and readv param type --SKIPIF-- --FILE-- writeVectorAll($iovector); } catch (\Exception $e) { echo $e->getMessage() . PHP_EOL; } try { $iovector = [1, '!']; $conn->readVectorAll($iovector); } catch (\Exception $e) { echo $e->getMessage() . PHP_EOL; } }); echo "DONE\n"; ?> --EXPECTF-- Item #[0] must be of type string, %s given Item #[1] must be of type int, %s given DONE ================================================ FILE: tests/swoole_socket_coro/closed.phpt ================================================ --TEST-- swoole_socket_coro: closed bad fd --SKIPIF-- --FILE-- connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); Assert::assert($socket->close()); Assert::same($socket->errCode, 0); Assert::assert(!$socket->bind('127.0.0.1', 9501)); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->listen()); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->accept()); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->connect('127.0.0.1', 9501)); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->send(get_safe_random())); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->recv()); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->sendto('127.0.0.1', 9501, get_safe_random())); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->recvfrom($peer)); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->getsockname()); Assert::same($socket->errCode, SOCKET_EBADF); Assert::assert(!$socket->getpeername()); Assert::same($socket->errCode, SOCKET_EBADF); echo "DONE\n"; }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/complete_test.phpt ================================================ --TEST-- swoole_socket_coro: complete test server&&client&&timeout(millisecond) --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm, $port) { $socket = new Socket(AF_INET, SOCK_STREAM, 0); Assert::isInstanceOf($socket, Socket::class); Assert::same($socket->errCode, 0); go(function () use ($socket, $port) { Assert::assert($socket->connect('localhost', $port)); $i = 0.000; while (true) { $socket->send('hello'); $server_reply = $socket->recv(1024, 0.1); Assert::same($server_reply, 'swoole'); co::sleep($i += .001); // after 10 times we sleep 0.01s to trigger server timeout if ($i > .01) { break; } } co::sleep(0.5); echo "client exit\n"; $socket->close(); }); Event::wait(); }; $pm->childFunc = function () use ($pm, $port) { $socket = new Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->bind('127.0.0.1', $port)); Assert::assert($socket->listen(128)); go(function () use ($socket, $pm) { $pm->wakeup(); $client = $socket->accept(); Assert::assert($client, 'error: ' . swoole_last_error()); Assert::isInstanceOf($client, Socket::class); $i = 0; while (true) { $client_data = $client->recv(1024, 0.1); if ($client->errCode > 0) { Assert::same($client->errCode, SOCKET_ETIMEDOUT); break; } $i++; Assert::same($client_data, 'hello'); $client->send('swoole'); } echo "{$i}\n"; echo "sever exit\n"; usleep(1); $client->close(); $socket->close(); }); Event::wait(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 10 sever exit client exit ================================================ FILE: tests/swoole_socket_coro/concurrency.phpt ================================================ --TEST-- swoole_socket_coro: concurrency --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); $socket->listen(128); go(function () use ($socket) { for ($i = 0; $i < N; $i++) { $client = $socket->accept(); go(function () use ($client) { while (true) { $data = $client->recv(); if (empty($data)) { $client->close(); break; } $client->send("Server: $data"); } }); } }); for ($i = 0; $i < N; $i++) { go(function () { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $ret = $cli->connect('127.0.0.1', 9601); if ($ret) { $cli->send("hello\n"); Assert::same($cli->recv(), 'Server: hello'."\n"); $cli->close(); } else { echo "ERROR\n"; } }); } Swoole\Event::wait(); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/construct_parse_args_failed.phpt ================================================ --TEST-- swoole_socket_coro: construct parse arguments failed --SKIPIF-- --FILE-- --EXPECTF-- Fatal error: Uncaught ArgumentCountError: Swoole\Coroutine\Socket::__construct() expects at least 2 %s, 0 given in %s/tests/swoole_socket_coro/construct_parse_args_failed.php:3 Stack trace: #0 %s/tests/swoole_socket_coro/construct_parse_args_failed.php(3): Swoole\Coroutine\Socket->__construct() #1 {main} thrown in %s/tests/swoole_socket_coro/construct_parse_args_failed.php on line 3 ================================================ FILE: tests/swoole_socket_coro/fd.phpt ================================================ --TEST-- swoole_socket_coro: fd --SKIPIF-- --FILE-- 1) { Assert::assert(end($sockets)->fd === prev($sockets)->fd + 1); } } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/getBoundCid.phpt ================================================ --TEST-- swoole_socket_coro: getBoundCid --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $sock = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($sock->connect('127.0.0.1', $pm->getFreePort())); set_socket_coro_buffer_size($sock, 8192); $write_co = $read_co = -1; go(function () use ($pm, $sock, &$write_co, &$read_co) { Co::sleep(0.001); echo "CLOSE\n"; Assert::eq($sock->getBoundCid(SWOOLE_EVENT_READ), $read_co); Assert::eq($sock->getBoundCid(SWOOLE_EVENT_WRITE), $write_co); $sock->close(); $pm->kill(); echo "DONE\n"; }); $write_co = go(function () use ($sock) { echo "SEND\n"; $size = 16 * 1024 * 1024; Assert::lessThan($sock->sendAll(str_repeat('S', $size)), $size); Assert::eq($sock->errCode, SOCKET_ECANCELED); echo "SEND CLOSED\n"; }); $read_co = go(function () use ($sock) { echo "RECV\n"; Assert::false($sock->recv(-1)); Assert::eq($sock->errCode, SOCKET_ECANCELED); echo "RECV CLOSED\n"; }); }); }; $pm->childFunc = function () use ($pm) { Co\run(function () use ($pm) { $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); co::sleep(5); $conn->close(); } $server->close(); }); $pm->wakeup(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- SEND RECV CLOSE SEND CLOSED RECV CLOSED DONE ================================================ FILE: tests/swoole_socket_coro/getopt/get.phpt ================================================ --TEST-- swoole_socket_coro/getopt: getOption --SKIPIF-- --FILE-- setOption($level, IP_MULTICAST_TTL, 9); var_dump($r); $r = $socket->getOption($level, IP_MULTICAST_TTL); var_dump($r); echo "\n"; echo "Setting IP_MULTICAST_LOOP\n"; $r = $socket->setOption($level, IP_MULTICAST_LOOP, 0); var_dump($r); $r = $socket->getOption($level, IP_MULTICAST_LOOP); var_dump($r); $r = $socket->setOption($level, IP_MULTICAST_LOOP, 1); var_dump($r); $r = $socket->getOption($level, IP_MULTICAST_LOOP); var_dump($r); echo "\n"; echo "Setting IP_MULTICAST_IF\n"; echo "interface 0:\n"; $r = $socket->setOption($level, IP_MULTICAST_IF, 0); var_dump($r); $r = $socket->getOption($level, IP_MULTICAST_IF); var_dump($r); echo "interface 1:\n"; $r = $socket->setOption($level, IP_MULTICAST_IF, 1); var_dump($r); $r = $socket->getOption($level, IP_MULTICAST_IF); var_dump($r); echo "\n"; ?> --EXPECT-- Setting IP_MULTICAST_TTL bool(true) int(9) Setting IP_MULTICAST_LOOP bool(true) int(0) bool(true) int(1) Setting IP_MULTICAST_IF interface 0: bool(true) int(0) interface 1: bool(true) int(1) ================================================ FILE: tests/swoole_socket_coro/getopt/tcpinfo.phpt ================================================ --TEST-- swoole_socket_coro/getopt: tcp info --SKIPIF-- --FILE-- getOption(SOL_TCP, TCP_INFO); Assert::greaterThan($info['rcv_space'], 0); Assert::greaterThan($info['rto'], 0); Assert::greaterThan($info['rtt'], 0); Assert::greaterThan($info['snd_mss'], 0); Assert::greaterThan($info['rcv_mss'], 0); echo "DONE\n"; }); Assert::assert(strpos($content, 'map.baidu.com') !== false); }); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/getpeername.phpt ================================================ --TEST-- swoole_socket_coro: getpeername --SKIPIF-- --FILE-- connect('www.baidu.com', 80); $info = $conn->getpeername(); Assert::eq($info['address'], System::gethostbyname('www.baidu.com')); Assert::eq($info['port'], 80); } ); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/getsockname.phpt ================================================ --TEST-- swoole_socket_coro: getsockname --SKIPIF-- --FILE-- bind('127.0.0.1'); $info = $conn->getsockname(); Assert::same($info['address'] ?? '', '127.0.0.1'); Assert::greaterThan($info['port'], 0); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/icmp.phpt ================================================ --TEST-- swoole_socket_coro: icmp --SKIPIF-- --FILE-- connect($host)); $socket->send($package, strlen($package)); $pkt = $socket->recv(256); Assert::notEmpty($pkt); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/import_1.phpt ================================================ --TEST-- swoole_socket_coro: import 1 --SKIPIF-- --FILE-- send("test message"); $sock->close(); var_dump(stream_get_contents($s1)); }); ?> --EXPECTF-- string(12) "test message" ================================================ FILE: tests/swoole_socket_coro/import_2.phpt ================================================ --TEST-- swoole_socket_coro: import 2 --SKIPIF-- --FILE-- getMessage(), "\n"; } $s = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND); var_dump($s); var_dump(fclose($s)); try { Swoole\Coroutine\Socket::import($s); } catch (TypeError $e) { echo $e->getMessage(), "\n"; } echo "Done."; }); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::import(): Cannot represent a stream of type %s as a Socket Descriptor in %s on line %d bool(false) Swoole\Coroutine\Socket::import(): Argument #1 ($stream) must be of type resource, %s given resource(%d) of type (stream) bool(true) Swoole\Coroutine\Socket::import(): supplied resource is not a valid stream resource Done. ================================================ FILE: tests/swoole_socket_coro/import_3.phpt ================================================ --TEST-- swoole_socket_coro: import 3 --SKIPIF-- '224.0.0.23', "interface" => "lo", )); if ($so === false) { die("SKIP joining group 224.0.0.23 on interface lo failed"); } socket_close($s); ?> --FILE-- setOption(IPPROTO_IP, MCAST_JOIN_GROUP, array( "group" => '224.0.0.23', "interface" => "lo", )); var_dump($so); $sendsock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); Assert::isInstanceOf($sock, Swoole\Coroutine\Socket::class); $br = socket_bind($sendsock, '127.0.0.1'); $so = socket_sendto($sendsock, $m = "my message", strlen($m), 0, "224.0.0.23", 58379); var_dump($so); stream_set_blocking($stream, 0); var_dump(fread($stream, strlen($m))); echo "Done.\n"; }); ?> --EXPECTF-- bool(true) int(10) string(10) "my message" Done. ================================================ FILE: tests/swoole_socket_coro/import_4.phpt ================================================ --TEST-- swoole_socket_coro: import 4 --SKIPIF-- --FILE-- getMessage(), "\n"; } echo "\n"; } if ($sock !== null) { echo "socket_set_block "; try { print_r(intval(socket_set_block($sock))); } catch (Error $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; } echo "\n"; echo "socket_get_option "; try { print_r(intval(socket_get_option($sock, SOL_SOCKET, SO_TYPE))); } catch (Error $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; } echo "\n"; } echo "\n"; } Co\run(function () { echo "normal\n"; $stream0 = stream_socket_server("udp://0.0.0.0:0", $errno, $errstr, STREAM_SERVER_BIND); $sock0 = Swoole\Coroutine\Socket::import($stream0); test($stream0, $sock0); echo "\nunset stream\n"; $stream1 = stream_socket_server("udp://0.0.0.0:0", $errno, $errstr, STREAM_SERVER_BIND); $sock1 = Swoole\Coroutine\Socket::import($stream1); unset($stream1); test(null, $sock1); echo "\nunset socket\n"; $stream2 = stream_socket_server("udp://0.0.0.0:0", $errno, $errstr, STREAM_SERVER_BIND); $sock2 = Swoole\Coroutine\Socket::import($stream2); unset($sock2); test($stream2, null); echo "\nclose stream\n"; $stream3 = stream_socket_server("udp://0.0.0.0:0", $errno, $errstr, STREAM_SERVER_BIND); $sock3 = Swoole\Coroutine\Socket::import($stream3); fclose($stream3); test($stream3, $sock3); echo "\nclose socket\n"; $stream4 = stream_socket_server("udp://0.0.0.0:0", $errno, $errstr, STREAM_SERVER_BIND); $sock4 = Swoole\Coroutine\Socket::import($stream4); socket_close($sock4); test($stream4, $sock4); echo "Done.\n"; }); ?> --EXPECTF-- normal stream_set_blocking 1 socket_set_block 1 socket_get_option 2 unset stream socket_set_block 1 socket_get_option 2 unset socket stream_set_blocking 1 close stream stream_set_blocking TypeError: stream_set_blocking(): supplied resource is not a valid stream resource socket_set_block 1 socket_get_option 2 close socket stream_set_blocking TypeError: stream_set_blocking(): supplied resource is not a valid stream resource socket_set_block 0 socket_get_option 0 Done. --EXPECTF_85-- normal stream_set_blocking 1 socket_set_block 1 socket_get_option 2 unset stream socket_set_block 1 socket_get_option 2 unset socket stream_set_blocking 1 close stream stream_set_blocking TypeError: stream_set_blocking(): Argument #1 ($stream) must be an open stream resource socket_set_block 1 socket_get_option 2 close socket stream_set_blocking TypeError: stream_set_blocking(): Argument #1 ($stream) must be an open stream resource socket_set_block 0 socket_get_option 0 Done. ================================================ FILE: tests/swoole_socket_coro/import_5.phpt ================================================ --TEST-- swoole_socket_coro: import 5 --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/iov_max.phpt ================================================ --TEST-- swoole_socket_coro: iov max --SKIPIF-- --FILE-- writeVectorAll($iovector)); Assert::eq($conn->errCode, SOCKET_EINVAL); Assert::eq($conn->errMsg, "The maximum of iov count is " . SWOOLE_IOV_MAX); Assert::false($conn->readVectorAll($iovectorLength)); Assert::eq($conn->errCode, SOCKET_EINVAL); Assert::eq($conn->errMsg, "The maximum of iov count is " . SWOOLE_IOV_MAX); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/peek_and_checkLiveness.phpt ================================================ --TEST-- swoole_socket_coro: peek and checkLiveness --SKIPIF-- --FILE-- initRandomDataEx(MAX_CONCURRENCY_MID, 1, 1024); $pm->parentFunc = function () use ($pm) { Coroutine\run(function () use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { Coroutine::create(function () use ($pm, $c) { $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($socket->connect('127.0.0.1', $pm->getFreePort())); $random = get_safe_random(); Assert::same($socket->sendAll($random), strlen($random)); for ($n = 100; $n--;) { Coroutine::sleep(0.001); $data = $socket->peek(strlen($random)); if ($data === $random) { break; } } Assert::greaterThan($n, 0); /* clean the socket buffer */ $socket->recv(); /* then we check the liveness */ Assert::false($socket->checkLiveness()); }); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('Receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { $server->send($fd, $data); $server->close($fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/protocol/bug_3586.phpt ================================================ --TEST-- swoole_socket_coro/protocol: bug GitHub#3586 --SKIPIF-- --FILE-- bind('127.0.0.1')); $server->setProtocol([ 'open_length_check' => true, 'package_length_func' => function (string $data) { if (strlen($data) < strlen(GREETER)) { return 0; } else { return strlen(GREETER); } } ]); Assert::assert($server->listen()); while (true) { $client = $server->accept(-1); if (!$client) { break; } /* @var $client Socket */ Coroutine::create(function () use ($client, $server) { Assert::isInstanceOf($client, Socket::class); Assert::eq($client->recvPacket(), GREETER); Assert::greaterThan($client->sendAll(GREETER), 1); $client->close(); $GLOBALS['counter'] ++; if ($GLOBALS['counter'] == CO_NUM) { $server->close(); } }); } }); for ($i = 0; $i < CO_NUM; $i++) { Coroutine::create(function () use ($server) { $client = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($client->connect('127.0.0.1', $server->getsockname()['port'])); if (rand(0, 5) > 3) { $client->send(substr(GREETER, 0, 5)); Co::sleep(0.1); $client->send(substr(GREETER, 5)); } else { $client->send(GREETER); } Assert::eq($client->recvPacket(), GREETER); $client->close(); }); } }); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/protocol/package_length_func.phpt ================================================ --TEST-- swoole_socket_coro/protocol: package_length_func --SKIPIF-- --FILE-- bind('127.0.0.1')); Assert::assert($server->listen()); while (true) { $client = $server->accept(-1); if (!$client) { break; } /* @var $client Socket */ Coroutine::create(function () use ($client) { Assert::isInstanceOf($client, Socket::class); for ($n = 0; $n < strlen(GREETER); $n++) { Assert::eq($client->sendAll(GREETER[$n]), 1); Coroutine::sleep(0.01); } $client->close(); }); } $server->close(); }); Coroutine::create(function () use ($server) { $client = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($client->connect('127.0.0.1', $server->getsockname()['port'])); $client->setProtocol([ 'open_length_check' => true, 'package_length_func' => function (string $data) { if (strlen($data) < strlen(GREETER)) { return 0; } else { return strlen(GREETER); } } ]); var_dump($client->recvPacket()); $client->close(); $server->close(); }); }); ?> --EXPECT-- string(12) "Hello Swoole" ================================================ FILE: tests/swoole_socket_coro/readVectorAll.phpt ================================================ --TEST-- swoole_socket_coro: readVector all --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn = $server->accept(); Assert::assert($conn instanceof Socket); Assert::assert($conn->fd > 0); global $packedStr, $iovector, $totalLength2; Assert::assert($conn instanceof Socket); $iov = []; for ($i = 0; $i < 10; $i++) { $iov[] = 1024 * 128; } Assert::eq($conn->readVectorAll($iov), $iovector); Assert::eq(implode('', $conn->readVectorAll($iov)), substr($packedStr, 0, $totalLength2)); // has close Assert::eq($conn->readVectorAll($iov), []); }); go(function () use ($server, $port) { global $packedStr, $totalLength, $totalLength2; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $port)); $ret = $conn->sendAll($packedStr); Assert::eq($ret, $totalLength); $ret = $conn->sendAll(substr($packedStr, 0, $totalLength2)); Assert::eq($ret, $totalLength2); /** *The TCP three-way handshake is handled by the kernel. After connect() succeeds, * the server coroutine's accept() function does not return immediately. * The client coroutine continues to execute sendAll() until an IO block occurs; * only when a writable event is detected does it switch to the server coroutine, at which point accept() returns and proceeds to execute readVectorAll(). * If $server->close() is called, it may cause the server coroutine's accept() to return false. */ $conn->close(); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/readVectorAll_ssl.phpt ================================================ --TEST-- swoole_socket_coro: readVectorAll with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); $ret = $conn->sendAll($packedStr); Assert::eq($ret, $totalLength); $conn->recv(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); $iov = []; for ($i = 0; $i < 10; $i++) { $iov[] = 1024 * 128; } Assert::eq($conn->readVectorAll($iov), $iovector); $conn->send('close'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/readVector_ssl.phpt ================================================ --TEST-- swoole_socket_coro: readVector with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); $ret = $conn->send('helloworld'); Assert::eq($ret, strlen('helloworld')); $conn->recv(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Assert::eq($conn->readVector([5, 5]), ['hello', 'world']); $conn->send('close'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/readVector_ssl_eagain.phpt ================================================ --TEST-- swoole_socket_coro: readVector_ssl eagain --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); Coroutine::sleep(0.5); $ret = $conn->sendAll($packedStr); Assert::eq($ret, $totalLength); $conn->recv(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); $iov = []; for ($i = 0; $i < 10; $i++) { $iov[] = 1024 * 128; } Assert::eq($conn->readVectorAll($iov), $iovector); $conn->send('close'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/readv.phpt ================================================ --TEST-- swoole_socket_coro: readv test --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->connect('127.0.0.1', $pm->getFreePort()); $conn->send('hello'); $iov = [5, 5]; $ret = $conn->readVector($iov); Assert::same($ret, ['hello', 'world']); $conn->send('hello'); $iov = [5, 7]; $ret = $conn->readVector($iov); Assert::same($ret, ['hello', 'world']); $conn->send('hello'); $iov = [5, 7, 7]; $ret = $conn->readVector($iov); Assert::same($ret, ['hello', 'world']); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), false); $server->handle(function (Connection $conn) use ($server) { while (true) { $conn->recv(); $conn->send("helloworld"); } }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/readv_eagain.phpt ================================================ --TEST-- swoole_socket_coro: readv with eagain --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn = $server->accept(); Assert::assert($conn instanceof Socket); Assert::assert($conn->fd > 0); global $packedStr, $iovector, $totalLength2; Assert::assert($conn instanceof Socket); $iov = []; for ($i = 0; $i < 10; $i++) { $iov[] = 1024 * 128; } Assert::eq($conn->readVectorAll($iov), $iovector); Assert::eq(implode('', $conn->readVectorAll($iov)), substr($packedStr, 0, $totalLength2)); // has close Assert::eq($conn->readVectorAll($iov), []); }); go(function () use ($server, $port) { global $packedStr, $totalLength, $totalLength2; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $port)); // Let readVectorAll trigger EAGAIN (verify the correctness of the error returned by readVectorAll) Coroutine::sleep(0.5); $ret = $conn->sendAll($packedStr); Assert::eq($ret, $totalLength); $ret = $conn->sendAll(substr($packedStr, 0, $totalLength2)); Assert::eq($ret, $totalLength2); $server->close(); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/recvAll.phpt ================================================ --TEST-- swoole_socket_coro: recvAll --SKIPIF-- --FILE-- initRandomDataArray(4, 512 * 1024); const CASE_LIST = '4'; $pm->parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $pm->getFreePort())); /** * Case1,第一次接收到少量內容,随后分多次触发可读事件接收更多数据片 */ if (swoole_string(CASE_LIST)->split(',')->contains(1, false)) { $conn->send("case-1"); Co::sleep(0.001); $header = $conn->recvAll(4); $len = unpack('Nlen', $header)['len']; $body = $conn->recvAll($len); Assert::eq($body, $pm->getRandomDataElement(0)); } /** * Case2,第一次未接收到任何内容,返回 EAGAIN,监听可读,随后分多次触发可读事件接收更多数据片 */ if (swoole_string(CASE_LIST)->split(',')->contains(2, false)) { $conn->send("case-2"); $header = $conn->recvAll(4); $len = unpack('Nlen', $header)['len']; $body = $conn->recvAll($len); Assert::eq($body, $pm->getRandomDataElement(1)); } /** * Case3,第一次收到少量内容,第二次服务端关闭连接 */ if (swoole_string(CASE_LIST)->split(',')->contains(3, false)) { $conn->send("case-3"); Co::sleep(0.001); $header = $conn->recvAll(4); $len = unpack('Nlen', $header)['len']; $body = $conn->recvAll($len); Assert::eq($body, substr($pm->getRandomDataElement(2), 0,1024)); } /** * Case4,接收4次,第5次服务端关闭连接 */ if (swoole_string(CASE_LIST)->split(',')->contains(4, false)) { $conn->send("case-4"); Co::sleep(0.001); $header = $conn->recvAll(4); $len = unpack('Nlen', $header)['len']; $body = $conn->recvAll($len); Assert::eq($body, substr($pm->getRandomDataElement(3), 0, 65536 * 3)); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('Receive', function (Swoole\Server $server, $fd, $rid, $data) use ($pm) { if ($data == 'case-1') { $body = $pm->getRandomDataElement(0); $data = pack('N', strlen($body)) . $body; $server->send($fd, substr($data, 0, 1024)); usleep(40000); $server->send($fd, substr($data, 1024)); } elseif ($data == 'case-2') { $body = $pm->getRandomDataElement(1); $server->send($fd, pack('N', strlen($body))); usleep(40000); $server->send($fd, $body); } elseif ($data == 'case-3') { $body = $pm->getRandomDataElement(2); $server->send($fd, pack('N', strlen($body))); $server->send($fd, substr($body, 0, 1024)); usleep(40000); $server->close($fd); } elseif ($data == 'case-4') { $body = $pm->getRandomDataElement(3); $server->send($fd, pack('N', strlen($body))); for ($i = 0; $i < 3; $i++) { $server->send($fd, substr($body, $i * 65536, 65536)); usleep(40000); } $server->close($fd); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/recvAll_timeout.phpt ================================================ --TEST-- swoole_socket_coro: recvAll timeout (slow network) --SKIPIF-- --FILE-- initRandomDataEx(MAX_CONCURRENCY_MID, 1, 1024); $pm->parentFunc = function ($pid) use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm, $c) { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $pm->getFreePort())); $conn->send($c); $timeout = ms_random(0.1, 1); $s = microtime(true); $data = $conn->recvAll(1024, $timeout); time_approximate($timeout, microtime(true) - $s); Assert::assert(strlen($data) > 0); Assert::assert(strlen($data) != 1024); Assert::assert(strpos($pm->getRandomDataEx($c), $data) === 0); Assert::assert($conn->errCode == SOCKET_ETIMEDOUT); Assert::assert($conn->errMsg == swoole_strerror(SOCKET_ETIMEDOUT)); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('Receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { $s = $pm->getRandomDataEx($data); while ($server->exists($fd)) { $server->send($fd, string_pop_front($s, 1)); Co::sleep(0.005); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/recv_bad_packet.phpt ================================================ --TEST-- swoole_socket_coro: recv bad packet --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i) { $cli = new Co\Socket(AF_INET, SOCK_STREAM, 0); $cli->setProtocol([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $pm->getFreePort()) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < 3; $i++) { $sid = strval(rand(100000, 999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recvPacket(0.2); Assert::isEmpty($data); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 4, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $len = rand(1024, 8192); $send_data = str_repeat('A', $len); //bad packet $serv->send($fd, pack('N', SIZE) . $send_data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/recv_line.phpt ================================================ --TEST-- swoole_socket_coro: recv line --SKIPIF-- --FILE-- handle(function (Connection $conn) use ($server) { $conn->send("hello world\n"); $conn->send("\r"); $conn->send(str_repeat('A', 128) . str_repeat('B', 125)); $server->shutdown(); }); $server->start(); }); $cli = new Co\Socket(AF_INET, SOCK_STREAM, 0); if ($cli->connect('127.0.0.1', $port) == false) { echo "ERROR\n"; return; } Assert::eq($cli->recvLine(128), "hello world\n"); Assert::eq($cli->recvLine(128), "\r"); Assert::eq($cli->recvLine(128), str_repeat('A', 128)); Assert::eq($cli->recvLine(128), str_repeat('B', 125)); Assert::eq($cli->recvLine(128), ""); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/recv_timeout.phpt ================================================ --TEST-- swoole_socket_coro: recv timeout --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm) { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $pm->getFreePort())); $conn->send(json_encode(['data' => 'hello'])); $timeout = ms_random(0.1, 1); $s = microtime(true); $case = mt_rand(0, 2); switch ($case) { case 0: $ret = $conn->recv(1024, $timeout); break; case 1: $ret = $conn->recvAll(1024, $timeout); break; case 2: $ret = $conn->recvfrom($peer, $timeout); break; } time_approximate($timeout, microtime(true) - $s); Assert::assert($ret === false, "[case=$case]"); Assert::assert($conn->errCode == SOCKET_ETIMEDOUT); }); } Swoole\Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('Receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/recv_with_buffer.phpt ================================================ --TEST-- swoole_socket_coro: recv with buffer --SKIPIF-- --FILE-- handle(function (Connection $conn) use ($server) { $conn->send(DATA); $server->shutdown(); }); $server->start(); }); $cli = new Co\Socket(AF_INET, SOCK_STREAM, 0); if ($cli->connect('127.0.0.1', $port) == false) { echo "ERROR\n"; return; } $data = ''; while (1) { $char = $cli->recvWithBuffer(1); if (strlen($char) == 1) { $data .= $char; } else { break; } } Assert::eq($data, DATA); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/reuse.phpt ================================================ --TEST-- swoole_socket_coro: reuse socket object --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { echo "Co [1]\n"; $map = []; Co\Run(function () use ($pm, &$map) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->send(SEND_STR)); echo $socket->recv(); $map['sock'] = $socket; }); echo "Co [2]\n"; Co\Run(function () use ($pm, &$map) { $socket = $map['sock']; Assert::assert($socket->send(SEND_STR)); echo $socket->recv(); unset($map['sock']); }); }; $pm->childFunc = function () use ($pm) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(128)); $pm->wakeup(); go(function () use ($socket, $pm) { $client = $socket->accept(); Assert::isInstanceOf($client, Swoole\Coroutine\Socket::class); while (true) { $client_data = $client->recv(1024, -1); if (empty($client_data)) { echo "closed\n"; break; } if ($client->errCode > 0) { Assert::same($client->errCode, SOCKET_ETIMEDOUT); break; } else { Assert::same($client_data, SEND_STR); $client->send('swoole '.SEND_STR); } } $client->close(); $socket->close(); echo "server exit\n"; }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Co [1] swoole hello world Co [2] swoole hello world closed server exit ================================================ FILE: tests/swoole_socket_coro/reuse_2.phpt ================================================ --TEST-- swoole_socket_coro: reuse socket object [2] --SKIPIF-- --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { $map = []; $serv = new Server('127.0.0.1', $pm->getFreePort(1), SWOOLE_BASE); $serv->on(Constant::EVENT_WORKER_START, function (Server $server) use ($pm, &$map) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->send(SEND_STR)); echo $socket->recv(); $map['sock'] = $socket; $server->shutdown(); }); $serv->on(Constant::EVENT_RECEIVE, function () { }); echo "Server start [1]\n"; $serv->start(); echo "Server stop [1]\n"; echo "Co [2]\n"; Co\Run(function () use ($pm, &$map) { $socket = $map['sock']; Assert::assert($socket->send(SEND_STR)); echo $socket->recv(); unset($map['sock']); }); }; $pm->childFunc = function () use ($pm) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(128)); $pm->wakeup(); go(function () use ($socket, $pm) { $client = null; while (true) { $client = $socket->accept(); if ($client) { break; } } Assert::isInstanceOf($client, Swoole\Coroutine\Socket::class); while (true) { $client_data = $client->recv(1024, -1); if (empty($client_data)) { echo "closed\n"; break; } if ($client->errCode > 0) { Assert::same($client->errCode, SOCKET_ETIMEDOUT); break; } else { Assert::same($client_data, SEND_STR); $client->send('swoole '.SEND_STR); } } $client->close(); $socket->close(); echo "server exit\n"; }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- Server start [1] swoole hello world Server stop [1] Co [2] swoole hello world closed server exit ================================================ FILE: tests/swoole_socket_coro/sendfile.phpt ================================================ --TEST-- swoole_socket_coro: sendfile --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { Co\Run(function () use ($pm) { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $pm->getFreePort())); $conn->send(pack('N', filesize(TEST_IMAGE))); $ret = $conn->sendfile(TEST_IMAGE); Assert::assert($ret); $data = $conn->recv(); $conn->send(pack('N', 8) . 'shutdown'); $conn->close(); Assert::same($data, md5_file(TEST_IMAGE)); }); }; $pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, 'log_file' => '/dev/null', 'open_length_check' => true, 'dispatch_mode' => 1, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 2000000, ]); $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on("Receive", function (Server $serv, $fd, $rid, $data) { if (substr($data, 4, 8) == 'shutdown') { $serv->shutdown(); return; } $serv->send($fd, md5(substr($data, 4))); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/sendto.phpt ================================================ --TEST-- swoole_socket_coro: sendto --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); echo "[Server] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n"; $socket->sendto($peer['address'], $peer['port'], "Swoole: $data"); } }); //Client go(function () { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); for ($i = 0; $i < N; $i++) { $socket->sendto('127.0.0.1', 9601, "hello-".$i); $peer = null; $data = $socket->recvfrom($peer); echo "[Client] recvfrom[{$peer['address']}:{$peer['port']}] : $data\n"; } }); Swoole\Event::wait(); ?> --EXPECTF-- [Server] recvfrom[127.0.0.1:%d] : hello-0 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-0 [Server] recvfrom[127.0.0.1:%d] : hello-1 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-1 [Server] recvfrom[127.0.0.1:%d] : hello-2 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-2 [Server] recvfrom[127.0.0.1:%d] : hello-3 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-3 [Server] recvfrom[127.0.0.1:%d] : hello-4 [Client] recvfrom[127.0.0.1:9601] : Swoole: hello-4 ================================================ FILE: tests/swoole_socket_coro/sendto_big.phpt ================================================ --TEST-- swoole_socket_coro: sendto --SKIPIF-- --FILE-- bind('/tmp/test-server.sock', 0); $data = $socket->recvfrom($peer); if (!$data) { return; } Assert::same($data, 'hello'); Assert::same($peer['address'], '/tmp/test-client.sock'); Assert::same($peer['port'], 0); global $randomData; for ($n = 0; $n < MAX_CONCURRENCY * MAX_REQUESTS; $n++) { $chunk = get_safe_random(1024); $randomData .= $chunk; Assert::same($socket->sendto($peer['address'], $peer['port'], $chunk), strlen($chunk)); } // close Assert::same($socket->sendto($peer['address'], $peer['port'], ''), 0); }); go(function () { $socket = new Swoole\Coroutine\Socket(AF_UNIX, SOCK_DGRAM, IPPROTO_IP); $socket->bind('/tmp/test-client.sock', 0); $socket->sendto('/tmp/test-server.sock', 0, 'hello'); $data = ''; while (true) { $tmp = $socket->recvfrom($peer); if (empty($tmp)) { break; } $data .= $tmp; } global $randomData; if (Assert::same($data, $randomData)) { echo "OK\n"; } }); }); ?> --EXPECT-- OK ================================================ FILE: tests/swoole_socket_coro/sendto_large_packet.phpt ================================================ --TEST-- swoole_socket_coro: send large packet --SKIPIF-- --FILE-- bind('127.0.0.1', 9601); for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); Assert::assert($data); Assert::assert($socket->sendto($peer['address'], $peer['port'], "Swoole: {$data}")); Assert::assert(strlen($data) >= MIN_PACKET_SIZE); Assert::assert(is_array($peer)); } }); // Client go(function () { $socket = new Socket(AF_INET, SOCK_DGRAM, 0); for ($i = 0; $i < N; $i++) { Assert::assert($socket->sendto('127.0.0.1', 9601, str_repeat('A', rand(MIN_PACKET_SIZE, MAX_PACKET_SIZE))), 'error: ' . swoole_strerror($socket->errCode)); $peer = null; $data = $socket->recvfrom($peer); Assert::assert($data); Assert::assert(is_array($peer)); Assert::assert(strlen($data) >= MIN_PACKET_SIZE); } }); Event::wait(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_socket_coro/server_accept.phpt ================================================ --TEST-- swoole_socket_coro: server accept --SKIPIF-- --FILE-- setProtocol([ 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => Protocol::LENGTH_OFFSET, 'package_body_offset' => Protocol::HEAD_LENGTH ]); if (!$server->bind('127.0.0.1')) { throw new Exception('Bind failed: ' . $server->errMsg); } if (!$server->listen()) { throw new Exception('Listen failed: ' . $server->errMsg); } $port = $server->getsockname()['port'] ?? 0; if (!$port) { throw new Exception('No port'); } for ($n = MAX_CONCURRENCY; $n--;) { Coroutine::create(function () use ($port) { Co::sleep(0.01); $client = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $connected = $client->connect('127.0.0.1', $port, 3); if (!$connected) { throw new Exception('Connect failed: ' . $client->errMsg); } for ($n = MAX_REQUESTS; $n--;) { $bytes = mt_rand(2, 1024); $random = $bytes ? get_safe_random($bytes - 1) . 'S' : ''; $data = Protocol::pack(mt_rand(0, 127), mt_rand(0, 4294967295), $random); if ($client->sendAll($data) !== strlen($data)) { throw new Exception('Send failed: ' . $client->errMsg); } $head = $client->recvAll(Protocol::HEAD_LENGTH); if (strlen($head) !== Protocol::HEAD_LENGTH) { throw new Exception('Recv head failed: ' . $client->errMsg); } $head = Protocol::unpack($head); $length = $head['length']; if ($length !== 0) { $body = $client->recvAll($length, -1); if (strlen($body) !== $length) { throw new Exception('Recv body failed: ' . $client->errMsg); } if (Assert::same($body[strlen($body) - 1], 'W')) { $body[strlen($body) - 1] = 'S'; } Assert::same($body, $random); } } $client->close(); }); } Coroutine::create(function () use ($server) { while (true) { Coroutine::sleep(0.1); if (Coroutine::stats()['coroutine_num'] === 2) { $server->close(); break; } } }); while (true) { /* @var $client Socket */ $client = $server->accept(-1); if (!$client) { break; } go(function () use ($client) { while (true) { $packet = $client->recvPacket(-1); if (!$packet) { /* Connection closed */ break; } if (strlen($packet) > Protocol::HEAD_LENGTH) { Assert::same($packet[strlen($packet) - 1], 'S'); $packet[strlen($packet) - 1] = 'W'; } $client->sendAll($packet); } }); } }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/setopt/bindtodevice.phpt ================================================ --TEST-- swoole_socket_coro/setopt:SO_BINDTODEVICE --SKIPIF-- --FILE-- setOption(SOL_SOCKET, SO_BINDTODEVICE, "lo"); Assert::assert($retval_1 === true); $retval_2 = $socket->setOption(SOL_SOCKET, SO_BINDTODEVICE, "ethIDONOTEXIST"); Assert::assert($retval_2 === false); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): setsockopt(%d) failed, Error: No such device[%d] in %s on line %d ================================================ FILE: tests/swoole_socket_coro/setopt/ipv6_pktinfo.phpt ================================================ --TEST-- swoole_socket_coro/setopt: setOption IPV6_PKTINFO --SKIPIF-- --FILE-- setOption(IPPROTO_IPV6, IPV6_PKTINFO, [])); var_dump($socket->setOption(IPPROTO_IPV6, IPV6_PKTINFO, [ "addr" => '::1', "ifindex" => 0 ])); ?> --EXPECTF-- bool(false) bool(true) ================================================ FILE: tests/swoole_socket_coro/setopt/multicast.phpt ================================================ --TEST-- swoole_socket_coro/setopt: multicast --SKIPIF-- --FILE-- bind('0.0.0.0', 9905); $ret = $socket->setOption(IPPROTO_IP, MCAST_JOIN_GROUP, array( 'group' => '224.10.20.30', 'interface' => 0 )); if ($ret === false) { throw new RuntimeException('Unable to join multicast group'); } go(function () use ($socket) { $n = 10; while($n--) { $addr = []; $data = $socket->recvfrom($addr); Assert::assert(strlen($data) > 10); Assert::assert(!empty($addr['port'])); Assert::assert(!empty($addr['address'])); } }); go(function () use ($socket) { $client = new Co\Client(SWOOLE_SOCK_UDP); $client->connect('224.10.20.30', 9905); $n = 10; while($n--) { $client->send("hello world [$n]\n"); co::sleep(.03); } }); Swoole\Event::wait(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_socket_coro/setopt/recvtimeo.phpt ================================================ --TEST-- swoole_socket_coro/setopt: setOption SO_RCVTIMEO --DESCRIPTION-- -wrong params -set/get params comparison --SKIPIF-- --FILE-- setOption(SOL_SOCKET, SO_RCVTIMEO, array()); Assert::assert($retval_1 === false); $options = array("sec" => 1, "usec" => 0); $retval_2 = $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, $options); Assert::assert($retval_2 === true); ?> --EXPECTF-- Warning: Swoole\Coroutine\Socket::setOption(): no key "sec" passed in optval in %s on line %d ================================================ FILE: tests/swoole_socket_coro/setopt/reuse.phpt ================================================ --TEST-- swoole_socket_coro/setopt: setOption SO_RCVTIMEO --DESCRIPTION-- -wrong params -set/get params comparison --SKIPIF-- --FILE-- bind("127.0.0.1", 9501); Assert::assert($socket->setOption(SOL_SOCKET, SO_REUSEADDR, true)); Assert::assert($socket->setOption(SOL_SOCKET, SO_REUSEPORT, true)); ?> --EXPECTF-- ================================================ FILE: tests/swoole_socket_coro/shutdown.phpt ================================================ --TEST-- swoole_socket_coro: shutdown --SKIPIF-- --FILE-- bind('127.0.0.1', 9601)); Assert::true($server->listen(512)); $conn = $server->accept(); Assert::isInstanceOf($conn, Swoole\Coroutine\Socket::class); Assert::same($conn->recv(), array_shift($randoms)); Assert::greaterThan($conn->send(array_shift($randoms)), 0); $conn->close(); $server->close(); }); go(function () use ($randoms) { $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::true($socket->connect('127.0.0.1', 9601)); Assert::greaterThan($socket->send(array_shift($randoms)), 0); Assert::same($socket->recv(), array_shift($randoms)); Assert::true($socket->shutdown(STREAM_SHUT_WR)); for ($n = MAX_REQUESTS; $n--;) { Assert::false($socket->send(array_shift($randoms))); Assert::same($socket->errCode, SOCKET_EPIPE); } Assert::assert($socket->shutdown(STREAM_SHUT_RD)); for ($n = MAX_REQUESTS; $n--;) { Assert::assert(!$socket->recv()); } Assert::false($socket->shutdown()); Assert::same($socket->errCode, SOCKET_ENOTCONN); Assert::true($socket->close()); Assert::false($socket->send('')); Assert::false($socket->recv()); Assert::same($socket->errCode, SOCKET_EBADF); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/ssl.phpt ================================================ --TEST-- swoole_socket_coro: ssl client --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/ssl_bad_server.phpt ================================================ --TEST-- swoole_socket_coro: ssl connect with bad server --SKIPIF-- --FILE-- SWOOLE_LOG_WARNING]); run(function () { $cli = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); $cli->setProtocol(['open_ssl' => true,]); Assert::false ($cli->connect('www.baidu.com', 80)); Assert::eq($cli->errCode, SWOOLE_ERROR_SSL_HANDSHAKE_FAILED); }); ?> --EXPECT-- ================================================ FILE: tests/swoole_socket_coro/tcp-c10k.phpt ================================================ --TEST-- swoole_socket_coro: server and client concurrency --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($socket->listen(MAX_CONCURRENCY_MID)); $i = 0; while ($conn = $socket->accept()) { go(function () use ($socket, $conn, &$i) { for ($n = MAX_REQUESTS; $n--;) { $data = $conn->recv(tcp_length($conn->recv(tcp_type_length()))); Assert::same($data, "Hello Swoole Server #{$n}!"); $conn->send(tcp_pack("Hello Swoole Client #{$n}!")); } $conn->close(); if (++$i === MAX_CONCURRENCY_MID) { $socket->close(); echo "DONE\n"; } }); } }); for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($port) { $client = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($client->connect('127.0.0.1', $port)); for ($n = MAX_REQUESTS; $n--;) { $client->send(tcp_pack("Hello Swoole Server #{$n}!")); $data = $client->recv(tcp_length($client->recv(tcp_type_length()))); Assert::same($data, "Hello Swoole Client #{$n}!"); } $client->close(); }); } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/ulimit.phpt ================================================ --TEST-- swoole_socket_coro: new socket failed --SKIPIF-- --FILE-- getCode() === SOCKET_EMFILE); echo "DONE\n"; } } else { $n = MAX_CONCURRENCY; $_SERVER['TEST_PHP_EXECUTABLE'] = $_SERVER['TEST_PHP_EXECUTABLE'] ?? 'php'; $dir = __DIR__; file_put_contents( '/tmp/ulimit.sh', "ulimit -n {$n} && {$_SERVER['TEST_PHP_EXECUTABLE']} {$_SERVER['PHP_SELF']} ulimit" ); echo shell_exec('/bin/sh /tmp/ulimit.sh'); @unlink('/tmp/ulimit.sh'); } ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_socket_coro/unix_dgram.phpt ================================================ --TEST-- swoole_socket_coro: unix dgram --SKIPIF-- --FILE-- bind('/tmp/test-server.sock'); go(function () use ($server) { while ($data = $server->recvfrom($peer)) { Assert::same($data, 'hello'); $server->sendto($peer['address'], 0, 'world'); } var_dump($peer); }); go(function () use ($server) { @unlink('/tmp/test-client.sock'); $client = new Swoole\Coroutine\Socket(AF_UNIX, SOCK_DGRAM, IPPROTO_IP); $client->bind('/tmp/test-client.sock'); $peer = []; for ($n = MAX_REQUESTS; $n--;) { $client->sendto('/tmp/test-server.sock', 0, 'hello'); $data = $client->recvfrom($peer); Assert::notEmpty($data); if (empty($data)) { break; } Assert::same($data, 'world'); } var_dump($peer); $client->close(); $server->close(); }); }); Swoole\Event::wait(); echo "DONE\n"; ?> --EXPECT-- array(2) { ["address"]=> string(21) "/tmp/test-server.sock" ["port"]=> int(0) } array(2) { ["address"]=> string(21) "/tmp/test-client.sock" ["port"]=> int(0) } DONE ================================================ FILE: tests/swoole_socket_coro/unix_stream.phpt ================================================ --TEST-- swoole_socket_coro: unix stream --SKIPIF-- --FILE-- bind(SOCK_FILE); $server->listen(); go(function () use ($server) { while (!$server->isClosed()) { $conn = $server->accept(); while ($data = $conn->recv()) { Assert::same($data, 'hello'); $conn->send('world'); } } }); go(function () use ($server) { $client = new Swoole\Coroutine\Socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP); $client->connect(SOCK_FILE); for ($n = MAX_REQUESTS; $n--;) { $client->send('hello'); $data = $client->recv(); Assert::notEmpty($data); if (empty($data)) { break; } Assert::same($data, 'world'); } $client->close(); $server->close(); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writeVectorAll.phpt ================================================ --TEST-- swoole_socket_coro: writeVector all --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn = $server->accept(); Assert::assert($conn instanceof Socket); Assert::assert($conn->fd > 0); global $totalLength, $packedStr; Assert::assert($conn instanceof Socket); Assert::eq($conn->recvAll($totalLength), $packedStr); }); go(function () use ($server, $port) { global $iovector, $totalLength; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $port)); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); $conn->close(); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writeVectorAll_ssl.phpt ================================================ --TEST-- swoole_socket_coro: writeVector with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); $conn->recv(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Assert::eq($conn->recvAll($totalLength), $packedStr, -1); $conn->send('close'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writeVector_ssl.phpt ================================================ --TEST-- swoole_socket_coro: writeVector with ssl --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); $iov = ['hello', 'world']; $ret = $conn->writeVector($iov); Assert::eq($ret, strlen('helloworld')); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { Assert::eq($data, 'helloworld'); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writeVector_ssl_eagain.phpt ================================================ --TEST-- swoole_socket_coro: writeVector_ssl eagain --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->setProtocol([ 'open_ssl' => true, ]); $conn->connect('127.0.0.1', $pm->getFreePort()); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); $conn->recv(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; $socket = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $socket->setProtocol([ 'open_ssl' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Coroutine::sleep(0.5); Assert::eq($conn->recvAll($totalLength), $packedStr, -1); $conn->send('close'); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writev.phpt ================================================ --TEST-- swoole_socket_coro: writev test --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { run(function () use ($pm) { $requestLine = "POST / HTTP/1.1\r\n"; $header = "Host: 127.0.0.1\r\n"; $header .= "Connection: keep-alive\r\n"; $header .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"; $header .= "Content-Length: 5\r\n"; $header .= "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36\r\n"; $header .= "\r\n"; $body = 'hello'; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); $conn->connect('127.0.0.1', $pm->getFreePort()); $ret = $conn->writeVector([$requestLine, $header, $body]); Assert::same($ret, strlen($requestLine) + strlen($header) + strlen($body)); $ret = $conn->recv(); Assert::contains($ret, 'world'); $pm->kill(); echo "DONE\n"; }); }; $pm->childFunc = function () use ($pm) { run(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); $server->handle('/', function (Request $request, Response $response) { Assert::same($request->getContent(), 'hello'); $response->end('world'); }); $server->start(); }); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_socket_coro/writev_eagain.phpt ================================================ --TEST-- swoole_socket_coro: writev with eagain --SKIPIF-- --FILE-- bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn = $server->accept(); Assert::assert($conn instanceof Socket); Assert::assert($conn->fd > 0); global $totalLength, $packedStr; Assert::assert($conn instanceof Socket); // Let writeVectorAll trigger EAGAIN (verify the correctness of the error returned by writeVectorAll) Coroutine::sleep(0.5); Assert::eq($conn->recvAll($totalLength), $packedStr); }); go(function () use ($server, $port) { global $iovector, $totalLength; $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', $port)); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); $conn->close(); }); }); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_ssh2/bug63480.phpt ================================================ --TEST-- Bug #63480 (Warning on using the SSH2 Session resource in the uri) --SKIPIF-- --FILE-- --EXPECT-- yada yada ================================================ FILE: tests/swoole_ssh2/bug79631.phpt ================================================ --TEST-- Bug 79631 (SSH disconnect segfault with SFTP (assertion failed)) --SKIPIF-- --FILE-- --EXPECT-- done ================================================ FILE: tests/swoole_ssh2/ssh2_auth.phpt ================================================ --TEST-- ssh2_auth_FOO() - Attempt to authenticate to a remote host --SKIPIF-- --FILE-- --EXPECT-- bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_auth_pubkey.phpt ================================================ --TEST-- ssh2_auth_pubkey() - Tests authentication with a key --SKIPIF-- --FILE-- --EXPECTF-- bool(true) resource(%d) of type (stream) testing echo with key auth ================================================ FILE: tests/swoole_ssh2/ssh2_auth_pubkey_file.phpt ================================================ --TEST-- ssh2_auth_pubkey_file() - Tests authentication with a key --SKIPIF-- --FILE-- --EXPECTF-- bool(true) resource(%d) of type (stream) testing echo with key auth ================================================ FILE: tests/swoole_ssh2/ssh2_connect.phpt ================================================ --TEST-- ssh2_connect() Basic connection and pre-authentication --SKIPIF-- --FILE-- 0); } echo "**Negotiation\n"; $mn = ssh2_methods_negotiated($ssh); var_dump(ssh2t_strset($mn['kex'])); var_dump(ssh2t_strset($mn['hostkey'])); foreach (['client_to_server', 'server_to_client'] as $direction) { $mnd = $mn[$direction]; var_dump(ssh2t_strset($mnd['crypt'])); var_dump(ssh2t_strset($mnd['comp'])); var_dump(ssh2t_strset($mnd['mac'])); } Assert::true(ssh2_disconnect($ssh)); }); ?> --EXPECT-- **Connect bool(true) string(12) "SSH2 Session" **Fingerprint MD5 bool(true) int(32) bool(true) **Fingerprint SHA1 bool(true) int(40) bool(true) **Negotiation bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_exec.phpt ================================================ --TEST-- ssh2_shell_test() - Tests opening a shell --SKIPIF-- --FILE-- --EXPECTF-- bool(true) resource(%d) of type (stream) testing echo ================================================ FILE: tests/swoole_ssh2/ssh2_scp_recv.phpt ================================================ --TEST-- ssh2_scp_recv() - Tests receiving a file via SCP --CREDITS-- Chris MacPherson --SKIPIF-- --FILE-- --EXPECTF-- bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_scp_send.phpt ================================================ --TEST-- ssh2_scp_send() - Tests sending a file via SCP --CREDITS-- Chris MacPherson --SKIPIF-- --FILE-- --EXPECTF-- bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_send_eof.phpt ================================================ --TEST-- ssh2_send_eof() - Tests closing standard input --SKIPIF-- --FILE-- --EXPECTF-- bool(true) resource(%d) of type (stream) bool(true) foo ================================================ FILE: tests/swoole_ssh2/ssh2_sftp_001.phpt ================================================ --TEST-- ssh2_sftp - SFTP tests --SKIPIF-- --FILE-- --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_sftp_002.phpt ================================================ --TEST-- ssh2_sftp - SFTP tests --SKIPIF-- --FILE-- --EXPECT-- Hello World Goodbye Planet bool(true) ================================================ FILE: tests/swoole_ssh2/ssh2_shell.phpt ================================================ --TEST-- ssh2_shell_test() - Tests opening a shell --SKIPIF-- --FILE-- --EXPECTF-- bool(true) resource(%d) of type (stream) %a %a ================================================ FILE: tests/swoole_ssh2/ssh2_skip.inc ================================================ --FILE-- 0) { while($line = fgets($shell)) { echo $line; if (str_ends_with($line, "howdy\r\n")) { break; } } } $elapsed = time() - $start; var_dump(($elapsed < $timeout)); }); ?> --EXPECTF-- bool(true) resource(%d) of type (stream) %a %a ================================================ FILE: tests/swoole_ssh2/ssh2_test.inc ================================================ --FILE-- '); $array[0] = 1; $array[1] = 2; $array[2] = 3; $array[3] = 999; Assert::false($array->isEmpty()); Assert::eq($array->count(), count($array)); Assert::eq($array->slice(0, 2), [1, 2]); Assert::true($array->contains(999)); Assert::eq($array->search(999), 3); Assert::true([]->isEmpty()); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/array_method/1.phpt ================================================ --TEST-- swoole_stdext/array_method: 1 --SKIPIF-- --FILE-- ', ["lemon", "orange", "banana", "apple"]); $sorted_array = array("apple", "banana", "lemon", "orange",); $ref = &$array; $ref->sort(SORT_NATURAL | SORT_FLAG_CASE); Assert::same($array, $sorted_array); Assert::same($ref, $sorted_array); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/array_method/2.phpt ================================================ --TEST-- swoole_stdext/array_method: 2 --SKIPIF-- --FILE-- count(), 4); $ref = &$stack; $fruit = $ref->shift(); Assert::eq($fruit, "orange"); Assert::eq($stack->count(), 3); Assert::eq($array->count(), 4); $ref->unshift("mango"); Assert::eq($stack->count(), 4); $stack2 = array("orange", "banana", "apple", "raspberry"); Assert::eq($stack2->count(), 4); $stack2->shift(); ?> --EXPECTF-- Warning: array_shift(): Argument #1 ($array) must be passed by reference, value given in %s on line %d ================================================ FILE: tests/swoole_stdext/array_method/method.phpt ================================================ --TEST-- swoole_stdext/array_method: all array methods test --SKIPIF-- --FILE-- = 80400) { $array = [ 'a' => 'dog', 'b' => 'cat', 'c' => 'cow', 'd' => 'duck', 'e' => 'goose', 'f' => 'elephant' ]; Assert::eq( $array->all(fn(string $value) => $value->length() > 12), array_all($array, fn(string $value) => $value->length() > 12) ); Assert::eq( $array->any(fn(string $value) => $value->length() > 5), array_any($array, fn(string $value) => $value->length() > 5) ); } $array = array("FirSt" => 1, "SecOnd" => 4); Assert::eq($array->changeKeyCase(CASE_UPPER), array_change_key_case($array, CASE_UPPER)); $array = array('a', 'b', 'c', 'd', 'e'); Assert::eq($array->chunk(2), array_chunk($array, 2)); $records = [ [ 'id' => 2135, 'first_name' => 'John', 'last_name' => 'Doe', ], [ 'id' => 3245, 'first_name' => 'Sally', 'last_name' => 'Smith', ], [ 'id' => 5342, 'first_name' => 'Jane', 'last_name' => 'Jones', ], [ 'id' => 5623, 'first_name' => 'Peter', 'last_name' => 'Doe', ] ]; Assert::eq($records->column('id'), array_column($records, 'id')); $array = array(1, "hello", 1, "world", "hello"); Assert::eq($array->countValues(), array_count_values($array)); $array1 = array("a" => "green", "red", "blue", "red"); $array2 = array("b" => "green", "yellow", "red"); Assert::eq($array1->diff($array2), array_diff($array1, $array2)); $array1 = array("a" => "green", "b" => "brown", "c" => "blue", "red"); $array2 = array("a" => "green", "yellow", "red"); Assert::eq($array1->diffAssoc($array2), array_diff_assoc($array1, $array2)); $array1 = array('blue' => 1, 'red' => 2, 'green' => 3, 'purple' => 4); $array2 = array('green' => 5, 'yellow' => 7, 'cyan' => 8); Assert::eq($array1->diffKey($array2), array_diff_key($array1, $array2)); function odd($var) { return $var & 1; } $array = [6, 7, 8, 9, 10, 11, 12]; Assert::eq($array->filter('odd'), array_filter($array, 'odd')); if (PHP_VERSION_ID >= 80400) { $array = [ 'a' => 'dog', 'b' => 'cat', 'c' => 'cow', 'd' => 'duck', 'e' => 'goose', 'f' => 'elephant' ]; function compare(string $value) { return strlen($value) > 4; } Assert::eq($array->find('compare'), array_find($array, 'compare')); } $input = array("oranges", "apples", "pears"); Assert::eq($input->flip(), array_flip($input)); $array1 = array("a" => "green", "red", "blue"); $array2 = array("b" => "green", "yellow", "red"); Assert::eq($array1->intersect($array2), array_intersect($array1, $array2)); Assert::eq($array1->intersectAssoc($array2), array_intersect_assoc($array1, $array2)); $array = ['apple', 2, 3]; Assert::eq($array->isList(), array_is_list($array)); $array = ['first' => 1, 'second' => 4]; Assert::eq($array->keyExists('first'), array_key_exists('first', $array)); $array = ['a' => 1, 'b' => 2, 'c' => 3]; Assert::eq($array->keyFirst(), array_key_first($array)); Assert::eq($array->keyLast(), array_key_last($array)); Assert::eq($array->keys(), array_keys($array)); Assert::eq($array->values(), array_values($array)); function cube($n) { return ($n * $n * $n); } $a = [1, 2, 3, 4, 5]; Assert::eq($a->map('cube'), array_map('cube', $a)); $array = array(12, 10, 9); Assert::eq($array->pad(5, 0), array_pad($array, 5, 0)); $a = array(2, 4, 6, 8); Assert::eq($a->product(), array_product($a)); $array = array("Neo", "Morpheus", "Trinity", "Cypher", "Tank"); Assert::notEq($array->rand(2), array_rand($array, 2)); function sum($carry, $item) { $carry += $item; return $carry; } $array = array(1, 2, 3, 4, 5); Assert::eq($array->reduce('sum'), array_reduce($array, 'sum')); $base = array("orange", "banana", "apple", "raspberry"); $replacements = array(0 => "pineapple", 4 => "cherry"); $replacements2 = array(0 => "grape"); Assert::eq($base->replace($replacements, $replacements2), array_replace($base, $replacements, $replacements2)); $array = array("php", 4.0, array("green", "red")); Assert::eq($array->reverse()->reverse(), array_reverse(array_reverse($array))); $array = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); Assert::eq($array->search('green'), array_search('green', $array)); $array = array("a", "b", "c", "d", "e"); Assert::eq($array->slice(-2, 1), array_slice($array, -2, 1)); $a = array(2, 4, 6, 8); Assert::eq($a->sum(), array_sum($a)); $array = ["a" => "green", "red", "b" => "green", "blue", "red"]; Assert::eq($array->unique(), array_unique($array)); Assert::eq($array->count(), count($array)); $array = array("Mac", "NT", "Irix", "Linux"); Assert::eq($array->contains("Mac"), in_array("Mac", $array)); Assert::eq($array->join(","), implode(",", $array)); $array = []; Assert::true($array->isEmpty()); $array = typed_array('', ["lemon", "orange", "banana", "apple"]); Assert::true($array->isTyped()); $fruits1 = array("lemon", "orange", "banana", "apple"); $fruits2 = array("lemon", "orange", "banana", "apple"); $result = &$fruits1; sort($fruits2); Assert::eq($result->sort(), $fruits2); Assert::eq($fruits1, $fruits2); $stack = array("orange", "banana", "apple", "raspberry"); $result = &$stack; Assert::eq($result->pop(), 'raspberry'); Assert::eq(array_pop($stack), 'apple'); $array = array("red","green"); $result = &$array; $result->push("blue"); Assert::eq($array, ["red","green", "blue"]); array_push($array, "yellow"); Assert::eq($array, ["red","green", "blue", "yellow"]); $stack = array("orange", "banana", "apple", "raspberry"); $result = &$stack; Assert::eq($result->shift(), 'orange'); Assert::eq(array_shift($stack), 'banana'); $queue = ["orange", "banana"]; $result = &$queue; $result->unshift("orange"); Assert::eq($queue, ["orange", "orange", "banana"]); array_unshift($queue, "orange"); Assert::eq($queue, ["orange", "orange", "orange", "banana"]); $array1 = array("red", "green", "blue", "yellow"); $array2 = array("red", "green", "blue", "yellow"); $result = &$array1; Assert::eq($result->splice(2), array_splice($array2, 2)); Assert::eq($array1, $array2); $find = array("Hello","world"); $replace = array("B"); $arr = array("Hello","world","!"); Assert::eq($arr->replaceStr($find, $replace), str_replace($find, $replace, $arr)); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/stream_method/0.phpt ================================================ --TEST-- swoole_stdext/stream_method: 0 --SKIPIF-- --FILE-- write($rdata->base64Encode()), $rdata->length()); $fp->seek(0); Assert::eq($rdata, $fp->read(8192)->base64Decode()); Assert::eq($fp->stat(), fstat($fp)); Assert::true($fp->sync()); Assert::true($fp->dataSync()); $fp->seek(100); Assert::true($fp->tell() == 100); Assert::true($fp->lock(LOCK_SH) == true); Assert::true($fp->lock(LOCK_UN) == true); Assert::true($fp->eof() == feof($fp)); $fp->seek(0); $char = $fp->getChar(); $fp->seek(0); Assert::eq($char, fgetc($fp)); $fp->seek(0); $line = $fp->getLine(); $fp->seek(0); Assert::eq($line, fgets($fp)); Assert::true($fp->truncate(1000)); Assert::true($fp->close()); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/0.phpt ================================================ --TEST-- swoole_stdext/string_method: 0 --SKIPIF-- --FILE-- isEmpty()); Assert::eq($string->length(), strlen($string)); Assert::eq($string->substr(0, 5), 'hello'); Assert::eq($string->contains('world'), true); Assert::eq($string->indexOf('test'), strpos($string, 'test')); Assert::eq($string->split(' '), explode(' ', $string)); Assert::true(''->isEmpty()); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/1.phpt ================================================ --TEST-- swoole_stdext/string_method: 1 --SKIPIF-- --FILE-- parseStr(); Assert::eq($output['first'], 'value'); Assert::eq($output['arr'][0], 'foo bar'); Assert::eq($output['arr'][1], 'baz'); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/json.phpt ================================================ --TEST-- swoole_stdext/string_method: json --SKIPIF-- --FILE-- random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; $str = $array->jsonEncode(); Assert::notEmpty($str); Assert::eq($str->jsonDecode(), $array); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/marshal.phpt ================================================ --TEST-- swoole_stdext/string_method: marshal --SKIPIF-- --FILE-- random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; $str = $array->marshal(); Assert::notEmpty($str); Assert::eq($str->unmarshal(), $array); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/match.phpt ================================================ --TEST-- swoole_stdext/string_method: match --SKIPIF-- --FILE-- match($regex1, PREG_OFFSET_CAPTURE); preg_match($regex1, $str, $matches2, PREG_OFFSET_CAPTURE); Assert::eq($matches, $matches2); $html = "bold textclick me"; $regex2 = "/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/"; preg_match_all($regex2, $html, $matches2, PREG_SET_ORDER); $matches = $html->matchAll($regex2, PREG_SET_ORDER); Assert::eq($matches, $matches2); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/mbstring.phpt ================================================ --TEST-- swoole_stdext/string_method: all mbstring methods test --SKIPIF-- --FILE-- mbUpperFirst(), mb_ucfirst($text)); $text = 'Bbbb大声向宇宙呐喊bbbb'; Assert::eq($text->mbLowerFirst(), mb_lcfirst($text)); $text = "\t\t大声向宇宙呐喊 :) ... "; Assert::eq($text->mbTrim(), mb_trim($text)); Assert::eq($text->mbLTrim(), mb_ltrim($text)); Assert::eq($text->mbRTrim(), mb_rtrim($text)); $text = '大声大声大声向宇宙呐喊'; Assert::eq($text->mbSubstrCount('大声'), mb_substr_count($text, '大声')); $text = "大声大声大声向宇宙呐喊"; Assert::eq($text->mbSubstr(0, -1), mb_substr($text, 0, -1)); $text = 'b大声大声大声向宇宙呐喊'; Assert::eq($text->mbUpper(), mb_strtoupper($text)); $text = 'BBBBBB大声大声大声向宇宙呐喊'; Assert::eq($text->mbLower(), mb_strtolower($text)); $email = 'name我哦知道@example.com'; Assert::eq($email->mbFind('哦'), mb_strstr($email, '哦')); Assert::eq($email->mbIFind('道'), mb_stristr($email, '道')); $mystring1 = '当地特产'; $mystring2 = '现在只是谈谈'; Assert::true($mystring1->mbIndexOf('地') === mb_strpos($mystring1, '地')); Assert::true($mystring2->mbIIndexOf('A') === mb_stripos($mystring2, 'a')); $mystring = '啦啦啦啦啦啦啦'; Assert::true($mystring->mbLastIndexOf('好') === false); Assert::true(mb_strrpos($mystring, '好') === false); Assert::eq($mystring->mbILastIndexOf('啦'), mb_strripos($mystring, '啦')); Assert::eq($mystring->mbLastCharIndexOf('啦'), mb_strrchr($mystring, '啦')); Assert::eq($mystring->mbILastCharIndex('啦'), mb_strrichr($mystring, '啦')); $text = '啦啦啦啦啦啦啦'; Assert::eq($text->mbLength(), mb_strlen($text)); Assert::eq($text->mbCut(0, 2), mb_strcut($text, 0, 2)); Assert::eq($text->mbDetectEncoding(), mb_detect_encoding($text)); Assert::eq($text->mbConvertEncoding('GBK'), mb_convert_encoding($text, 'GBK')); $text = 'bbbbba啦啦啦啦啦啦啦'; Assert::eq( $text->mbConvertCase(MB_CASE_UPPER)->mbConvertCase(MB_CASE_LOWER), mb_convert_case(mb_convert_case($text, MB_CASE_UPPER), MB_CASE_LOWER) ); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/string_method/method.phpt ================================================ --TEST-- swoole_stdext/string_method: all string methods test --SKIPIF-- --FILE-- length(), strlen($text)); $text = ''; Assert::eq($text->isEmpty(), true); $text = 'A'; Assert::eq($text->lower(), strtolower($text)); Assert::eq($text->lowerFirst(), lcfirst($text)); $text = 'b'; Assert::eq($text->upper(), strtoupper($text)); Assert::eq($text->upperFirst(), ucfirst($text)); $text = 'hello world!'; Assert::eq($text->upperWords(), ucwords($text)); $text = "PHP isThirty\nYears Old!\tYay to the Elephant!\n"; $characters = "\0..\37!@\177..\377"; Assert::eq($text->addCSlashes($characters), addCSlashes($text, $characters)); $text = "O'Reilly?"; Assert::eq($text->addSlashes(), addslashes($text)); $text = 'This is quite a long string, which will get broken up because the line is going to be too long after base64 encoding it.'; Assert::eq($text->base64Encode()->chunkSplit(), chunk_split(base64_encode($text))); $text = "Two Ts and one F."; Assert::eq($text->countChars(1), count_chars($text, 1)); $text = "I'll \"walk\" the dog now"; Assert::eq($text->htmlEntityEncode()->htmlEntityDecode(), html_entity_decode(htmlentities($text))); $text = "Test"; Assert::eq($text->htmlSpecialCharsEncode(ENT_QUOTES)->htmlSpecialCharsDecode(), htmlspecialchars_decode(htmlspecialchars($text, ENT_QUOTES))); $text = "\t\tThese are a few words :) ... "; Assert::eq($text->trim(), trim($text)); Assert::eq($text->lTrim(), ltrim($text)); Assert::eq($text->rTrim(), rtrim($text)); $text = "first=value&arr[]=foo+bar&arr[]=baz"; parse_str($text, $result); Assert::eq($text->parseStr(), $result); $url = 'http://username:password@hostname:9090/path?arg=value#anchor'; Assert::eq($url->parseUrl(), parse_url($url)); $text = 'The lazy fox jumped over the fence'; Assert::eq($text->contains('lazy'), str_contains($text, 'lazy')); if (PHP_VERSION_ID >= 80300) { $text = 'ABC'; Assert::eq($text->incr(), str_increment($text)); Assert::eq($text->decr(), str_decrement($text)); } $text = ""; Assert::eq($text->replace("%BODY%", "black"), str_replace("%BODY%", "black", $text)); Assert::eq($text->iReplace("%body%", "black"), str_ireplace("%body%", "black", $text)); $text = "Alien"; Assert::eq($text->pad(10), str_pad($text, 10)); $text = "-="; Assert::eq($text->repeat(10), str_repeat($text, 10)); $text = 'abcdef'; Assert::notEq($text->shuffle(), str_shuffle($text)); $text = "piece1 piece2 piece3 piece4 piece5 piece6"; Assert::eq($text->split(' '), explode(' ', $text)); $text = 'The lazy fox jumped over the fence'; Assert::eq($text->startsWith('The'), str_starts_with($text, 'The')); Assert::eq($text->endsWith('fence'), str_ends_with($text, 'fence')); $text = "Hello fri3nd, you're looking good today!"; Assert::eq($text->wordCount(0), str_word_count($text, 0)); Assert::eq($text->wordCount(1), str_word_count($text, 1)); Assert::eq($text->wordCount(2), str_word_count($text, 2)); $var1 = "Hello"; $var2 = "hello"; Assert::eq($var1->iCompare($var2), strcasecmp($var1, $var2)); Assert::eq($var1->compare($var2), strcmp($var1, $var2)); $email = 'name@example.com'; Assert::eq($email->find('@'), strstr($email, '@')); Assert::eq($email->iFind('N'), stristr($email, 'N')); $text = '

Test paragraph.

Other text'; Assert::eq($text->stripTags(), strip_tags($text)); $text = 'I\'d have a coffee.\nNot a problem.'; Assert::eq($text->stripCSlashes(), stripcslashes($text)); $str = "Is your name O\'reilly?"; Assert::eq($str->stripSlashes(), stripslashes($str)); $mystring1 = 'xyz'; $mystring2 = 'ABC'; Assert::true($mystring1->iIndexOf('A') === stripos($mystring1, 'a')); Assert::true($mystring2->iIndexOf('A') === stripos($mystring2, 'a')); Assert::eq($mystring2->indexOf('a'), strpos($mystring2, 'a')); $mystring = 'Elephpant'; Assert::true($mystring->lastIndexOf('b') === false); Assert::true(strrpos($mystring, 'b') === false); Assert::eq($mystring->iLastIndexOf('E'), strripos($mystring, 'E')); Assert::eq($mystring->lastCharIndexOf('E'), strrchr($mystring, 'E')); $text = "abcdef"; Assert::eq($text->substr(0, -1), substr($text, 0, -1)); Assert::eq($text->substrCompare("bc", 1, 2), substr_compare($text, "bc", 1, 2)); $text = 'This is a test'; Assert::eq($text->substrCount('is'), substr_count($text, 'is')); $var = 'ABCDEFGH:/MNRPQR/'; Assert::eq($var->substrReplace('bob', 0), substr_replace($var, 'bob', 0)); $var = 'abcdefghijklmn'; Assert::eq($var->reverse(), strrev($var)); Assert::eq($var->md5(), md5($var)); Assert::eq($var->sha1(), sha1($var)); Assert::eq($var->crc32(), crc32($var)); Assert::eq($var->hash('sha256'), hash('sha256', $var)); $str = 'This is an encoded string'; Assert::eq($str->base64Encode()->base64Decode(), base64_decode(base64_encode($str))); $text = 'Data123!@-_ +'; Assert::eq($text->urlEncode()->urlDecode(), urldecode(urlencode($text))); $text = 'foo @+%/'; Assert::eq($text->rawUrlEncode()->rawUrlDecode(), rawurldecode(rawurlencode($text))); $pattern = "/php/i"; $text = "PHP is the web scripting language of choice."; $result1 = $text->match("/php/i"); preg_match("/php/i", $text, $result2); Assert::eq($result1, $result2); $pattern = "/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x"; $subject = "Call 555-1212 or 1-800-555-1212"; $result1 = $subject->matchAll($pattern); preg_match_all($pattern, $subject, $result2); Assert::eq($result1, $result2); $text = '112'; Assert::eq($text->isNumeric(), is_numeric($text)); ?> --EXPECT-- ================================================ FILE: tests/swoole_stdext/typed_array/0.phpt ================================================ --TEST-- swoole_stdext/typed_array: 0 --SKIPIF-- --FILE-- '); try { $array = typed_array('getMessage()->contains("must start with '<' and end with '>'")); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/1.phpt ================================================ --TEST-- swoole_stdext/typed_array: 1 --SKIPIF-- --FILE-- '); $array[] = 123; try { $array[] = '456'; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); } $array = typed_array(''); $array[] = true; $array[] = false; try { $array[] = '456'; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); } $array = typed_array(''); $array[] = '456'; try { $array[] = true; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); } $array = typed_array(''); $array[] = 4556.56; try { $array[] = 456; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); } $array = typed_array(''); $array[] = new stdClass(); try { $array[] = new ArrayObject(); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_stdext/typed_array/10.phpt ================================================ --TEST-- swoole_stdext/typed_array: 10 --SKIPIF-- --FILE-- '); $array[999] = $bytes; $copy = typed_array('', $array); Assert::eq($copy[999], $bytes); try { $copy2 = typed_array('', $array); } catch (Throwable $e) { Assert::true($e->getMessage()->contains('not match the initial values')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/11.phpt ================================================ --TEST-- swoole_stdext/typed_array: 11 --SKIPIF-- --FILE-- ', [1, $num, 3]); foreach($array as &$v) { var_dump($v); echo "each\n"; } echo "end\n"; ?> --EXPECTF-- Fatal error: Uncaught Error: The type array do not support using references for element value during iteration in %s:%d Stack trace: #0 {main} thrown in %s on line %d ================================================ FILE: tests/swoole_stdext/typed_array/2.phpt ================================================ --TEST-- swoole_stdext/typed_array: 2 --SKIPIF-- --FILE-- >'); $array[999] = typed_array(''); try { $array[888] = typed_array(''); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } try { $array[777] = []; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE DONE ================================================ FILE: tests/swoole_stdext/typed_array/3.phpt ================================================ --TEST-- swoole_stdext/typed_array: 3 --SKIPIF-- --FILE-- '); $map['a'] = 1; $map['b'] = 2; $map['c'] = 3; unset($map['b']); Assert::true(isset($map['a'])); Assert::true(isset($map['c'])); Assert::false(isset($map['b'])); $array = typed_array(''); $array[] = 3; $array[] = 5; $array[] = 7; try { unset($array[0]); } catch (Throwable $e) { Assert::true($e->getMessage()->contains('not support')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/4.phpt ================================================ --TEST-- swoole_stdext/typed_array: 4 --SKIPIF-- --FILE-- '); $array[999] = 'test'; try { $array['hello'] = 'world'; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array key type mismatch')); echo "DONE\n"; } $key = str_repeat('a', 1000); try { $array[$key] = 'world'; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array key type mismatch')); echo "DONE\n"; } ?> --EXPECTF-- DONE DONE ================================================ FILE: tests/swoole_stdext/typed_array/5.phpt ================================================ --TEST-- swoole_stdext/typed_array: 5 --SKIPIF-- --FILE-- '); $array[] = 3; $array[] = 5; $array[] = 7; $array[3] = random_int(1, 100); try { $array[5] = 999; } catch (Throwable $e) { Assert::true($e->getMessage()->contains('out of the permitted range')); echo "DONE\n"; } try { $array["hello"] = 999; } catch (Throwable $e) { Assert::true($e->getMessage()->contains('must be undef or int')); echo "DONE\n"; } ?> --EXPECT-- DONE DONE ================================================ FILE: tests/swoole_stdext/typed_array/6.phpt ================================================ --TEST-- swoole_stdext/typed_array: 6 --SKIPIF-- --FILE-- ', [1, $num, 3]); Assert::eq($array->count(), 3); Assert::eq($array[1], $num); try { $array = typed_array('', [1, "hello", 3]); } catch (Throwable $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/7.phpt ================================================ --TEST-- swoole_stdext/typed_array: 7 --SKIPIF-- --FILE-- ', [1, $num, 3]); $copy = []; foreach($array as $k => $v) { $copy[] = $v; $array[$k] = $v * 2; } Assert::eq($copy[1], $num); Assert::eq($array[1], $num * 2); Assert::true($array->isTyped()); try { $array[] = 'hello'; } catch (Throwable $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } foreach($copy as &$v) { $v = 0; } Assert::eq($copy->sum(), 0); try { foreach ($array as &$v) { var_dump($v); } } catch (Throwable $e) { Assert::true($e->getMessage()->contains('not support using references for element value')); echo "DONE\n"; } ?> --EXPECT-- DONE DONE ================================================ FILE: tests/swoole_stdext/typed_array/8.phpt ================================================ --TEST-- swoole_stdext/typed_array: 8 --SKIPIF-- --FILE-- '); $arr[] = 1; $arr[0] += 10; Assert::eq($arr[0], 11); try { $arr[0] .= "hello world"; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/9.phpt ================================================ --TEST-- swoole_stdext/typed_array: 9 --SKIPIF-- --FILE-- '); $array[999] = random_bytes(128); try { $a = typed_array(''); $array[888] = $a; } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/array_pop.phpt ================================================ --TEST-- swoole_stdext/typed_array: array_pop --SKIPIF-- --FILE-- ', [1, 3, 5, 7]); $v = array_pop($arr); Assert::eq($v, 7); Assert::eq($arr[2], 5); Assert::eq($arr->count(), 3); Assert::true($arr->isList()); try { array_push($arr, 9, 'hello world', true); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/array_push.phpt ================================================ --TEST-- swoole_stdext/typed_array: 9 --SKIPIF-- --FILE-- ', [1, 3, 5]); array_push($arr, 7); Assert::eq($arr[3], 7); Assert::true($arr->isList()); try { array_push($arr, 9, 'hello world', true); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/array_shift.phpt ================================================ --TEST-- swoole_stdext/typed_array: array_shift --SKIPIF-- --FILE-- ', [1, 3, 5, 7]); $v = array_shift($arr); Assert::eq($v, 1); Assert::eq($arr[1], 5); Assert::eq($arr->count(), 3); Assert::true($arr->isList()); try { array_unshift($arr, 9, 'hello world', true); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/array_splice.phpt ================================================ --TEST-- swoole_stdext/typed_array: array_splice --SKIPIF-- --FILE-- ', array("red", "green", "blue", "yellow")); array_splice($arr, 2); Assert::eq($arr->count(), 2); array_push($arr, "purple", "orange"); Assert::eq($arr->count(), 4); Assert::true($arr->isList()); array_splice($arr, 1, count($arr), "black"); Assert::true($arr->contains("black")); Assert::eq($arr->count(), 2); Assert::true($arr->isList()); Assert::true($arr->isTyped()); array_splice($arr, -1, 1, array("gray", "maroon")); Assert::eq($arr->count(), 3); Assert::true($arr->contains("maroon")); Assert::true($arr->isTyped()); Assert::true($arr->isList()); try { array_splice($arr, -1, 1, array(9999, false)); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/array_unshift.phpt ================================================ --TEST-- swoole_stdext/typed_array: 10 --SKIPIF-- --FILE-- ', [1, 3, 5]); array_unshift($arr, 999); Assert::eq($arr[0], 999); Assert::true($arr->isList()); try { array_unshift($arr, 9, 'hello world', true); } catch (TypeError $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/resource.phpt ================================================ --TEST-- swoole_stdext/typed_array: resource --SKIPIF-- --FILE-- '); $map['a'] = fopen(__FILE__, 'r'); Assert::eq(get_resource_type($map['a']), 'stream'); try { $map['b'] = 1; } catch (Throwable $e) { Assert::true($e->getMessage()->contains('Array value type mismatch')); echo "DONE\n"; } ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_stdext/typed_array/sort.phpt ================================================ --TEST-- swoole_stdext/typed_array: sort --SKIPIF-- --FILE-- ', [1243, 3434, 5453, 4532, 2, 3454, 5233, 655, 234, 6, 2356, 4554]); Assert::true($arr->isList()); Assert::true($arr->isTyped()); sort($arr); Assert::true($arr->isTyped()); shuffle($arr); Assert::true($arr->isTyped()); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/big_size.phpt ================================================ --TEST-- swoole_table: big size --SKIPIF-- --FILE-- column('string', \Swoole\Table::TYPE_STRING, $string_size = mt_rand(2, 6) * 0xffff); $table->column('int', \Swoole\Table::TYPE_INT, 8); $table->create(); $data = [ 'string' => str_repeat('S', $string_size), 'int' => PHP_INT_MAX ]; $table->set('test', $data); Assert::same($table->get('test'), $data); var_dump($table->get('test')); ?> --EXPECTF-- array(2) { ["string"]=> string(%d) "%s" ["int"]=> int(%d) } ================================================ FILE: tests/swoole_table/bug_2263.phpt ================================================ --TEST-- swoole_table: bug_2263 --SKIPIF-- --FILE-- column('data', Table::TYPE_STRING, 1); $table->create(); $table->set("1234567890", ['data' => '1']); $table->set("44984", ['data' => '2']); $table->del("1234567890"); foreach($table as $ip => $row) { echo $ip."\n"; } ?> --EXPECT-- 44984 ================================================ FILE: tests/swoole_table/bug_2290.phpt ================================================ --TEST-- swoole_table: bug_2290 --SKIPIF-- --FILE-- column('h', \Swoole\Table::TYPE_STRING, 128); $table->column('b', \Swoole\Table::TYPE_STRING, 1024 * 512); $table->column('_e', \Swoole\Table::TYPE_INT); $table->create(); $headers = ['Content-Type' => 'text/html; charset=utf-8']; $body = << EOS; $value = ['h' => json_encode($headers, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'b' => $body]; $value['_e'] = time(); // Remove this line and the result is correct $table->set('test', $value); echo $table->get('test', 'b'); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/create_10k_object.phpt ================================================ --TEST-- swoole_table: create 10,000 objects --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_table/del.phpt ================================================ --TEST-- swoole_table: clear all columns --SKIPIF-- --FILE-- column('state', Swoole\Table::TYPE_INT); $table->column('remainLen', Swoole\Table::TYPE_INT); $table->column('data', Swoole\Table::TYPE_STRING, 64); $table->create(); function data($table) { $table_key = 'table'; $table->incr($table_key, 'state'); $state = $table->get($table_key, 'state'); var_dump($state); $data = $table->get($table_key, 'data'); $data .= 'abc'; $table->set($table_key, ['data' => $data]); var_dump($table->get($table_key)); if ($state === 1) { $table->incr($table_key, 'remainLen', 3); } else { $remainLen = $table->get($table_key, 'remainLen'); if ($remainLen === 3) { $res = $table->del($table_key); var_dump($res); var_dump($table->get($table_key)); } } } data($table); data($table); data($table); ?> --EXPECT-- int(1) array(3) { ["state"]=> int(1) ["remainLen"]=> int(0) ["data"]=> string(3) "abc" } int(2) array(3) { ["state"]=> int(2) ["remainLen"]=> int(3) ["data"]=> string(6) "abcabc" } bool(true) bool(false) int(1) array(3) { ["state"]=> int(1) ["remainLen"]=> int(0) ["data"]=> string(3) "abc" } ================================================ FILE: tests/swoole_table/force_unlock.phpt ================================================ --TEST-- swoole_table: force unlock --SKIPIF-- --FILE-- column('string', \Swoole\Table::TYPE_STRING, 4 * 1024 * 1024); $table->column('int', \Swoole\Table::TYPE_INT, 8); $table->create(); $str_size = 4 * 1024 * 1024; $str_value = random_bytes($str_size); $data = [ 'string' => $str_value, 'int' => PHP_INT_MAX ]; $table->set('test', $data); $proc = new Process(function () use ($table) { $str = str_repeat('A', 5 * 1024 * 1024); // Fatal error: memory exhausted $data = $table->get('test'); var_dump(strlen($data['string'])); var_dump(strlen($str)); var_dump(memory_get_usage()); }, true, SOCK_STREAM); $proc->start(); $exit_status = Process::wait(); Assert::eq($exit_status['code'], 255); Assert::contains($proc->read(), 'Fatal error: Allowed memory'); $data = $table->get('test'); Assert::eq(strlen($data['string']), $str_size); Assert::eq($data['string'], $str_value); echo "Done\n"; ?> --EXPECTF-- [%s] WARNING TableRow::lock(): lock process[%d] not exists, force unlock Done ================================================ FILE: tests/swoole_table/foreach.phpt ================================================ --TEST-- swoole_table: iterator --SKIPIF-- --FILE-- column('id', Swoole\Table::TYPE_INT); $table->column('name', Swoole\Table::TYPE_STRING, 128); $table->column('num', Swoole\Table::TYPE_FLOAT); if (!$table->create()) { echo __LINE__." error"; } $table->set('test_key', array('id' => 1, 'name' => 'rango', 'num' => 3.1415926)); $table->set('hello_world', array('id' => 100, 'name' => 'xinhua', 'num' => 399.66)); $_key = array(); $_num = array(); foreach ($table as $key => $value) { $_key [] = $key; $_num [] = $value['num']; } sort($_key); sort($_num); if (implode('', $_key) == 'hello_worldtest_key' and array_sum($_num) == 399.66 + 3.1415926) { echo 'SUCCESS'; } ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_table/getMemorySize_1.phpt ================================================ --TEST-- swoole_table: getMemorySize --SKIPIF-- --FILE-- getMemorySize(), 0); $table->column('name', Table::TYPE_STRING, 32); $table->create(); Assert::greaterThan($table->getMemorySize(), 0); ?> --EXPECTF-- ================================================ FILE: tests/swoole_table/get_after_destroy.phpt ================================================ --TEST-- swoole_table: get after destroy --SKIPIF-- --FILE-- column('id', Table::TYPE_INT); $table->column('name', Table::TYPE_STRING, 10); $table->create(); $table->set('1', ['id' => 1, 'name' => 'rango']); Assert::eq($table->get('1')['id'], 1); Assert::eq($table->get('1')['name'], 'rango'); $proc = new Process(function () use ($table) { usleep(10000); Assert::eq($table->get('1')['id'], 2); Assert::eq($table->get('1')['name'], ''); }, true, SOCK_STREAM); $proc->start(); $table->destroy(); $output = $proc->read(); Assert::contains($output, 'table is not created or has been destroyed'); $retval = Process::wait(); Assert::eq($retval['code'], 255); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/get_before_create.phpt ================================================ --TEST-- swoole_table: get before create --SKIPIF-- --FILE-- column('id', Table::TYPE_INT); $table->column('name', Table::TYPE_STRING, 10); Assert::eq($table->get('1')['id'], 1); Assert::eq($table->get('1')['name'], 'rango'); }, true, SOCK_STREAM); $proc->start(); $output = $proc->read(); Assert::contains($output, 'table is not created or has been destroyed'); $retval = Process::wait(); Assert::eq($retval['code'], 255); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/gh-5789.phpt ================================================ --TEST-- swoole_table: bug_5789 --SKIPIF-- --FILE-- column('test', Table::TYPE_INT); $table->create(); $value = random_int(1, PHP_INT_MAX); $table->set('firstrow', ['test' => $value]); Assert::eq($table->get('firstrow', 'test'), $value); Assert::eq($table->get('firstrow', null), ['test' => $value]); Assert::same($table->get('not-exists', null), false); Assert::same($table->get('not-exists', 'test'), false); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/incr_after_del.phpt ================================================ --TEST-- swoole_table: incr after del --SKIPIF-- --FILE-- column('id', \Swoole\Table::TYPE_INT); $table->create(); $table->set('1', ['id' => 1,]); for($i=0;$i<10;$i++) { $table->incr('1', 'id', 1); } Assert::eq($table->get('1')['id'], 11); Assert::true($table->del('1')); Assert::false($table->get('1')); for($i = 0; $i < 10; $i++) { $table->incr('1', 'id', 1); } Assert::eq($table->get('1')['id'], 10); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/int.phpt ================================================ --TEST-- swoole_table: int --SKIPIF-- --FILE-- column('i8', Swoole\Table::TYPE_INT, 1); $table->column('i16', Swoole\Table::TYPE_INT, 2); $table->column('i32', Swoole\Table::TYPE_INT, 4); $table->column('i64', Swoole\Table::TYPE_INT, 8); if (!$table->create()) { echo __LINE__." error"; } $ret = $table->set('test_key', array( 'i8' => -120, 'i16' => -30000, 'i32' => -1247483648, 'i64' => -9023372036854775808, )); if (!$ret) { echo __LINE__." error"; } $ret = $table->get('test_key'); if (!$ret) { echo __LINE__." error"; } Assert::same($ret['i8'], -120); Assert::same($ret['i16'], -30000); Assert::same($ret['i32'], -1247483648); Assert::same($ret['i64'], -9023372036854775808); $ret = $table->incr('test_key', 'i8', 8); if (!$ret) { echo __LINE__." error"; } Assert::same($table->get('test_key', 'i8'), -120 + 8); $ret = $table->decr('test_key', 'i32', 8); if (!$ret) { echo __LINE__." error"; } Assert::same($table->get('test_key', 'i32'), -1247483648 - 8); $ret = $table->set('test_key', array( 'i8' => 120, 'i16' => 30000, 'i32' => 1247483648, 'i64' => 9023372036854775808, )); if (!$ret) { echo __LINE__." error"; } $ret = $table->get('test_key'); if (!$ret) { echo __LINE__." error"; } Assert::same($ret['i8'], 120); Assert::same($ret['i16'], 30000); Assert::same($ret['i32'], 1247483648); Assert::same($ret['i64'], 9023372036854775808); $ret = $table->incr('test_key', 'i8', 4); if (!$ret) { echo __LINE__." error"; } Assert::same($table->get('test_key', 'i8'), 120 + 4); $ret = $table->decr('test_key', 'i32', 8); if (!$ret) { echo __LINE__." error"; } Assert::same($table->get('test_key', 'i32'), 1247483648 - 8); echo "SUCCESS"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_table/key_value.phpt ================================================ --TEST-- swoole_table: key-value operate --SKIPIF-- --FILE-- column('id', Swoole\Table::TYPE_INT); $table->column('name', Swoole\Table::TYPE_STRING, 128); $table->column('num', Swoole\Table::TYPE_FLOAT); if (!$table->create()) { echo __LINE__." error"; } if (!$table->set('test_key', array('id' => 1, 'name' => NAME, 'num' =>PI))) { echo __LINE__." error"; } $ret = $table->get('test_key'); if (!($ret and is_array($ret) and $ret['id'] == 1)) { echo __LINE__." error"; } $ret = $table->get('test_key', 'id'); if (!($ret and $ret == 1)) { echo __LINE__." error"; } Assert::eq($table->get('test_key', 'name'), NAME); Assert::eq($table->get('test_key', 'num'), PI); // field not exists Assert::false($table->get('test_key', 'id_no_exists')); Assert::false($table->get('test_key_no_exists', 'id_no_exists')); $ret = $table->exist('test_key'); if (!($ret)) { echo __LINE__." error"; } $ret = $table->exist('test_key_not_exists'); if ($ret) { echo __LINE__." error"; } $ret = $table->incr('test_key','id', 2); if (!$ret) { echo __LINE__." error"; } $_value = $table->get('test_key', 'id'); if ($_value != 3) { echo __LINE__." error"; } $ret = $table->decr('test_key', 'id', 2); if (!$ret) { echo __LINE__ . " error"; } $_value = $table->get('test_key', 'id'); if ($_value != 1) { echo __LINE__ . " error"; } $table->set('hello_world', array('id' => 100, 'name' => 'xinhua', 'num' => 399.66)); if (count($table) != 2) { echo __LINE__." error"; } $ret = $table->del('test_key'); if (!$ret) { echo __LINE__." error"; } if ($table->exist('test_key')) { echo __LINE__." error"; } echo "SUCCESS\n"; ?> --EXPECT-- SUCCESS ================================================ FILE: tests/swoole_table/negative.phpt ================================================ --TEST-- swoole_table: negative --SKIPIF-- --FILE-- column('v1', Swoole\Table::TYPE_INT); $table->column('v2', Swoole\Table::TYPE_FLOAT); if (!$table->create()) { echo __LINE__." error"; } $table->set('test1', ['v1' => 0, 'v2' => 0]); Assert::same($table->decr('test1', 'v1', 1), -1); Assert::same($table->decr('test1', 'v2', 1.5), -1.5); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/random_bytes.phpt ================================================ --TEST-- swoole_table: read/write random data --SKIPIF-- --FILE-- column('string', \Swoole\Table::TYPE_STRING, 256 * 1024); $table->create(); $n = IS_IN_CI ? 100 : 1000; // $n = 100; $map = []; while($n--) { $key = "key-".rand(1000000, 9999999); $value = RandStr::getBytes(rand(100*1024, 250*1024)); $map[$key] = $value; $table->set($key, ['string' => $value]); } foreach($map as $k => $v) { Assert::same($table->get($k)['string'], $v); } ?> --EXPECT-- ================================================ FILE: tests/swoole_table/set_after_del.phpt ================================================ --TEST-- swoole_table: set after del --SKIPIF-- --FILE-- column('id', \Swoole\Table::TYPE_INT); $table->column('name', \Swoole\Table::TYPE_STRING, 10); $table->create(); $table->set('1', ['id' => 1, 'name' => 'rango']); Assert::eq($table->get('1')['id'], 1); Assert::eq($table->get('1')['name'], 'rango'); Assert::true($table->del('1')); Assert::false($table->get('1')); $table->set('1', ['id' => 2, ]); Assert::eq($table->get('1')['id'], 2); Assert::eq($table->get('1')['name'], ''); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/stats.phpt ================================================ --TEST-- swoole_table: stats --SKIPIF-- --FILE-- column('string', Table::TYPE_STRING, 256); $table->create(); $map = []; $keys = []; $n = N; while ($n--) { $key = base64_decode(RandStr::getBytes(rand(10, 30))); $value = RandStr::getBytes(rand(100, 250)); if ($table->set($key, ['string' => $value])) { $map[$key] = $value; $keys[] = $key; } } $stats1 = $table->stats(); Assert::eq(count($keys), N); Assert::eq(count(array_unique($keys)), $stats1['insert_count']); phpt_var_dump("insert\n".str_repeat('-', 64), $stats1); define('UPDATE_N', rand(100, 1000)); $_n = UPDATE_N; while ($_n--) { $key = array_rand($map); $value = RandStr::getBytes(rand(100, 250)); Assert::true($table->set($key, ['string' => $value])); $map[$key] = $value; } $stats2 = $table->stats(); Assert::eq($stats1['update_count'] + UPDATE_N, $stats2['update_count']); phpt_var_dump("update\n" . str_repeat('-', 64), $stats2); foreach($map as $k => $v) { Assert::same($table->get($k)['string'], $v); $table->del($k); } $stats3 = $table->stats(); Assert::eq($stats3['num'], 0); Assert::eq($stats3['available_slice_num'], $stats3['total_slice_num']); phpt_var_dump("delete\n" . str_repeat('-', 64), $stats3); ?> --EXPECT-- ================================================ FILE: tests/swoole_table/type_conv.phpt ================================================ --TEST-- swoole_table: type convert --SKIPIF-- --FILE-- column('id', Swoole\Table::TYPE_INT); $table->column('name', Swoole\Table::TYPE_STRING, 128); $table->column('num', Swoole\Table::TYPE_FLOAT); if (!$table->create()) { echo __LINE__." error"; } $table->set('test_key', array('id' => 1, 'name' => NAME, 'num' => PI)); $table->set(1002, array('id' => '2', 'name' => 'hello', 'num' => PI + 9)); $r1 = ($table->get('test_key')); $r2 = ($table->get(1002)); Assert::same($r1['id'], 1); Assert::same($r2['id'], 2); $table->set('test_key', array('id' => '2348', 'name' => 1024, 'num' => '3.231')); $r1 = ($table->get('test_key')); Assert::same($r1['id'], 2348); Assert::same($r1['num'], 3.231); Assert::same($r1['name'], '1024'); $table->set('test_key', array('id' => 'abc', 'name' => 1024, 'num' => '3.231')); $r1 = ($table->get('test_key')); Assert::same($r1['id'], 0); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/add_update.phpt ================================================ --TEST-- swoole_thread: add/update --SKIPIF-- --FILE-- update(KEY_NOT_EXISTS, $value)); Assert::true($m->add(KEY_NOT_EXISTS, $value)); Assert::eq($m[KEY_NOT_EXISTS], $value); unset($m[KEY_NOT_EXISTS]); Assert::eq($m[KEY_NOT_EXISTS], null); Assert::false($m->add(KEY_EXISTS, $value)); Assert::true($m->update(KEY_EXISTS, $value)); Assert::eq($m[KEY_EXISTS], $value); $m2 = new Map(); $m2[INDEX_EXISTS] = $value; Assert::false($m2->update(INDEX_NOT_EXISTS, $value)); Assert::true($m2->add(INDEX_NOT_EXISTS, $value)); Assert::eq($m2[INDEX_NOT_EXISTS], $value); unset($m2[INDEX_NOT_EXISTS]); Assert::eq($m2[INDEX_NOT_EXISTS], null); Assert::false($m2->add(INDEX_EXISTS, $value)); Assert::true($m2->update(INDEX_EXISTS, $value)); Assert::eq($m2[INDEX_EXISTS], $value); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/affinity.phpt ================================================ --TEST-- swoole_thread: Affinity --SKIPIF-- --FILE-- parentFunc = function () { $thread = new Thread(__FILE__, 'child'); $r = Thread::getAffinity(); Assert::eq(count($r), swoole_cpu_num()); Assert::assert(Thread::setAffinity([1])); Assert::eq(Thread::getAffinity(), [1]); $thread->join(); }; $tm->childFunc = function () { $r = Thread::getAffinity(); Assert::eq(count($r), swoole_cpu_num()); Assert::assert(Thread::setAffinity([0])); Assert::eq(Thread::getAffinity(), [0]); }; $tm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/arraylist.phpt ================================================ --TEST-- swoole_thread: arraylist --SKIPIF-- --FILE-- toArray(), $array); Assert::eq($l->find($uuid), 2); for ($i = 0; $i < count($array); $i++) { Assert::eq($l[$i], $array[$i]); } $array2 = [ 'key' => 'value', 'hello' => 'world', ]; $l[] = $array2; Assert::eq(count($l), 5); Assert::eq($l[4]->toArray(), $array2); try { $l2 = new ArrayList($array2); echo "never here\n"; } catch (Throwable $e) { Assert::contains($e->getMessage(), 'must be an array of type list'); } $uuid2 = uniqid(); $l[] = $uuid2; $count = count($l); unset($l[1]); Assert::eq(count($l), $count - 1); Assert::eq($l[1], $uuid); Assert::eq($l->find($uuid), 1); Assert::eq($l->find($uuid2), $count - 2); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/async-io.phpt ================================================ --TEST-- swoole_thread: async-io --SKIPIF-- --FILE-- join(); } Assert::eq($atomic->get(), C * N); Assert::eq($atomicLong->get(), C * N * M); } else { $id = $args[0]; $atomic = $args[1]; $atomicLong = $args[2]; Co\run(function () use ($atomic, $atomicLong, $md5) { $n = N; while ($n--) { $atomic->add(); $atomicLong->add(M); $rs = \Swoole\Coroutine\System::readFile(__FILE__); Assert::eq(md5($rs), $md5); } }); exit(0); } echo "DONE\n"; ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_thread/atomic_ctor.phpt ================================================ --TEST-- swoole_thread: atomic ctor --SKIPIF-- --FILE-- parentFunc = function () { $lock = new Lock; $lock->lock(); $num1 = random_int(1, 1 << 31); $num2 = random_int(1 << 31, PHP_INT_MAX); $atomic1 = new Swoole\Thread\Atomic($num1); $atomic2 = new Swoole\Thread\Atomic\Long($num2); $thread = new Thread(__FILE__, $lock, $atomic1, $atomic2, $num1, $num2); $lock->lock(); echo "main thread\n"; $thread->join(); }; $tm->childFunc = function ($lock, $atomic1, $atomic2, $num1, $num2) { echo "child thread\n"; usleep(200_000); $lock->unlock(); Assert::eq($atomic1->get(), $num1); Assert::eq($atomic2->get(), $num2); exit(0); }; $tm->run(); ?> --EXPECTF-- child thread main thread ================================================ FILE: tests/swoole_thread/barrier.phpt ================================================ --TEST-- swoole_thread: barrier --SKIPIF-- --FILE-- parentFunc = function () { $barrier = new Barrier(2); $s = microtime(true); $thread = new Thread(__FILE__, $barrier); $barrier->wait(); Assert::greaterThanEq(microtime(true) - $s, 0.2); echo "main thread\n"; $thread->join(); }; $tm->childFunc = function ($barrier) { echo "child thread\n"; usleep(200_000); $barrier->wait(); exit(0); }; $tm->run(); ?> --EXPECTF-- child thread main thread ================================================ FILE: tests/swoole_thread/co-stream.phpt ================================================ --TEST-- swoole_thread: co stream --SKIPIF-- --FILE-- initFreePorts(increment: crc32(__FILE__) % 1000); $tm->parentFunc = function () use ($tm) { Runtime::enableCoroutine(SWOOLE_HOOK_ALL); Co\run(function () use ($tm) { $queue = new Queue(); $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); $queue->push($fp); $thread = new Thread(__FILE__, $queue); var_dump('main thread'); $thread->join(); }); }; $tm->childFunc = function ($queue) use ($tm) { var_dump('child thread'); $fp = $queue->pop(); Co\run(function () use ($fp, $tm) { var_dump('child thread, co 0'); Co\go(function () use ($tm) { var_dump('child thread, co 1'); $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); Assert::notEmpty($client); $data = fread($client, 8192); Assert::eq($data, "hello world\n"); fclose($client); }); $conn = stream_socket_accept($fp, -1); fwrite($conn, "hello world\n"); fclose($conn); fclose($fp); }); }; $tm->run(); ?> --EXPECT-- string(11) "main thread" string(12) "child thread" string(18) "child thread, co 0" string(18) "child thread, co 1" ================================================ FILE: tests/swoole_thread/co-user-yield.phpt ================================================ --TEST-- swoole_thread: co user yield --SKIPIF-- --FILE-- false]); if (empty($args)) { $threads = []; $atomic = new Swoole\Thread\Atomic(); for ($i = 0; $i < C; $i++) { $threads[] = new Thread(__FILE__, $i, $atomic); } for ($i = 0; $i < C; $i++) { $threads[$i]->join(); } Assert::eq($atomic->get(), C * N); } else { $id = $args[0]; $atomic = $args[1]; Co\run(function () use ($atomic) { $n = N; $cid = Co::getCid(); while ($n--) { Timer::after(10, function () use ($cid) { Co::resume($cid); }); Co::yield(); $atomic->add(); } }); exit(0); } echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_thread/empty_args.phpt ================================================ --TEST-- swoole_thread: info --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_thread/exit.phpt ================================================ --TEST-- swoole_thread: lock --SKIPIF-- --FILE-- parentFunc = function () { $lock = new Lock; $lock->lock(); $thread = new Thread(__FILE__, $lock); $lock->unlock(); $thread->join(); Assert::eq($thread->getExitStatus(), CODE); echo 'DONE' . PHP_EOL; }; $tm->childFunc = function ($lock) { $lock->lock(); usleep(100_000); exit(CODE); }; $tm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_thread/fatal_error_1.inc ================================================ join(); echo "stop child thread\n"; } else { Co\run(function () { (function () { swoole_implicit_fn('fatal_error'); })(); }); } echo "DONE\n"; ================================================ FILE: tests/swoole_thread/fatal_error_1.phpt ================================================ --TEST-- swoole_thread: fatal error --SKIPIF-- --FILE-- getChildOutput(); Assert::contains($output, "start child thread\n"); Assert::contains($output, "stop child thread\n"); Assert::contains($output, "Fatal error: Uncaught Swoole\Error: test"); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/fatal_error_2.inc ================================================ join(); echo "stop child thread\n"; } else { (function () { swoole_implicit_fn('fatal_error'); })(); } echo "DONE\n"; ================================================ FILE: tests/swoole_thread/fatal_error_2.phpt ================================================ --TEST-- swoole_thread: fatal error --SKIPIF-- --FILE-- getChildOutput(); Assert::contains($output, "start child thread\n"); Assert::contains($output, "stop child thread\n"); Assert::contains($output, "Fatal error: Uncaught Swoole\Error: test"); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/fatal_error_3.phpt ================================================ --TEST-- swoole_thread: fatal error 3 --SKIPIF-- --FILE-- parentFunc = function () { register_shutdown_function(function () { echo "shutdown\n"; }); Assert::eq(Thread::getInfo()['thread_num'], 1); $thread = new Thread(__FILE__, 'child'); usleep(100000); echo "main thread\n"; Assert::eq(Thread::getInfo()['thread_num'], 2); $thread->detach(); }; $tm->childFunc = function () { echo "child thread\n"; sleep(1000); exit(0); }; $tm->run(); ?> --EXPECTF-- child thread main thread shutdown [%s] WARNING PHPCoroutine::enable_hook(): The runtime hook can only set on the main thread and no child threads have been created [%s] WARNING php_swoole_thread_rshutdown(): Fatal Error: 2 active threads are running, cannot exit safely. ================================================ FILE: tests/swoole_thread/incr.phpt ================================================ --TEST-- swoole_thread: incr/decr --SKIPIF-- --FILE-- incr(KEY_NOT_EXISTS_LVAL), 1); Assert::eq($m[KEY_NOT_EXISTS_LVAL], 1); Assert::eq($m->incr(KEY_NOT_EXISTS_DVAL, $add_dval), $add_dval); Assert::eq($m[KEY_NOT_EXISTS_DVAL], $add_dval); Assert::eq($m->incr(KEY_EXISTS_LVAL), $init_lval + 1); Assert::eq($m[KEY_EXISTS_LVAL], $init_lval + 1); Assert::eq($m->incr(KEY_EXISTS_DVAL), $init_dval + 1); Assert::eq($m[KEY_EXISTS_DVAL], $init_dval + 1); // clean $m[KEY_EXISTS_LVAL] = $init_lval; $m[KEY_EXISTS_DVAL] = $init_dval; unset($m[KEY_NOT_EXISTS_DVAL], $m[KEY_NOT_EXISTS_LVAL]); Assert::eq($m->incr(KEY_EXISTS_LVAL, $add_lval), $init_lval + $add_lval); Assert::eq($m[KEY_EXISTS_LVAL], $init_lval + $add_lval); Assert::eq($m->incr(KEY_EXISTS_DVAL, $add_lval), $init_dval + $add_lval); Assert::eq($m[KEY_EXISTS_DVAL], $init_dval + $add_lval); Assert::eq($m->decr(KEY_NOT_EXISTS_LVAL), -1); Assert::eq($m[KEY_NOT_EXISTS_LVAL], -1); $m[KEY_EXISTS_LVAL] = $init_lval; $m[KEY_EXISTS_DVAL] = $init_dval; Assert::eq($m->decr(KEY_EXISTS_LVAL, $add_lval), $init_lval - $add_lval); Assert::eq($m[KEY_EXISTS_LVAL], $init_lval - $add_lval); Assert::eq($m->decr(KEY_EXISTS_DVAL, $add_lval), $init_dval - $add_lval); Assert::eq($m[KEY_EXISTS_DVAL], $init_dval - $add_lval); Assert::eq($l->incr(0), 1); Assert::eq($l[0], 1); Assert::eq($l->incr(1, $add_lval), $add_lval); Assert::eq($l[1], $add_lval); $l[0] = 0; $l[1] = 0; Assert::eq($l->incr(0, $add_dval), intval($add_dval)); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/info.phpt ================================================ --TEST-- swoole_thread: info --SKIPIF-- --FILE-- parentFunc = function () { $thread = new Thread(__FILE__, 'child'); $info = Thread::getInfo(); Assert::true($info['is_main_thread']); $thread->join(); }; $tm->childFunc = function () { $info = Thread::getInfo(); Assert::false($info['is_main_thread']); }; $tm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/lock.phpt ================================================ --TEST-- swoole_thread: lock --SKIPIF-- --FILE-- parentFunc = function () { $lock = new Lock; $lock->lock(); $thread = new Thread(__FILE__, $lock); $lock->lock(); echo "main thread\n"; $thread->join(); }; $tm->childFunc = function ($lock) { echo "child thread\n"; usleep(200_000); $lock->unlock(); exit(0); }; $tm->run(); ?> --EXPECTF-- child thread main thread ================================================ FILE: tests/swoole_thread/map.phpt ================================================ --TEST-- swoole_thread: map --SKIPIF-- --FILE-- random_int(1, 999999999999999999), 'b' => random_bytes(128), 'c' => $uuid, 'd' => time(), ]; $m = new Map($array); Assert::eq($m->toArray(), $array); Assert::eq(count($m), count($array)); Assert::eq($m->find($uuid), 'c'); foreach ($array as $k => $v) { Assert::eq($m[$k], $array[$k]); } $array2 = [ 'key' => 'value', 'hello' => 'world', ]; $m['map'] = $array2; Assert::eq(count($m), 5); Assert::eq($m['map']->toArray(), $array2); Assert::eq(count($m['map']), count($array2)); Assert::eq($m['map']->values(), array_values($array2)); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/map2array.phpt ================================================ --TEST-- swoole_thread: map to array --SKIPIF-- --FILE-- 1111]); $ls = $LURDATE[$time]->toArray(); foreach ($ls as $k => $v) { unset($LURDATE[$time][$k]); } unset($LURDATE[$time]); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/name.phpt ================================================ --TEST-- swoole_thread: name --SKIPIF-- --FILE-- parentFunc = function () { $thread = new Thread(__FILE__, 'child'); Thread::setName('master thread'); Assert::eq(get_thread_name(), 'master thread'); $thread->join(); }; $tm->childFunc = function () { Thread::setName('child thread'); Assert::eq(get_thread_name(), 'child thread'); }; $tm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/numeric_strkey.phpt ================================================ --TEST-- swoole_thread: numeric key --SKIPIF-- --FILE-- 2222, 'test' => $uuid]); Assert::eq($arr[S_KEY], 2222); Assert::eq($arr[6666], 2222); Assert::eq($arr['test'], $uuid); unset($arr[S_KEY]); Assert::false(isset($arr[S_KEY])); Assert::keyNotExists($arr->toArray(), I_KEY); $uuid2 = uniqid(); $arr[6666.66] = $uuid2; $arr['6666.66'] = $uuid2; Assert::eq($arr[6666], $uuid2); $arr[true] = $uuid2; $arr[false] = $uuid2; $arr[null] = $uuid2; $stream = fopen('php://stdin', 'r+'); @$arr[$stream] = $uuid2; Assert::eq($arr[true], $uuid2); Assert::eq($arr[false], $uuid2); Assert::eq($arr[null], $uuid2); Assert::eq(@$arr[$stream], $uuid2); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/php_socket.phpt ================================================ --TEST-- swoole_thread: php_socket --SKIPIF-- --FILE-- initFreePorts(increment: crc32(__FILE__) % 1000); $tm->parentFunc = function () use ($tm) { $queue = new Queue(); $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); socket_bind($sock, '127.0.0.1', $tm->getFreePort()); $queue->push($sock); $thread = new Thread(__FILE__, $queue, 0); var_dump('main thread'); $thread->join(); }; $tm->childFunc = function ($queue, $id) use ($tm) { if ($id === 0) { var_dump('child thread 0'); $svr_sock = $queue->pop(); socket_listen($svr_sock, 128); $thread = new Thread(__FILE__, $queue, 1); $conn = socket_accept($svr_sock); socket_write($conn, "Swoole: hello world\n"); socket_close($conn); socket_close($svr_sock); $thread->join(); } else { var_dump('child thread 1'); $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_connect($sock, '127.0.0.1', $tm->getFreePort()); socket_send($sock, "hello world", 0, 0); socket_recv($sock, $buf, 1024, 0); Assert::eq($buf, "Swoole: hello world\n"); socket_close($sock); } exit(0); }; $tm->run(); echo "Done\n"; ?> --EXPECT-- string(11) "main thread" string(14) "child thread 0" string(14) "child thread 1" Done ================================================ FILE: tests/swoole_thread/pipe.phpt ================================================ --TEST-- swoole_thread: pipe --SKIPIF-- --FILE-- recv(8192), $rdata); $thread->join(); echo "DONE\n"; }); } else { $socket = $args[0]; $rdata = $args[1]; Co\run(function () use ($socket, $rdata, $argv) { usleep(100); shell_exec('sleep 0.01'); $socket->send($rdata); }); exit(0); } ?> --EXPECTF-- DONE ================================================ FILE: tests/swoole_thread/priority.phpt ================================================ --TEST-- swoole_thread: priority --SKIPIF-- --FILE-- parentFunc = function () { $thread = new Thread(__FILE__, 'child'); test_thread_priority(10, Thread::SCHED_FIFO); $thread->join(); }; $tm->childFunc = function () { test_thread_priority(5, Thread::SCHED_RR); }; $tm->run(); ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/putenv.phpt ================================================ --TEST-- swoole_thread: putenv --SKIPIF-- --FILE-- join(); } for ($i = 0; $i < $c; $i++) { $env = getenv('TEST_THREAD_' . $i); Assert::notEmpty($env); } ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/queue.phpt ================================================ --TEST-- swoole_thread: queue --SKIPIF-- --FILE-- push($rdata, Queue::NOTIFY_ONE); usleep(random_int(100, 1000)); } $n = 4; while ($n--) { $queue->push('', Queue::NOTIFY_ONE); } for ($i = 0; $i < C; $i++) { $threads[$i]->join(); $total_child += $map[$i]; } Assert::eq($total_parent, $total_child); } else { $i = $args[0]; $queue = $args[1]; $map = $args[2]; $map[$i] = 0; while (1) { $job = $queue->pop(-1); if (!$job) { break; } $map[$i] += strlen($job); Assert::assert(strlen($job), 16); } exit(0); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/queue_notify_all.phpt ================================================ --TEST-- swoole_thread: queue notify all --SKIPIF-- --FILE-- wait(); usleep(10000); for ($i = 0; $i < C; $i++) { $queue->push($uuid, Queue::NOTIFY_ALL); } for ($i = 0; $i < C; $i++) { $threads[$i]->join(); } Assert::eq($queue->count(), 0); } else { $i = $args[0]; $queue = $args[1]; $uuid = $args[2]; $barrier = $args[3]; $barrier->wait(); $job = $queue->pop(-1); if ($job !== null) { Assert::eq($job, $uuid); } else { Assert::eq(swoole_last_error(), SWOOLE_ERROR_NO_PAYLOAD); } exit(0); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_thread/server/base.phpt ================================================ --TEST-- swoole_thread/server: base --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'open_eof_check' => true, 'package_eof' => "\r\n", 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $json = json_decode(rtrim($data)); if ($json->type == 'eof') { $serv->send($fd, "EOF\r\n"); } }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { thread_server_test_eof_client($port); }); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/bug_5662.phpt ================================================ --TEST-- swoole_thread/server: Github #5662 --SKIPIF-- --FILE-- set([ 'log_file' => '/dev/null', 'worker_num' => 2, 'max_request' => 5, 'init_arguments' => function () { global $queue; $queue = new Queue(); return [$queue]; } ]); $server->on('WorkerStart', function (Swoole\Server $server, $workerId) { [$queue] = Thread::getArguments(); $queue->push('start', Queue::NOTIFY_ALL); }); $server->addProcess(new Swoole\Process(function ($process) use ($server, $port) { [$queue] = Thread::getArguments(); Assert::true($queue->pop(-1) == 'start'); for ($i = 0; $i < 20; $i++) { Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); } $server->shutdown(); })); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end('OK'); }); $server->start(); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/server/bug_5694.phpt ================================================ --TEST-- swoole_thread/server: Github #5694 --SKIPIF-- --FILE-- set([ 'log_file' => '/dev/null', 'worker_num' => 1, 'task_worker_num' => 1, 'max_request' => 1, 'heartbeat_check_interval'=> 1, 'heartbeat_idle_time'=> 2, 'enable_coroutine' => true, 'hook_flags' => SWOOLE_HOOK_ALL, 'init_arguments' => function () { global $queue; $queue = new Queue(); return [$queue]; } ]); $server->on('WorkerStart', function (Swoole\Server $server, $workerId) { [$queue] = Thread::getArguments(); $queue->push('start', Queue::NOTIFY_ALL); }); $server->on('Task', function (Swoole\Server $server, int $task_id, int $src_worker_id, mixed $data) { var_dump($data); }); $server->addProcess(new Swoole\Process(function ($process) use ($server, $port) { [$queue] = Thread::getArguments(); Assert::true($queue->pop(-1) == 'start'); Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); sleep(1); Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); sleep(2); $server->shutdown(); })); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { $server->task('12313'); $response->end('OK'); }); $server->start(); ?> --EXPECT-- string(5) "12313" string(5) "12313" ================================================ FILE: tests/swoole_thread/server/create_response.phpt ================================================ --TEST-- swoole_thread/server: create response --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'task_worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == 5) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { $resp->detach(); $serv->task(['fd' => $resp->fd, 'uid' => $req->get['uid']]); }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { $response = Swoole\Http\Response::create($data['fd']); $response->end($data['uid']); $response->close(); }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; Assert::eq($atomic1->get(), 5); Assert::eq($atomic2->get(), 5); echo "shutdown\n"; }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); $reqUid = uniqid(); Assert::eq(file_get_contents('http://127.0.0.1:' . $port . '/?uid=' . $reqUid), $reqUid); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECTF-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/exit.phpt ================================================ --TEST-- swoole_thread/server: exit --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_file' => '/dev/null', 'log_level' => SWOOLE_LOG_ERROR, 'enable_coroutine' => false, 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == 2) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } echo 'worker start', PHP_EOL; }); $serv->on('workerError', function (Server $serv, $workerId, $workerPid, $status, $signal) { Assert::eq($status, CODE); echo 'worker error', PHP_EOL; }); $serv->on('WorkerStop', function (Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); }); $serv->on('Request', function ($req, $resp) use ($serv) { if ($req->server['request_uri'] == '/exit') { swoole_implicit_fn('bailout', CODE); } }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 3); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); $rs = @file_get_contents('http://127.0.0.1:' . $port . '/exit'); Assert::false($rs); usleep(200_000); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECTF-- worker start worker start begin worker error worker start done shutdown ================================================ FILE: tests/swoole_thread/server/fatal_error.phpt ================================================ --TEST-- swoole_thread/server: fatal error --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == 2) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { if ($req->server['request_uri'] == '/error') { trigger_error('user fatal error', E_USER_ERROR); } }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 3); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); $rs = @file_get_contents('http://127.0.0.1:' . $port . '/error'); Assert::false($rs); usleep(100_000); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECTF-- begin Fatal error: user fatal error in %s on line %d done shutdown --EXPECTF_85-- begin Fatal error: user fatal error in %s on line %d Stack trace: #0 %s(%d): trigger_error('%s', %d) #1 [internal function]: {closure:%s:%d}(Object(Swoole\Http\Request), Object(Swoole\Http\Response)) #2 {main} done shutdown ================================================ FILE: tests/swoole_thread/server/functions.inc ================================================ set([ 'open_eof_check' => true, 'package_eof' => "\r\n", ]); Assert::assert($cli->connect('127.0.0.1', $port, 2)); $cli->send(json_encode(['type' => 'eof']) . "\r\n"); Assert::eq($cli->recv(), "EOF\r\n"); } ================================================ FILE: tests/swoole_thread/server/heartbeat.phpt ================================================ --TEST-- swoole_thread/server: heartbeat --SKIPIF-- --FILE-- set(array( 'worker_num' => 1, 'log_level' => SWOOLE_LOG_ERROR, 'heartbeat_check_interval' => 1, 'heartbeat_idle_time' => 2, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $port, 5, 0)) { echo "Error: " . $client->errCode; die("\n"); } $s1 = time(); Assert::same(@$client->recv(), ''); $s2 = time(); Assert::assert($s2 - $s1 > 1); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/hook_flags.phpt ================================================ --TEST-- swoole_thread/server: base --SKIPIF-- --FILE-- set(array( 'worker_num' => 4, 'log_level' => SWOOLE_LOG_ERROR, 'hook_flags' => SWOOLE_HOOK_ALL, 'init_arguments' => function () { global $queue, $atomic; $queue = new Queue(); $atomic = new Atomic(0); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); Assert::eq(Runtime::getHookFlags(), SWOOLE_HOOK_ALL); $output = file_get_contents("http://127.0.0.1:$port/"); $queue->push($output, Queue::NOTIFY_ALL); }); $serv->on('Request', function ($req, $resp) { usleep(100000); $resp->end('DONE'); }); $serv->on('shutdown', function ($server) { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), $server->setting['worker_num']); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); for ($i = 0; $i < 4; $i++) { echo $queue->pop(-1), PHP_EOL; $atomic->add(1); } $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- DONE DONE DONE DONE shutdown ================================================ FILE: tests/swoole_thread/server/listen.phpt ================================================ --TEST-- swoole_thread/server: listen --SKIPIF-- --FILE-- listen('127.0.0.1', $port, SWOOLE_SOCK_TCP); $serv->set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'open_eof_check' => true, 'package_eof' => "\r\n", 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $json = json_decode(rtrim($data)); if ($json->type == 'eof') { $serv->send($fd, "EOF\r\n"); } }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { Co::join([ Co\go(function () use ($port) { thread_server_test_eof_client($port); }), Co\go(function () use ($port) { thread_server_test_eof_client($port + 1); }) ]); }); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/manager_timer.phpt ================================================ --TEST-- swoole_thread/server: reload --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic1->add(); }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { }); $serv->on('managerStart', function ($serv) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); $count = 0; Timer::tick(100, function () use ($queue, &$count) { $count++; if ($count == 5) { $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); } }); }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 2); Assert::eq($atomic2->get(), 2); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); echo $queue->pop(-1); echo $queue->pop(-1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin 1 begin 2 done shutdown ================================================ FILE: tests/swoole_thread/server/reload.phpt ================================================ --TEST-- swoole_thread/server: reload --SKIPIF-- --FILE-- set(array( 'worker_num' => 3, 'task_worker_num' => 4, 'log_level' => SWOOLE_LOG_ERROR, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == 14) { $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { }); $serv->on('managerStart', function ($serv) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); }); $serv->on('beforeReload', function ($serv) { echo 'beforeReload', PHP_EOL; }); $serv->on('afterReload', function ($serv) { echo 'afterReload', PHP_EOL; [$queue, $atomic1, $atomic2] = Thread::getArguments(); }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 14); Assert::eq($atomic2->get(), 14); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); echo $queue->pop(-1); $serv->reload(); echo $queue->pop(-1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin 1 beforeReload afterReload begin 2 done shutdown ================================================ FILE: tests/swoole_thread/server/reload_task_workers.phpt ================================================ --TEST-- swoole_thread/server: reload task workers --SKIPIF-- --FILE-- set(array( 'worker_num' => 3, 'task_worker_num' => 4, 'log_level' => SWOOLE_LOG_ERROR, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $c = $atomic1->add(); if ($c == 11) { $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { }); $serv->on('managerStart', function ($serv) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); }); $serv->on('beforeReload', function ($serv) { echo 'beforeReload', PHP_EOL; }); $serv->on('afterReload', function ($serv) { echo 'afterReload', PHP_EOL; [$queue, $atomic1, $atomic2] = Thread::getArguments(); }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 11); Assert::eq($atomic2->get(), 11); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); echo $queue->pop(-1); $serv->reload(true); echo $queue->pop(-1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin 1 beforeReload afterReload begin 2 done shutdown ================================================ FILE: tests/swoole_thread/server/reload_task_workers_async.phpt ================================================ --TEST-- swoole_thread/server: reload task workers [async] --SKIPIF-- --FILE-- set(array( 'worker_num' => 3, 'task_worker_num' => 4, 'log_level' => SWOOLE_LOG_ERROR, 'task_enable_coroutine' => true, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $c = $atomic1->add(); if ($c == 11) { $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { }); $serv->on('managerStart', function ($serv) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); }); $serv->on('beforeReload', function ($serv) { echo 'beforeReload', PHP_EOL; }); $serv->on('afterReload', function ($serv) { echo 'afterReload', PHP_EOL; [$queue, $atomic1, $atomic2] = Thread::getArguments(); }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 11); Assert::eq($atomic2->get(), 11); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); echo $queue->pop(-1); $serv->reload(true); echo $queue->pop(-1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin 1 beforeReload afterReload begin 2 done shutdown ================================================ FILE: tests/swoole_thread/server/reset_concurrency.phpt ================================================ --TEST-- swoole_thread/server: reset concurrency [SWOOLE_THREAD] --SKIPIF-- --FILE-- set(array( 'worker_num' => WORKER_NUM, 'max_concurrency' => 160, 'log_level' => SWOOLE_LOG_ERROR, 'log_file' => '/dev/null', 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == WORKER_NUM) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Server $serv, $workerId) { echo 'WORKER STOP', PHP_EOL; }); $serv->on('pipeMessage', function (Server $serv, $wid, $msg) { swoole_implicit_fn('bailout'); }); $serv->on('Request', function (Request $req, Response $resp) use ($serv) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $c = $atomic2->add(); if ($c < N) { Co::sleep(100); } elseif ($c == N) { $stats = $serv->stats(); Assert::eq($stats['concurrency'], N); $wid = $serv->getWorkerId(); for ($i = 0; $i < WORKER_NUM; $i++) { if ($i !== $wid) { $serv->sendMessage('error', $i); } } swoole_implicit_fn('bailout'); } else { $stats = $serv->stats(); Assert::eq($stats['concurrency'], 1); $resp->end(json_encode($stats)); } }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'SHUTDOWN', PHP_EOL; Assert::eq($atomic1->get(), WORKER_NUM * 2); Assert::eq($atomic2->get(), N + 1); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv, $port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $queue->pop(-1); run(function () use ($port, $serv, $atomic1, $queue) { $n = N; $coroutines = []; while ($n--) { $coroutines[] = go(function () use ($port) { $client = new Client('127.0.0.1', $port); $client->set(['timeout' => 10]); Assert::eq($client->get('/'), false); Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); }); } Co::join($coroutines); while (1) { if ($atomic1->get() == WORKER_NUM * 2) { break; } Co::sleep(0.1); } $client = new Client('127.0.0.1', $port); Assert::assert($client->get('/')); $stats = json_decode($client->getBody()); Assert::eq($stats->concurrency, 1); $serv->shutdown(); echo "DONE\n"; }); })); $serv->start(); ?> --EXPECT-- DONE WORKER STOP WORKER STOP WORKER STOP WORKER STOP SHUTDOWN ================================================ FILE: tests/swoole_thread/server/send_in_user_process.phpt ================================================ --TEST-- swoole_thread/server: send in user process --SKIPIF-- --FILE-- pop(-1); $reqUid = uniqid(); Assert::eq(file_get_contents('http://127.0.0.1:' . $port . '/?uid=' . $reqUid), $reqUid); echo "done\n"; $serv->shutdown(); }); $serv->addProcess($proc); $proc2 = new Swoole\Process(function ($process) use ($serv) { $json = $process->read(); $data = json_decode($json, true); $response = Swoole\Http\Response::create($data['fd']); $response->end($data['uid']); $response->close(); }); $serv->addProcess($proc2); $serv->set(array( 'worker_num' => 1, 'log_level' => SWOOLE_LOG_ERROR, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(0); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); $atomic->add(); $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic] = Thread::getArguments(); $atomic->add(); }); $serv->on('Request', function ($req, $resp) use ($serv, $proc2) { $resp->detach(); $proc2->write(json_encode(['fd' => $resp->fd, 'uid' => $req->get['uid']])); }); $serv->on('shutdown', function () { global $queue, $atomic; Assert::eq($atomic->get(), 2); echo "shutdown\n"; }); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/send_large_packet.phpt ================================================ --TEST-- swoole_thread/server: send large packet --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on("WorkerStop", function (Swoole\Server $serv, $workerId) { }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $send_data = str_repeat('A', SIZE - 12) . substr($data, -8, 8); $serv->send($fd, pack('N', strlen($send_data)) . $send_data); }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); $c = MAX_CONCURRENCY_LOW; $n = MAX_REQUESTS_LOW; for ($i = 0; $i < $c; $i++) { go(function () use ($i, $n, $atomic, $port) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, ]); if ($cli->connect('127.0.0.1', $port, 2) == false) { echo "ERROR\n"; return; } for ($i = 0; $i < $n; $i++) { $sid = strval(rand(10000000, 99999999)); $send_data = str_repeat('A', 1000) . $sid; $cli->send(pack('N', strlen($send_data)) . $send_data); $data = $cli->recv(); Assert::same(strlen($data), SIZE); Assert::same($sid, substr($data, -8, 8)); } }); } Swoole\Event::wait(); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/setting.phpt ================================================ --TEST-- swoole_thread/server: setting --SKIPIF-- --FILE-- set(array( 'worker_num' => N, 'log_level' => SWOOLE_LOG_ERROR, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(0); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); Assert::isArray($serv->setting); if ($atomic->add(1) == N) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), N); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); echo $queue->pop(-1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/server/stop_worker.phpt ================================================ --TEST-- swoole_thread/server: stop worker --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'task_worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'init_arguments' => function () { global $queue, $atomic1, $atomic2; $queue = new Swoole\Thread\Queue(); $atomic1 = new Swoole\Thread\Atomic(0); $atomic2 = new Swoole\Thread\Atomic(0); return [$queue, $atomic1, $atomic2]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); if ($atomic1->add() == 5) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { [$queue, $atomic1, $atomic2] = Thread::getArguments(); $atomic2->add(); }); $serv->on('Request', function ($req, $resp) use ($serv) { if ($req->server['request_uri'] == '/stop') { $serv->stop($req->get['worker'] ?? 0); $resp->end("OK\n"); } }); $serv->on('Task', function ($serv, $task_id, $worker_id, $data) { }); $serv->on('shutdown', function () { global $queue, $atomic1, $atomic2; echo 'shutdown', PHP_EOL; Assert::eq($atomic1->get(), 7); Assert::eq($atomic2->get(), 7); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); echo file_get_contents('http://127.0.0.1:' . $port . '/stop?worker=' . random_int(0, 1)); echo file_get_contents('http://127.0.0.1:' . $port . '/stop?worker=' . random_int(2, 4)); sleep(1); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECTF-- begin OK OK done shutdown ================================================ FILE: tests/swoole_thread/server/taskCo.phpt ================================================ --TEST-- swoole_thread/server: taskCo --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'task_worker_num' => 2, 'enable_coroutine' => true, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('task', function (Server $server, int $task_id, int $src_worker_id, mixed $data) { $server->finish($data * 7); } ); $serv->on('Request', function (Request $request, Response $response) use ($serv) { $tasks = range(1, 16); $result = $serv->taskCo($tasks); $response->header('Content-Type', 'application/json'); $response->end(json_encode(["count" => count($result)])); } ); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; }); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin {"count":16} done shutdown ================================================ FILE: tests/swoole_thread/server/taskWaitMulti.phpt ================================================ --TEST-- swoole_thread/server: taskWaitMulti --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'task_worker_num' => 2, 'enable_coroutine' => false, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('task', function (Server $server, int $task_id, int $src_worker_id, mixed $data) { $server->finish($data * 7); } ); $serv->on('Request', function (Request $request, Response $response) use ($serv) { $tasks = [9999, 8888]; $result = $serv->taskWaitMulti($tasks); $response->header('Content-Type', 'application/json'); $_rs = array_values($result); sort($_rs); $response->end(json_encode(["data" => $_rs])); } ); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; }); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin {"data":[62216,69993]} done shutdown ================================================ FILE: tests/swoole_thread/server/udp_port.phpt ================================================ --TEST-- swoole_thread/server: listen udp port --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'reload_async' => true, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(0); return [$queue, $atomic]; } )); $udp = $serv->addListener('127.0.0.1', $port + 1, SWOOLE_SOCK_UDP); $udp->on('packet', function ($serv, $data, $addr) { echo "udp packet\n"; $serv->sendto($addr['address'], $addr['port'], $data); }); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($atomic->add() == 1) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } echo "worker start\n"; }); $serv->on('message', function (Server $server, $frame) { echo "message\n"; }); $serv->on('workerExit', function (Server $server, $wid) { var_dump('worker exit: ' . $wid); Timer::clearAll(); }); $serv->on('shutdown', function (Server $server) { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), $server->setting['worker_num']); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { $udp_sock = stream_socket_client('udp://127.0.0.1:' . ($port + 1), $errno, $errstr); $pkt = random_bytes(1024); fwrite($udp_sock, $pkt); $data = fread($udp_sock, 1024); Assert::eq($pkt, $data); }); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- worker start worker start begin udp packet done shutdown ================================================ FILE: tests/swoole_thread/server/websocket.phpt ================================================ --TEST-- swoole_thread/server: websocket --SKIPIF-- --FILE-- set(array( 'worker_num' => 2, 'log_level' => SWOOLE_LOG_ERROR, 'init_arguments' => function () { global $queue, $atomic; $queue = new Swoole\Thread\Queue(); $atomic = new Swoole\Thread\Atomic(1); return [$queue, $atomic]; } )); $serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { [$queue, $atomic] = Thread::getArguments(); if ($workerId == 0) { $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); } }); $serv->on('message', function (Server $server, $frame) { $server->push($frame->fd, $frame->data); }); $serv->on('shutdown', function () { global $queue, $atomic; echo 'shutdown', PHP_EOL; Assert::eq($atomic->get(), 0); }); $serv->addProcess(new Swoole\Process(function ($process) use ($serv) { [$queue, $atomic] = Thread::getArguments(); global $port; echo $queue->pop(-1); Co\run(function () use ($port) { $cli = new Co\Http\Client('127.0.0.1', $port); $data = base64_decode(random_bytes(2048)); Assert::assert($cli->upgrade('/')); $cli->push($data); $frame = $cli->recv(); Assert::eq($frame->data, $data); }); $atomic->set(0); echo "done\n"; $serv->shutdown(); })); $serv->start(); ?> --EXPECT-- begin done shutdown ================================================ FILE: tests/swoole_thread/shell_exec.phpt ================================================ --TEST-- swoole_thread: lock --SKIPIF-- --FILE-- parentFunc = function () { $lock = new Lock; $lock->lock(); $thread = new Thread(__FILE__, $lock); $lock->unlock(); $thread->join(); Assert::eq($thread->getExitStatus(), 0); echo 'DONE' . PHP_EOL; }; $tm->childFunc = function ($lock) { $lock->lock(); usleep(100_000); Co\run(function () { shell_exec('ls /tmp'); sleep(1); gethostbyname('www.baidu.com'); }); exit(0); }; $tm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_thread/signal.phpt ================================================ --TEST-- swoole_thread: signal --SKIPIF-- --FILE-- send('exit'); } Co\go(function () use ($parent_pipe, $thread) { // 从管道中读取子线程退出的信息 echo $parent_pipe->recv(8192), PHP_EOL; // 回收子线程 $thread->join(); }); }); } else { $child_pipe = $args[0]; Co\run(function () use ($child_pipe) { // 收到父线程的指令,开始退出 echo $child_pipe->recv(8192), PHP_EOL; // 通知父线程已退出 $child_pipe->send('child exit'); }); exit(0); } ?> --EXPECTF-- timer signal term exit child exit ================================================ FILE: tests/swoole_thread/sort.phpt ================================================ --TEST-- swoole_thread: sort --SKIPIF-- --FILE-- "lemon", "o" => "orange", "O" => "Orange", "O1" => "Orange1", "o2" => "orange2", "O3" => "Orange3", "o20" => "orange20", "b" => "banana", ); $unsorted_map = new Map($original_map); $unsorted_map->sort(); $copied_map = $original_map; asort($copied_map); Assert::eq($unsorted_map->toArray(), $copied_map); $original_list = array( 100, 33, 555, 22 ); $copied_list = $original_list; $unsorted_list = new ArrayList($original_list); $unsorted_list->sort(); sort($copied_list); Assert::eq($unsorted_list->toArray(), $copied_list); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/stdio.phpt ================================================ --TEST-- swoole_thread: stdio --SKIPIF-- --FILE-- parentFunc = function () { $lock = new Lock; $lock->lock(); $thread = new Thread(__FILE__, $lock); $lock->lock(); $thread->join(); echo "main thread\n"; }; $tm->childFunc = function ($lock) { echo "child thread\n"; usleep(200_000); $lock->unlock(); fwrite(STDOUT, "hello swoole\n"); Assert::notEmpty(STDIN); exit(0); }; $tm->run(); echo "DONE\n"; ?> --EXPECTF-- child thread hello swoole main thread DONE ================================================ FILE: tests/swoole_thread/stream.phpt ================================================ --TEST-- swoole_thread: stream --SKIPIF-- --FILE-- initFreePorts(increment: crc32(__FILE__) % 1000); $tm->parentFunc = function () use ($tm) { $queue = new Queue(); $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); $queue->push($fp); $thread = new Thread(__FILE__, $queue, 0); var_dump('main thread'); $thread->join(); }; $tm->childFunc = function ($queue, $id) use ($tm) { if ($id === 0) { var_dump('child thread 0'); $fp = $queue->pop(); $thread = new Thread(__FILE__, $queue, 1); $conn = stream_socket_accept($fp, -1); fwrite($conn, "hello world\n"); fclose($conn); fclose($fp); $thread->join(); } else { var_dump('child thread 1'); $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); Assert::notEmpty($client); $data = fread($client, 8192); Assert::eq($data, "hello world\n"); fclose($client); } }; $tm->run(); ?> --EXPECT-- string(11) "main thread" string(14) "child thread 0" string(14) "child thread 1" ================================================ FILE: tests/swoole_thread/stream_arg.phpt ================================================ --TEST-- swoole_thread: stream as a thread argument --SKIPIF-- --FILE-- initFreePorts(increment: crc32(__FILE__) % 1000); $tm->parentFunc = function () use ($tm) { $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); $thread = new Thread(__FILE__, $fp, 0); var_dump('main thread'); $thread->join(); }; $tm->childFunc = function ($fp, $id) use ($tm) { if ($id === 0) { var_dump('child thread 0'); $thread = new Thread(__FILE__, $fp, 1); $conn = stream_socket_accept($fp, -1); fwrite($conn, "hello world\n"); fclose($conn); fclose($fp); $thread->join(); } else { var_dump('child thread 1'); $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); Assert::notEmpty($client); $data = fread($client, 8192); Assert::eq($data, "hello world\n"); fclose($client); } }; $tm->run(); ?> --EXPECT-- string(11) "main thread" string(14) "child thread 0" string(14) "child thread 1" ================================================ FILE: tests/swoole_thread/thread_status.phpt ================================================ --TEST-- swoole_thread: thread status --SKIPIF-- --FILE-- joinable()); Assert::true($t1->isAlive()); Assert::true($t1->join()); Assert::false($t1->joinable()); Assert::false($t1->isAlive()); $t2 = new Thread(TESTS_API_PATH . '/swoole_thread/sleep.php'); $t2->detach(); usleep(10); Assert::false($t2->joinable()); Assert::true($t2->isAlive()); while (Thread::activeCount() > 1) { usleep(10); } Assert::false($t2->isAlive()); ?> --EXPECT-- ================================================ FILE: tests/swoole_thread/yield.phpt ================================================ --TEST-- swoole_thread: thread status --SKIPIF-- --FILE-- isAlive()); $t1->detach(); Thread::yield(); usleep(10); Assert::true($t1->isAlive()); while (Thread::activeCount() > 1) { usleep(10); } Assert::false($t1->isAlive()); ?> --EXPECT-- ================================================ FILE: tests/swoole_timer/after.phpt ================================================ --TEST-- swoole_timer: swoole_timer_after,swoole_timer_exists,swoole_timer_clear --SKIPIF-- --FILE-- timer_id && Swoole\Timer::exists($this->timer_id)) { Swoole\Timer::clear($this->timer_id); $this->timer_id = null; } if (self::$count == 10) { return; } $this->timer_id = Swoole\Timer::after($ms, array($this, 'onTimerTick')); Assert::assert($this->timer_id > 0); } public function onTimerTick() { self::$count++; echo "onTimerTick:" . self::$count . "\n"; $this->resetTimer(10); } } $timer_test = new TimerTest(); $timer_test->onTimerTick(); ?> --EXPECT-- onTimerTick:1 onTimerTick:2 onTimerTick:3 onTimerTick:4 onTimerTick:5 onTimerTick:6 onTimerTick:7 onTimerTick:8 onTimerTick:9 onTimerTick:10 ================================================ FILE: tests/swoole_timer/after_fork.phpt ================================================ --TEST-- swoole_timer: after fork --SKIPIF-- --FILE-- start(); $process::wait(); Assert::true(Swoole\Timer::clear($id)); } Swoole\Event::wait(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/bug_2342.phpt ================================================ --TEST-- swoole_timer: bug Github#2342 --SKIPIF-- --FILE-- data = str_repeat('A', 1024 * 1024 * 1); } } function worker($timerId, $info) { Swoole\Timer::clear($timerId); } function manager($timerID) { Swoole\Timer::tick( 10, 'worker', new workerInfo()); } $mem = memory_get_usage(); $timerId = Swoole\Timer::tick(50, 'manager'); Swoole\Timer::after(500, function()use($timerId){ Swoole\Timer::clear($timerId); }); Swoole\Event::wait(); Assert::assert($mem + 1024 * 1024 * 1 > memory_get_usage()); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/bug_4794.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- push(['rand' => 9999]); }); go(function () use ($channel) { $data = $channel->pop(0.00001); var_dump($data); }); }); ?> --EXPECT-- array(1) { ["rand"]=> int(9999) } ================================================ FILE: tests/swoole_timer/bug_4794_2.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- start(); $processSlow = new Process(function () use ($atomic) { $atomic->wait(10); usleep(10 * 1000); }); $processSlow->start(); Coroutine\run(function () use ($processFast, $processSlow, $atomic) { for ($n = MAX_REQUESTS; $n--;) { $status = System::waitPid($processSlow->pid, 0.0001); Assert::false($status); Assert::same(swoole_last_error(), SOCKET_ETIMEDOUT); } $atomic->wakeup(); $status = System::waitPid($processSlow->pid, 1); Assert::same($status['pid'], $processSlow->pid); var_dump($status); $status = System::waitPid($processFast->pid); Assert::same($status['pid'], $processFast->pid); var_dump($status); }); ?> --EXPECTF-- array(3) { ["pid"]=> int(%d) ["code"]=> int(0) ["signal"]=> int(0) } array(3) { ["pid"]=> int(%d) ["code"]=> int(0) ["signal"]=> int(0) } ================================================ FILE: tests/swoole_timer/bug_4794_3.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/bug_4794_4.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- wait(); echo "2\n"; switch_process(); Process::kill($pid, SIGUSR1); $atomic->wait(); echo "6\n"; switch_process(); Process::kill($pid, SIGUSR2); echo "8\n"; }); $killer->start(); Coroutine\run(function () use ($atomic) { Coroutine::sleep(0.001); switch_process(); $atomic->wakeup(); echo "1\n"; Assert::eq(System::waitSignal(SIGUSR1), SIGUSR1); echo "3\n"; Assert::false(System::waitSignal(SIGUSR2, 0.0001)); echo "4\n"; $atomic->wakeup(); echo "5\n"; Assert::eq(System::waitSignal(SIGUSR2), SIGUSR2); echo "7\n"; System::wait(0.0001); echo "9\n"; }); ?> --EXPECT-- 1 2 3 4 5 6 8 7 9 ================================================ FILE: tests/swoole_timer/bug_4794_5.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_timer/bug_4794_6.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- connect('www.baidu.com', 80); $info = $conn->getpeername(); Assert::eq($info['host'], System::gethostbyname('www.baidu.com'), 'AF_INET', 0.0001); Assert::eq($info['port'], 80); } ); ?> --EXPECT-- ================================================ FILE: tests/swoole_timer/bug_4794_7.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- connect("11.11.11.11", 80, 0.0005); Assert::false($r); Assert::eq($cli->errCode, SOCKET_ETIMEDOUT); ?> --EXPECT-- ================================================ FILE: tests/swoole_timer/bug_4794_8.phpt ================================================ --TEST-- swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid --SKIPIF-- --FILE-- bind('127.0.0.1', 9601)); Assert::assert($sock->listen(512)); $conn = $sock->accept(0.0001); Assert::assert($conn); Assert::isInstanceOf($conn, Swoole\Coroutine\Socket::class); $data = $conn->recv(0.0001); $json = json_decode($data, true); Assert::same($json['data'] ?? '', 'hello'); $conn->send("world\n"); $conn->close(); }); go(function () { $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($conn->connect('127.0.0.1', 9601)); $conn->send(json_encode(['data' => 'hello'])); echo $conn->recv(); }); ?> --EXPECT-- world ================================================ FILE: tests/swoole_timer/call_private.phpt ================================================ --TEST-- swoole_timer: call private method --SKIPIF-- --FILE-- expectExitCode(255); $output = $pm->getChildOutput(); if (PHP_VERSION_ID < 80000) { Assert::contains($output, 'Uncaught TypeError: Argument 2 passed to Swoole\Timer::after() must be callable, array given'); } else { Assert::contains($output, 'Uncaught TypeError: Swoole\Timer::after(): Argument #2 ($callback) must be a valid callback, class Test does not have a method "not_exist"'); } // private method //------------------------------------------------------------------------------------------------------------------ $pm = ProcessManager::exec(function () { Swoole\Timer::After(1, [Test::class, 'foo']); }); $pm->expectExitCode(255); $output = $pm->getChildOutput(); if (PHP_VERSION_ID < 80000) { Assert::contains($output, 'Uncaught TypeError: Argument 2 passed to Swoole\Timer::after() must be callable, array given'); } else { Assert::contains($output, 'Swoole\Timer::after(): Argument #2 ($callback) must be a valid callback, cannot access private method Test::foo()'); } // private method //------------------------------------------------------------------------------------------------------------------ $pm = ProcessManager::exec(function () { Swoole\Timer::After(1, [new Test, 'bar']); }); $pm->expectExitCode(255); $output = $pm->getChildOutput(); if (PHP_VERSION_ID < 80000) { Assert::contains($output, 'Uncaught TypeError: Argument 2 passed to Swoole\Timer::after() must be callable, array given'); } else { Assert::contains($output, 'Swoole\Timer::after(): Argument #2 ($callback) must be a valid callback, cannot access private method Test::bar()'); } ?> --EXPECT-- ================================================ FILE: tests/swoole_timer/callback_bug_with_array.phpt ================================================ --TEST-- swoole_timer: Timer callback --SKIPIF-- --FILE-- test; } } for ($c = MAX_CONCURRENCY; $c--;) { Swoole\Timer::after($c + 1, [new TestCo, 'foo']); } ?> --EXPECTF-- ================================================ FILE: tests/swoole_timer/clearAll.phpt ================================================ --TEST-- swoole_timer: #2697 --SKIPIF-- --FILE-- set(['log_file' => '/dev/null']); $server->on('workerStart', function (Swoole\Server $server, int $worker_id) { Swoole\Timer::after(1000, function () { var_dump('never here'); }); Swoole\Timer::tick(1000, function () { var_dump('never here'); }); Swoole\Timer::clearAll(); if ($worker_id === 0) { Swoole\Timer::after(200, function () use ($server) { $server->shutdown(); }); } }); $server->on('receive', function () { }); $server->start(); echo "DONE\n"; ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/enable_coroutine.phpt ================================================ --TEST-- swoole_timer: enable_coroutine setting --SKIPIF-- --FILE-- false ]); Swoole\Timer::after(1, function () { $uid = Co::getuid(); echo "#{$uid}\n"; }); ?> --EXPECT-- #-1 ================================================ FILE: tests/swoole_timer/enable_coroutine2.phpt ================================================ --TEST-- swoole_timer: enable_coroutine setting --SKIPIF-- --FILE-- false ]); Swoole\Timer::after(1, function () { $uid = Co::getuid(); echo "#{$uid}\n"; }); Swoole\Event::wait(); swoole_async_set([ 'enable_coroutine' => true ]); Swoole\Timer::after(1, function () { $uid = Co::getuid(); echo "#{$uid}\n"; }); Swoole\Event::wait(); ?> --EXPECT-- #-1 #1 ================================================ FILE: tests/swoole_timer/function_alias.phpt ================================================ --TEST-- swoole_timer: function alias --SKIPIF-- --FILE-- --EXPECTF-- bool(true) ================================================ FILE: tests/swoole_timer/greater_than_0.phpt ================================================ --TEST-- swoole_timer: Timer must be greater than 0 --SKIPIF-- --FILE-- --EXPECTF-- ================================================ FILE: tests/swoole_timer/info.phpt ================================================ --TEST-- swoole_timer: list --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/list.phpt ================================================ --TEST-- swoole_timer: list --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/manager.phpt ================================================ --TEST-- swoole_timer: timer in manager --SKIPIF-- --FILE-- setWaitTimeout(10); $pm->parentFunc = function ($pid) use ($pm) { $fp = fopen(RES_FILE, "rw"); while(!feof($fp)) { $line = fgets($fp); if ($line) { echo $line; } } unlink(RES_FILE); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Swoole\Server("0.0.0.0", $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( 'worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on('managerStart', function ($serv) use ($pm) { file_put_contents(RES_FILE, "start\n", FILE_APPEND); $id = Swoole\Timer::tick(300, function () { file_put_contents(RES_FILE, "timer 1\n", FILE_APPEND); }); Swoole\Timer::after(900, function () use ($id, $serv, $pm) { file_put_contents(RES_FILE, "timer 2\n", FILE_APPEND); Swoole\Timer::clear($id); Swoole\Timer::tick(200, function ($id) use ($serv, $pm) { static $i = 0; file_put_contents(RES_FILE, "timer 3\n", FILE_APPEND); $i ++; if ($i > 4) { file_put_contents(RES_FILE, "end\n", FILE_APPEND); Swoole\Timer::clear($id); $pm->wakeup(); $serv->shutdown(); } }); }); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- start timer 1 timer 1 timer 1 timer 2 timer 3 timer 3 timer 3 timer 3 timer 3 end ================================================ FILE: tests/swoole_timer/master.phpt ================================================ --TEST-- swoole_timer: timer in master --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm) { $fp = fopen(RES_FILE, 'rw'); while (!feof($fp)) { $line = fgets($fp); if ($line) { echo $line; } } if (IS_MAC_OS) { $pm->kill(); } }; $pm->childFunc = function () use ($pm) { $server = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $server->on('start', function (Server $server) use ($pm) { file_put_contents(RES_FILE, "start\n", FILE_APPEND); $id = Timer::tick(30, function () { file_put_contents(RES_FILE, "timer 1\n", FILE_APPEND); }); Timer::after(90, function () use ($id, $server, $pm) { file_put_contents(RES_FILE, "timer 2\n", FILE_APPEND); Timer::clear($id); Timer::tick(10, function ($id) use ($server, $pm) { static $i = 0; file_put_contents(RES_FILE, "timer 3\n", FILE_APPEND); $i++; if ($i > 4) { file_put_contents(RES_FILE, "end\n", FILE_APPEND); Timer::clear($id); $pm->wakeup(); $server->shutdown(); } }); }); }); $server->on('receive', function () {}); $server->start(); }; $pm->childFirst(); $pm->run(); @unlink(RES_FILE); ?> --EXPECT-- start timer 1 timer 1 timer 1 timer 2 timer 3 timer 3 timer 3 timer 3 timer 3 end ================================================ FILE: tests/swoole_timer/master_base.phpt ================================================ --TEST-- swoole_timer: timer in master (base) --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function () use ($pm) { if (IS_MAC_OS) { $pm->kill(); } }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $server->on('start', function (Swoole\Server $server) use ($pm) { echo "start\n"; $id = Swoole\Timer::tick(100, function () { echo "timer 1\n"; }); Swoole\Timer::after(300, function () use ($id, $server, $pm) { echo "timer 2\n"; Swoole\Timer::clear($id); Swoole\Timer::tick(50, function ($id) use ($server, $pm) { static $i = 0; echo "timer 3\n"; $i++; if ($i > 4) { echo "end\n"; Swoole\Timer::clear($id); $pm->wakeup(); $server->shutdown(); } }); }); }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- start timer 1 timer 1 timer 1 timer 2 timer 3 timer 3 timer 3 timer 3 timer 3 end ================================================ FILE: tests/swoole_timer/memory.phpt ================================================ --TEST-- swoole_timer: memory leak test --SKIPIF-- --FILE-- count = 0; $stat->m0 = memory_get_usage(); $stat->data = []; Swoole\Timer::tick(1, function ( $id ) use ( &$stat ) { $obj = new stdClass(); $obj->name = random_bytes(8192); $stat->data = $obj->name; $stat->count++; if ($stat->count == 1) { $stat->m1 = memory_get_usage(); echo 'diff[0] ' . ($stat->m1 - $stat->m0) . "\n"; } elseif ($stat->count == 99) { $stat->m2 = memory_get_usage(); Assert::lessThan($stat->m2 - $stat->m1, 128); echo 'diff[1] ' . ($stat->m2 - $stat->m1) . "\n"; Swoole\Timer::clear($id); } }); \Swoole\Event::wait(); $stat->m3 = memory_get_usage(); $stat->data = null; echo 'diff[2] ' . ($stat->m3 - $stat->m0) . "\n"; ?> --EXPECTF-- diff[0] %d diff[1] %d diff[2] %d ================================================ FILE: tests/swoole_timer/next_round.phpt ================================================ --TEST-- swoole_timer: timer round control --SKIPIF-- --FILE-- --EXPECT-- ================================================ FILE: tests/swoole_timer/not_exist.phpt ================================================ --TEST-- swoole_timer: clear timer not exist --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_timer/reinit_1.phpt ================================================ --TEST-- swoole_timer: reinit [1] --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm) { $fp = fopen(RES_FILE, "rw"); while (!feof($fp)) { $line = fgets($fp); if ($line) { echo $line; } } if (IS_MAC_OS) { $pm->kill(); } }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server("0.0.0.0", $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $server->on('start', function (Swoole\Server $server) use ($pm) { file_put_contents(RES_FILE, "start\n", FILE_APPEND); static $i = 0; $id = Timer::tick(100, function ($timerId) use (&$i, $server, $pm) { file_put_contents(RES_FILE, "timer 1\n", FILE_APPEND); if (($i++) == 4) { Timer::clear($timerId); $server->shutdown(); $pm->wakeup(); } }); }); static $j = 0; Timer::tick(50, function ($timerId) use (&$j) { file_put_contents(RES_FILE, "timer 2\n", FILE_APPEND); if (($j++) == 5) { Timer::clear($timerId); } }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); unlink(RES_FILE); ?> --EXPECT-- start timer 2 timer 2 timer 1 timer 2 timer 2 timer 1 timer 2 timer 2 timer 1 timer 1 timer 1 ================================================ FILE: tests/swoole_timer/reinit_2.phpt ================================================ --TEST-- swoole_timer: reinit [2] --SKIPIF-- --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm) { $fp = fopen(RES_FILE, "rw"); while (!feof($fp)) { $line = fgets($fp); if ($line) { echo $line; } } if (IS_MAC_OS) { $pm->kill(); } }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Server("0.0.0.0", $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); $server->on('start', function (Swoole\Server $server) use ($pm) { file_put_contents(RES_FILE, "start\n", FILE_APPEND); static $i = 0; $id = Timer::tick(100, function ($timerId) use (&$i, $server, $pm) { file_put_contents(RES_FILE, "timer 1\n", FILE_APPEND); if (($i++) == 4) { Timer::clear($timerId); $server->shutdown(); $pm->wakeup(); } }); }); static $j = 0; Timer::tick(50, function ($timerId) use (&$j) { file_put_contents(RES_FILE, "timer 2\n", FILE_APPEND); if (($j++) == 5) { Timer::clear($timerId); } }); $server->on('receive', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); unlink(RES_FILE); ?> --EXPECT-- start timer 2 timer 2 timer 1 timer 2 timer 2 timer 1 timer 2 timer 2 timer 1 timer 1 timer 1 ================================================ FILE: tests/swoole_timer/stats.phpt ================================================ --TEST-- swoole_timer: list --SKIPIF-- --FILE-- --EXPECTF-- array(3) { ["initialized"]=> bool(false) ["num"]=> int(0) ["round"]=> int(0) } array(3) { ["initialized"]=> bool(true) ["num"]=> int(1000) ["round"]=> int(0) } array(3) { ["initialized"]=> bool(true) ["num"]=> int(1) ["round"]=> int(1) } ================================================ FILE: tests/swoole_timer/swoole_timer_list_alias.phpt ================================================ --TEST-- swoole_timer: function alias about swoole_timer_list --SKIPIF-- --FILE-- --EXPECT-- 3 ================================================ FILE: tests/swoole_timer/task_worker.phpt ================================================ --TEST-- swoole_timer: call after in Task-Worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->set(['open_eof_check' => true, 'package_eof' => "\r\n\r\n"]); $cli->connect('127.0.0.1', $pm->getFreePort(), 5) or exit('ERROR'); $cli->send('task-01') or exit('ERROR'); for ($i = 0; $i < 5; $i++) { echo trim($cli->recv()) . "\n"; } $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', ]); $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->task([$fd, 'timer']); }); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { [$fd] = $data; Timer::after(500, function () use ($serv, $fd) { $serv->send($fd, "500\r\n\r\n"); Timer::after(300, function () use ($serv, $fd) { $serv->send($fd, "800\r\n\r\n"); }); }); Timer::after(1000, function () use ($serv, $fd) { $serv->send($fd, "1000[1]\r\n\r\n"); }); Timer::after(1000, function () use ($serv, $fd) { $serv->send($fd, "1000[2]\r\n\r\n"); }); Timer::after(500, function () use ($serv, $fd) { $serv->send($fd, "500[2]\r\n\r\n"); }); Timer::after(2000, function () use ($serv, $fd) { $serv->send($fd, "2000\r\n\r\n"); }); }); $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 500 500[2] 800 1000[1] 1000[2] ================================================ FILE: tests/swoole_timer/task_worker_tick_1k.phpt ================================================ --TEST-- swoole_timer: call tick timer in Task-Worker --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); $cli->connect('127.0.0.1', $pm->getFreePort(), 5) or die("ERROR"); $cli->send("task-01") or die("ERROR"); for ($i = 0; $i < 3; $i++) { echo trim($cli->recv())."\n"; } $pm->kill(); }; $pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( "worker_num" => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { $serv->task([$fd, 'timer']); }); $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { static $i = 0; Swoole\Timer::tick(1, function () use(&$i, $serv) { $i++; if ($i % 500 == 499) { $serv->send(1, "timer-$i\r\n\r\n"); } }); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- timer-499 timer-999 timer-1499 ================================================ FILE: tests/swoole_timer/verify.phpt ================================================ --TEST-- swoole_timer: verify timer --SKIPIF-- --FILE-- --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/bug_1.phpt ================================================ --TEST-- swoole_websocket_server: bug 1 --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { global $count; $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); Assert::assert($cli->upgrade('/')); $data1 = get_safe_random(random_int(1024, 8192)); $cli->push($data1); $frame1 = $cli->recv(); Assert::eq($frame1->data, md5($data1)); $data2 = get_safe_random(random_int(65536, 65536 * 2)); $pkt2 = Swoole\WebSocket\Server::pack($data2, WEBSOCKET_OPCODE_TEXT); $cli->socket->sendAll(substr($pkt2, 0, 4)); usleep(1000); $cli->socket->sendAll(substr($pkt2, 4)); $frame2 = $cli->recv(); Assert::eq($frame2->data, md5($data2)); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort()); $serv->set([ 'open_http2_protocol' => true, // 'log_file' => '/dev/null', ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $server->push($frame->fd, md5($frame->data)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/close_frame_flag.phpt ================================================ --TEST-- swoole_websocket_server: websocket server active close with close frame flag false --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { $cli = new SwooleTest\Samtleben\WebsocketClient; $connected = $cli->connect('127.0.0.1', $pm->getFreePort(), '/'); Assert::assert($connected); $cli->sendRecv('shutdown'); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null', 'open_websocket_close_frame' => false ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function ($serv, $frame) { if ($frame->opcode == WEBSOCKET_OPCODE_CLOSE) { echo "{$frame->code}\n"; echo "{$frame->reason}\n"; Assert::true(false, 'never here'); // Should never reach here } else { if ($frame->data == 'shutdown') { echo "{$frame->data}"; $serv->disconnect($frame->fd, 4000, 'shutdown received'); } } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- shutdown ================================================ FILE: tests/swoole_websocket_server/close_frame_full.phpt ================================================ --TEST-- swoole_websocket_server: websocket server send and recv close frame full test --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); for ($n = MAX_REQUESTS; $n--;) { Assert::true($cli->upgrade('/')); $code = mt_rand(0, 5000); $reason = md5($code); $close_frame = new Swoole\WebSocket\CloseFrame; $close_frame->code = $code; $close_frame->reason = $reason; Assert::true($cli->push($close_frame)); // recv the last close frame $frame = $cli->recv(); Assert::isInstanceOf($frame, Swoole\WebSocket\CloseFrame::class); Assert::same($frame->opcode, WEBSOCKET_OPCODE_CLOSE); Assert::same(md5($frame->code), $frame->reason); // connection closed Assert::true($cli->disconnect($frame->code, $frame->reason)); Assert::false($cli->recv()); Assert::false($cli->connected); Assert::same($cli->errCode, 5001); // connection close normally } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { Assert::isInstanceOf($frame, Swoole\WebSocket\CloseFrame::class); Assert::same($frame->opcode, WEBSOCKET_OPCODE_CLOSE); if (mt_rand(0, 1)) { $serv->push($frame->fd, $frame); } else { $serv->disconnect($frame->fd, $frame->code, $frame->reason); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/compression.phpt ================================================ --TEST-- swoole_websocket_server: compression --SKIPIF-- --FILE-- initRandomData(MAX_REQUESTS); $pm->parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'websocket_compression' => true, ]); $ret = $cli->upgrade('/'); if (!Assert::assert($ret)) { return; } for ($n = MAX_REQUESTS; $n--;) { $data = $pm->getRandomData(); $cli->push( $data, SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN | SWOOLE_WEBSOCKET_FLAG_COMPRESS ); $frame = $cli->recv(); if (!Assert::same($frame->data, $data)) { return; } if (!Assert::eq($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS, defined('SWOOLE_HAVE_ZLIB'))) { return; } } echo "DONE\n"; }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'log_file' => '/dev/null', 'websocket_compression' => true, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm) { if (!Assert::same($frame->data, $pm->getRandomData())) { $server->close($frame->fd); return; } if (!Assert::eq($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS, defined('SWOOLE_HAVE_ZLIB'))) { $server->close($frame->fd); return; } $server->push($frame->fd, $frame); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/disconnect.phpt ================================================ --TEST-- swoole_websocket_server: websocket server disconnect --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { $cli = new SwooleTest\Samtleben\WebsocketClient; $connected = $cli->connect('127.0.0.1', $pm->getFreePort(), '/'); Assert::assert($connected); $response = $cli->sendRecv("shutdown"); echo unpack('n', substr($response, 0, 2))[1] . "\n"; echo substr($response, 2) . "\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { if ($frame->data == 'shutdown') { $serv->disconnect($frame->fd, 4000, 'shutdown received'); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 4000 shutdown received ================================================ FILE: tests/swoole_websocket_server/disconnect_with_code.phpt ================================================ --TEST-- swoole_websocket_server: websocket server disconnect with one param code --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { $cli = new SwooleTest\Samtleben\WebsocketClient; $connected = $cli->connect('127.0.0.1', $pm->getFreePort(), '/'); Assert::assert($connected); $response = $cli->sendRecv("shutdown"); $byteArray = unpack('n', $response); echo $byteArray[1] . "\n"; echo substr($response, 2); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { if ($frame->data == 'shutdown') { $serv->disconnect($frame->fd, 4001); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- 4001 ================================================ FILE: tests/swoole_websocket_server/dynamic_property.phpt ================================================ --TEST-- swoole_websocket_server: Creation of dynamic property is deprecated. --SKIPIF-- --FILE-- initFreePorts(10); $websocket = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort()); $port1 = $websocket->listen("127.0.0.1", $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2 = $websocket->listen("127.0.0.1", $pm->getFreePort(2), SWOOLE_SOCK_TCP); $port3 = $websocket->listen("127.0.0.1", $pm->getFreePort(3), SWOOLE_SOCK_TCP); $port1->on('handshake', function($request, $response) {}); $port1->on('beforehandshakeresponse', function($request, $response) {}); var_dump($port1->getCallback('handshake') != null); var_dump($port1->getCallback('BeforeHandshakeResponse') != null); $port2->on('HANDSHAKE', function($request, $response) {}); $port2->on('BEFOREHANDSHAKERESPONSE', function($request, $response) {}); var_dump($port1->getCallback('HANDSHAKE') != null); var_dump($port1->getCallback('BEFOREHANDSHAKERESPONSE') != null); $port3->on('handShake', function($request, $response) {}); $port3->on('beforehandShakeResponse', function($request, $response) {}); var_dump($port1->getCallback('handShake') != null); var_dump($port1->getCallback('beforehandShakeResponse') != null); echo "DONE\n"; ?> --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) DONE ================================================ FILE: tests/swoole_websocket_server/empty_message.phpt ================================================ --TEST-- swoole_websocket_server: empty message --SKIPIF-- --FILE-- useConstantPorts = true; $pm->initFreePorts(); $pm->parentFunc = function (int $pid) use ($pm) { go(function() use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $connected = $cli->upgrade('/?test=a&b=hello'); Assert::assert($connected); $cli->push(''); $response = $cli->recv(); Assert::assert($response); $json = json_decode($response->data); Assert::isEmpty($json->data); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function (Swoole\WebSocket\Server $serv, Swoole\Http\Request $req) { }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { $serv->push($frame->fd, json_encode($frame)); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/exists_and_isEstablished.phpt ================================================ --TEST-- swoole_websocket_server: exists & isEstablished --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { $ready = new Chan; for ($c = 0; $c < MAX_CONCURRENCY; $c++) { go(function () use ($pm, $ready, $c) { /* @var $connections Co\Http\Client[][] */ global $connections; $connections[$c] = [ 'cli' => new Co\Http\Client('127.0.0.1', $pm->getFreePort()), 'type' => array_random(['null', 'http', 'websocket']), 'fd' => -1 ]; if ($connections[$c]['type'] !== 'null') { if (!Assert::assert($connections[$c]['cli']->get('/'))) { exit; } $connections[$c]['fd'] = (int)$connections[$c]['cli']->body; if ($connections[$c]['type'] === 'websocket') { if (!Assert::assert($connections[$c]['cli']->upgrade('/'))) { exit; } } } $ready->push(true); }); } go(function () use ($pm, $ready) { global $connections; for ($c = 0; $c < MAX_CONCURRENCY; $c++) { Assert::true($ready->pop()); } $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); if (Assert::assert($cli->upgrade('/'))) { for ($c = 0; $c < MAX_CONCURRENCY; $c++) { if (!Assert::assert($cli->push($connections[$c]['fd']))) { exit; } $frame = $cli->recv(); if (!Assert::assert($frame instanceof Swoole\WebSocket\Frame)) { exit; } // var_dump($connections[$c], $frame->data); if (!Assert::assert($frame->data === ($connections[$c]['type'] ?? 'null'))) { exit; } } } $connections = null; echo "DONE\n"; }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Swoole\WebSocket\Server ('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set(['log_file' => '/dev/null']); $server->on('start', function () use ($pm) { switch_process(); $pm->wakeup(); }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->end($request->fd); }); $server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $fd = (int)$frame->data; $server->push( $frame->fd, $server->isEstablished($fd) ? 'websocket' : ($server->exists($fd) ? 'http' : 'null') ); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/fin.phpt ================================================ --TEST-- swoole_websocket_server: websocket server recv and merge fin packages --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm, &$count) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); $rand_list = []; $times = MAX_REQUESTS; for ($n = $times; $n--;) { $rand = get_safe_random(mt_rand(1, 1280)); $rand_list[] = $rand; $opcode = $n === $times - 1 ? WEBSOCKET_OPCODE_TEXT : WEBSOCKET_OPCODE_CONTINUATION; $finish = $n === 0; if (mt_rand(0, 1)) { $frame = new Swoole\WebSocket\Frame; $frame->opcode = $opcode; $frame->data = $rand; $frame->finish = $finish; $ret = $cli->push($frame); } else { $ret = $cli->push($rand, $opcode, $finish); } Assert::assert($ret); } $frame = $cli->recv(); if (Assert::assert($frame->data === implode('', $rand_list))) { $count++; } }); } Swoole\Event::wait(); Assert::same($count, MAX_CONCURRENCY); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { if (mt_rand(0, 1)) { $serv->push($frame->fd, $frame); } else { $serv->push($frame->fd, $frame->data, $frame->opcode, true); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/fin2.phpt ================================================ --TEST-- swoole_websocket_server: fin [2] --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); $rand_list = []; $rand = get_safe_random(mt_rand(1, 1280)); $rand_list[] = $rand; $times = rand(8, 32); for ($n = $times; $n--;) { $opcode = $n === $times - 1 ? WEBSOCKET_OPCODE_TEXT : WEBSOCKET_OPCODE_CONTINUATION; $finish = $n === 0; if (mt_rand(0, 1)) { $frame = new Swoole\WebSocket\Frame; $frame->opcode = $opcode; $frame->data = $rand; $frame->finish = $finish; $ret = $cli->push($frame); } else { $ret = $cli->push($rand, $opcode, $finish); } } Assert::assert($ret); $frame = $cli->recv(); Assert::assert($frame instanceof Swoole\WebSocket\Frame); $data = json_decode($frame->data); Assert::assert($data->finish); Assert::assert($data->data, implode('', $rand_list)); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { if (mt_rand(0, 1)) { $frame->data = json_encode($frame); $serv->push($frame->fd, $frame); } else { $serv->push($frame->fd, json_encode($frame), $frame->opcode, true); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/get_large_requests.phpt ================================================ --TEST-- swoole_websocket_server: websocket with large data concurrency --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($c = MAX_CONCURRENCY_MID; $c--;) { go(function () use ($pm) { global $count; $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $ret = $cli->upgrade('/'); Assert::assert($ret); $len = mt_rand(35000, 40000); $data = get_safe_random($len); for ($n = MAX_REQUESTS; $n--;) { $cli->push($data); $ret = $cli->recv(); if (Assert::eq($ret->data, $data)) { $count++; } } if (co::stats()['coroutine_num'] === 1) { Assert::same($count, (MAX_CONCURRENCY_MID * MAX_REQUESTS)); $cli->push('max'); Assert::assert((int)$cli->recv()->data > 1); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Server $server, Frame $frame) { co::sleep(0.001); if ($frame->data === 'max') { $server->push($frame->fd, co::stats()['coroutine_peak_num']); } else { Assert::assert(strlen($frame->data) >= 35000); $server->push($frame->fd, $frame->data); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/get_small_requests.phpt ================================================ --TEST-- swoole_websocket_server: websocket with small data concurrency --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm) { global $count; $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); $len = mt_rand(1, 100); $data = get_safe_random($len); for ($n = MAX_REQUESTS; $n--;) { $cli->push($data); $ret = $cli->recv(); if (Assert::assert($ret->data == $len)) { $count++; } } if (co::stats()['coroutine_num'] === 1) { Assert::same($count, (MAX_CONCURRENCY * MAX_REQUESTS)); $cli->push('max'); Assert::assert((int)$cli->recv()->data > 1); } }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { if (mt_rand(0, 1)) { co::sleep(0.001); // 50% block } if ($frame->data === 'max') { $server->push($frame->fd, co::stats()['coroutine_peak_num']); } else { $server->push($frame->fd, strlen($frame->data)); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/greeter.phpt ================================================ --TEST-- swoole_websocket_server: websocket greeter and reply twice --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { for ($c = MAX_CONCURRENCY; $c--;) { go(function () use ($pm, &$count) { global $count; $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); $data = sha1(get_safe_random(mt_rand(1, 1024))); for ($n = MAX_REQUESTS; $n--;) { $cli->push($data); $ret = $cli->recv(); Assert::same($ret->data, "Hello {$data}!"); $ret = $cli->recv(); Assert::same($ret->data, "How are you, {$data}?"); $count++; } }); } Swoole\Event::wait(); Assert::same($count, (MAX_CONCURRENCY * MAX_REQUESTS)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { $server->push($frame->fd, "Hello {$frame->data}!"); $server->push($frame->fd, "How are you, {$frame->data}?"); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/header_token.phpt ================================================ --TEST-- swoole_websocket_server: header token --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { Co\run(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); Assert::assert($connected); $cli->send("GET /chat HTTP/1.1\r\n" . "Host: localhost\r\n" . "Upgrade: websocket, abc\r\n" . "Connection: Upgrade, abc\r\n" . "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . "Sec-WebSocket-Protocol: chat\r\n" . "Sec-WebSocket-Version: 13\r\n\r\n"); $res = $cli->recv(); Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); Assert::assert($connected); $cli->send("GET /chat HTTP/1.1\r\n" . "Host: localhost\r\n" . "Upgrade: abc, websocket\r\n" . "Connection: abc, Upgrade\r\n" . "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . "Sec-WebSocket-Protocol: chat\r\n" . "Sec-WebSocket-Version: 13\r\n\r\n"); $res = $cli->recv(); Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); Assert::assert($connected); $cli->send("GET /chat HTTP/1.1\r\n" . "Host: localhost\r\n" . "Upgrade: abc, websocket, def\r\n" . "Connection: abc, Upgrade, def\r\n" . "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . "Sec-WebSocket-Protocol: chat\r\n" . "Sec-WebSocket-Version: 13\r\n\r\n"); $res = $cli->recv(); Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'log_file' => LOG_FILE, ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(LOG_FILE); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/listener.phpt ================================================ --TEST-- swoole_websocket_server: addlistener --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', 9506); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); foreach (range(1, 100) as $i) { $ret = $cli->push("hello"); Assert::assert($ret); $frame = $cli->recv(); Assert::same($frame->data, "Swoole: hello"); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->listen('127.0.0.1', 9506, SWOOLE_SOCK_TCP); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function ($swoole_server, $req) { }); $serv->on('message', function ($swoole_server, $frame) { $swoole_server->push($frame->fd, "Swoole: {$frame->data}"); }); $serv->on('close', function ($swoole_server, $fd) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/malformed_data.phpt ================================================ --TEST-- swoole_websocket_server: malformed data --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count) { $bytes = [chr(25)]; swoole_loop_n(255, function () use (&$bytes) { $bytes[] = chr(255); }); Co\run(function () use ($bytes, $pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); Assert::assert($connected); $cli->send("GET /chat HTTP/1.1\r\n" . "Host: localhost\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . "Sec-WebSocket-Protocol: chat\r\n" . "Sec-WebSocket-Version: 13\r\n\r\n"); $r1 = $cli->recv(); Assert::contains($r1, 'HTTP/1.1 101 Switching Protocols'); $cli->send(implode('', $bytes)); $r2 = $cli->recv(); Assert::eq($r2, false); Assert::eq($cli->errCode, SOCKET_ECONNRESET); }); $pm->kill(); $log = file_get_contents(LOG_FILE); Assert::contains($log, 'malformed data'); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ 'log_file' => LOG_FILE, ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); unlink(LOG_FILE); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/memory.phpt ================================================ --TEST-- swoole_websocket_server: memory trace --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { phpt_echo("start to benchmark " . MAX_REQUESTS_MID . " times...\n"); $concurrency = PRESSURE_LEVEL === PRESSURE_NORMAL ? MAX_CONCURRENCY * 4 : MAX_CONCURRENCY; Co::set(['max_coroutine' => $concurrency + 1]); Co\run(function () use ($pm, $concurrency) { phpt_echo("Concurrency: {$concurrency}\n"); for ($c = $concurrency; $c--;) { go(function () use ($pm, $c) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); while (!@$cli->upgrade('/')) { Co::sleep(0.1); } while ($cli->recv(-1)) { continue; } }); } }); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Websocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); $server->on('workerStart', function (Swoole\Websocket\Server $server, int $worker_id) use ($pm) { global $mem_records; phpt_echo("init\n"); co::sleep(1); $pm->wakeup(); phpt_echo("start\n"); while (true) { $master_top = top($server->master_pid); $worker_top = top($server->worker_pid); if (empty($master_top) || empty($worker_top)) { phpt_echo("shutdown\n"); foreach ($server->connections as $fd) { @$server->close($fd); } $server->shutdown(); return; } $mem_records[] = [ 'master_virtual' => $master_top['VIRT'], 'master_real' => $master_top['RES'], 'worker_virtual' => $worker_top['VIRT'], 'worker_real' => $worker_top['RES'] ]; phpt_var_dump(end($mem_records)); if (($records_count = count($mem_records)) === MAX_REQUESTS_MID) { phpt_echo("=== master virtual ===\n"); phpt_var_dump($master_virtual = array_column($mem_records, 'master_virtual')); phpt_echo("=== master real ===\n"); phpt_var_dump($master_real = array_column($mem_records, 'master_real')); phpt_echo("=== worker virtual ===\n"); phpt_var_dump($worker_virtual = array_column($mem_records, 'worker_virtual')); phpt_echo("=== worker real ===\n"); phpt_var_dump($worker_real = array_column($mem_records, 'worker_real')); for ($i = $records_count / 2; $i < $records_count; $i++) { approximate($master_virtual[$i], $master_virtual[$records_count / 2]); approximate($worker_virtual[$i], $worker_virtual[$records_count / 2]); approximate($worker_real[$i], $worker_real[$records_count / 2]); } $server->shutdown(); return; } $fd = 0; $success = 0; foreach ($server->connections as $fd) { if (@$server->push($fd, str_repeat('S', FRAME_DATA_SIZE))) { $success++; } } phpt_echo("#{$records_count}: push " . (FRAME_DATA_SIZE / 1024) . "k data to {$fd} client success {$success}!\n"); co::sleep(REQUESTS_WAIT_TIME); switch_process(); } }); $server->on('message', function (Swoole\Websocket\Server $server, Swoole\WebSocket\Frame $frame) { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/message_size.phpt ================================================ --TEST-- swoole_websocket_server: message size --SKIPIF-- --FILE-- push($data); $ret = $cli->recv(); if (Assert::eq($ret->data, $data)) { $count++; } } } $pm->parentFunc = function (int $pid) use ($pm) { for ($c = N; $c--;) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $ret = $cli->upgrade('/'); Assert::assert($ret); test($cli, 64, 200); test($cli, 25600, 70000); test($cli, 70000, 400000); }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Server $server, Frame $frame) { $server->push($frame->fd, $frame->data); }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/onDisconnct.phpt ================================================ --TEST-- swoole_websocket_server: onDisconnect --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { run(function () use ($pm) { $data = httpGetBody('http://127.0.0.1:' . $pm->getFreePort() . '/'); Assert::contains($data, 'HTTP 400 Bad Request'); $client = new Client('127.0.0.1', $pm->getFreePort()); Assert::assert($client->upgrade('/websocket')); Assert::eq($client->getStatusCode(), 101); $client->push('hello world'); $client->close(); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('Message', function (Server $serv, Frame $frame) { if ($frame->data == 'shutdown') { $serv->disconnect($frame->fd, 4000, 'shutdown received'); } }); $serv->on('connect', function ($s, $id) use ($pm) { puts("connect " . $id); }); $serv->on('disconnect', function ($s, $id) use ($pm) { puts("disconnect " . $id); }); $serv->on('open', function ($s, $req) use ($pm) { puts("open " . $req->fd); }); $serv->on('close', function ($s, $id) use ($pm) { puts("close " . $id); }); $serv->start(); }; $pm->childFirst(); $pm->run(); puts('done!'); ?> --EXPECT-- connect 1 disconnect 1 connect 2 open 2 close 2 done! ================================================ FILE: tests/swoole_websocket_server/pack.phpt ================================================ --TEST-- swoole_websocket_server: websocket frame pack/unpack --SKIPIF-- --FILE-- code = $code; $frame->reason = $data; } else { $frame = new Frame; $frame->data = $data; } $frame->opcode = $opcode; $frame->finish = $finish; if (!mt_rand(0, 4)) { unset($frame->data); unset($frame->reason); $data = ''; } if (mt_rand(0, 1)) { $packed = (string)$frame; } else { $packed = Frame::pack($frame); } } else { $packed = Frame::pack($data, $opcode, $finish); } // unpack $unpacked = Frame::unpack($packed); // verify if ($opcode === WEBSOCKET_OPCODE_CLOSE) { Assert::same($unpacked->code, $code); Assert::same($unpacked->reason, $data); Assert::true($unpacked->finish == SWOOLE_WEBSOCKET_FLAG_FIN); } else { Assert::same($unpacked->data, $data); Assert::same($unpacked->opcode, $opcode); if ($opcode === WEBSOCKET_OPCODE_PING || $opcode === WEBSOCKET_OPCODE_PONG) { Assert::true($unpacked->finish == SWOOLE_WEBSOCKET_FLAG_FIN); } else { Assert::same($unpacked->finish, $finish); } } } ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/ping.phpt ================================================ --TEST-- swoole_websocket_server: test ping function --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $client->set(['open_websocket_ping_frame' => true]); Assert::true($client->upgrade('/')); $client->push('123456'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); Assert::true($frame->data == 'Hello World'); $frame = $client->recv(); Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); Assert::true($frame->data == ''); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) { $server->ping($frame->fd, 'Hello World'); $server->ping($frame->fd); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/pingloop.phpt ================================================ --TEST-- swoole_websocket_server: ping loop --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_MID; $i--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/'); Assert::assert($ret); $loop = 0; while ($response = $cli->recv(-1)) { switch ($response->opcode) { case WEBSOCKET_OPCODE_PING: global $count; $count++; $loop++; if (mt_rand(0, 1)) { $pong = new Swoole\WebSocket\Frame; $pong->opcode = WEBSOCKET_OPCODE_PONG; $ret = $cli->push($pong); } else { $ret = $cli->push('', WEBSOCKET_OPCODE_PONG); } Assert::assert($ret); break; case WEBSOCKET_OPCODE_CLOSE: break 2; default: Assert::assert(0, 'never hear.'); } } Assert::same($loop, PING_LOOP); }); } Swoole\Event::wait(); global $count; Assert::same($count, PING_LOOP * MAX_CONCURRENCY_MID); $pm->kill(); echo "DONE"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function (Swoole\WebSocket\Server $server) use ($pm) { $timer_id = Swoole\Timer::tick(PING_INTERVAL, function () use ($server) { foreach ($server->connections as $fd) { if (mt_rand(0, 1)) { $ping = new Swoole\WebSocket\Frame; $ping->opcode = WEBSOCKET_OPCODE_PING; $server->push($fd, $ping); } else { $server->push($fd, '', WEBSOCKET_OPCODE_PING); } } }); Swoole\Timer::after(PING_LOOP * PING_INTERVAL, function () use ($pm, $server, $timer_id) { Swoole\Timer::clear($timer_id); foreach ($server->connections as $fd) { $server->push($fd, new Swoole\WebSocket\CloseFrame); } }); $pm->wakeup(); }); $serv->on('open', function ($server, $req) { }); $serv->on('message', function ($server, Swoole\WebSocket\Frame $frame) { Assert::same($frame->opcode, WEBSOCKET_OPCODE_PONG); }); $serv->on('close', function ($server, $fd) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/pingloop_open_ping_pong_frame.phpt ================================================ --TEST-- swoole_websocket_server: ping loop --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_MID; $i--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/'); Assert::assert($ret); $loop = 0; while ($response = $cli->recv(-1)) { switch ($response->opcode) { case WEBSOCKET_OPCODE_PING: Assert::same($response->data, 'ping'); global $count; $count++; $loop++; if (mt_rand(0, 1)) { $pong = new Swoole\WebSocket\Frame; $pong->data = 'pong'; $pong->opcode = WEBSOCKET_OPCODE_PONG; $ret = $cli->push($pong); } else { $ret = $cli->push('pong', WEBSOCKET_OPCODE_PONG); } Assert::assert($ret); break; case WEBSOCKET_OPCODE_CLOSE: break 2; default: Assert::assert(0, 'never hear.'); } } Assert::same($loop, PING_LOOP); }); } Swoole\Event::wait(); global $count; Assert::same($count, PING_LOOP * MAX_CONCURRENCY_MID); $pm->kill(); echo "DONE"; }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => '/dev/null', 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, ]); $serv->on('workerStart', function (Swoole\WebSocket\Server $server) use ($pm) { $timer_id = Swoole\Timer::tick(PING_INTERVAL, function () use ($server) { foreach ($server->connections as $fd) { if (mt_rand(0, 1)) { $ping = new Swoole\WebSocket\Frame; $ping->data = 'ping'; $ping->opcode = WEBSOCKET_OPCODE_PING; $server->push($fd, $ping); } else { $server->push($fd, 'ping', WEBSOCKET_OPCODE_PING); } } }); Swoole\Timer::after(PING_LOOP * PING_INTERVAL, function () use ($pm, $server, $timer_id) { Swoole\Timer::clear($timer_id); Swoole\Coroutine::sleep(0.1); // wait pong foreach ($server->connections as $fd) { $server->push($fd, new Swoole\WebSocket\CloseFrame); } }); $pm->wakeup(); }); $serv->on('open', function ($server, $req) { }); $atomic = new Swoole\Atomic; $serv->on('message', function ($server, Swoole\WebSocket\Frame $frame) use ($atomic) { if ($frame->opcode === WEBSOCKET_OPCODE_PONG) { Assert::same($frame->data, 'pong'); $atomic->add(); } }); $serv->on('close', function ($server, $fd) { }); $serv->start(); Assert::same($atomic->get(), PING_LOOP * MAX_CONCURRENCY_MID); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/pingpong.phpt ================================================ --TEST-- swoole_websocket_server: websocket ping pong (auto) --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/'); Assert::assert($ret); for ($i = 100; $i--;) { $ping = new Swoole\WebSocket\Frame; $ping->opcode = WEBSOCKET_OPCODE_PING; $ret = $cli->push($ping); Assert::assert($ret); $pong = $cli->recv(); Assert::same($pong->opcode, WEBSOCKET_OPCODE_PONG); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function ($swoole_server, $req) { }); $serv->on('message', function ($swoole_server, $frame) { }); $serv->on('close', function ($swoole_server, $fd) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/pingpong_open_ping_pong_frame.phpt ================================================ --TEST-- swoole_websocket_server: websocket ping pong (auto) --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, 'open_websocket_close_frame' => true, ]); $ret = $cli->upgrade('/'); Assert::assert($ret); for ($i = 100; $i--;) { $ping = new Swoole\WebSocket\Frame; $ping->opcode = WEBSOCKET_OPCODE_PING; $ping->data = 'ping'; $ret = $cli->push($ping); Assert::assert($ret); $pong = $cli->recv(); Assert::same($pong->opcode, WEBSOCKET_OPCODE_PONG); Assert::same($pong->data, 'pong'); } $pm->kill(); }); Swoole\Event::wait(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null', 'open_websocket_ping_frame' => true, 'open_websocket_pong_frame' => true, ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function ($swoole_server, $req) { }); $atomic = new Swoole\Atomic; $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) use ($atomic) { if ($frame->opcode === WEBSOCKET_OPCODE_PING) { Assert::same($frame->data, 'ping'); $atomic->add(); $pongFrame = new Swoole\WebSocket\Frame; $pongFrame->opcode = WEBSOCKET_OPCODE_PONG; $pongFrame->data = 'pong'; $server->push($frame->fd, $pongFrame); } }); $serv->on('close', function ($swoole_server, $fd) { }); $serv->start(); Assert::same($atomic->get(), 100); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/query.phpt ================================================ --TEST-- swoole_websocket_server: query --SKIPIF-- --FILE-- useConstantPorts = true; $pm->initFreePorts(); $pm->parentFunc = function (int $pid) use ($pm) { go(function() use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $connected = $cli->upgrade('/?test=a&b=hello'); Assert::assert($connected); $response = $cli->recv(); Assert::assert($response); $json = json_decode($response->data, true); Assert::assert(is_array($json)); Assert::same($json['test'], 'a'); Assert::same($json['b'], 'hello'); }); Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function (Swoole\WebSocket\Server $serv, Swoole\Http\Request $req) { $serv->push($req->fd, json_encode($req->get)); }); $serv->on('Message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/recv_decode.phpt ================================================ --TEST-- swoole_websocket_server: websocket server full test --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data_list) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm, $data_list) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); foreach ($data_list as $data) { if (mt_rand(0, 1)) { $frame = new Swoole\WebSocket\Frame; $frame->opcode = (int)explode('|', $data, 3)[1]; $frame->data = $data; $ret = $cli->push($frame); } else { $ret = $cli->push($data, (int)explode('|', $data, 3)[1]); } if (!Assert::assert($ret)) { var_dump(swoole_strerror(swoole_last_error())); } else { $ret = $cli->recv(); unset($data_list[$ret->data]); } } Assert::assert(empty($data_list)); }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null' ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $recv_frame) { global $data_list; list($id, $opcode) = explode('|', $recv_frame->data, 3); if (!Assert::assert($recv_frame->finish)) { return; } if (!Assert::assert($recv_frame->opcode === (int)$opcode)) { return; } if (!Assert::assert($recv_frame->data === $data_list[$id])) { var_dump($recv_frame->data); var_dump($data_list[$id]); return; } if (mt_rand(0, 1)) { $send_frame = new Swoole\WebSocket\Frame; $send_frame->data = $id; $serv->push($recv_frame->fd, $send_frame); } else { $serv->push($recv_frame->fd, $id); } }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/send_close_frame.phpt ================================================ --TEST-- swoole_websocket_server: send close frame will not close connection --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $client = new Client('127.0.0.1', $pm->getFreePort()); $ret = $client->upgrade('/'); $client->push('aaa'); $client->push('lalalala'); }); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, 'package_max_length' => 100 * 1024 * 1024, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('message', function (Server $server, Frame $frame) use ($pm) { if ($frame->data == 'aaa') { $close = new CloseFrame(); $close->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; $close->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; $close->reason = 'hahahhah'; Assert::true($server->push($frame->fd, $close)); } else { var_dump($frame->data); } }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- string(8) "lalalala" ================================================ FILE: tests/swoole_websocket_server/send_encode.phpt ================================================ --TEST-- swoole_websocket_server: websocket server full test --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, &$count, $data_list) { for ($c = $count; $c--;) { go(function () use ($pm, &$count, $data_list) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/'); Assert::assert($ret); while (($frame = $cli->recv())) { /**@var $frame Swoole\WebSocket\Frame */ list($id, $opcode) = explode('|', $frame->data, 3); Assert::assert($frame->finish); Assert::same($frame->opcode, (int)$opcode); Assert::same($frame->data, $data_list[$id]); if (Assert::true(isset($data_list[$id]))) { unset($data_list[$id]); } if (empty($data_list)) { break; } } if (Assert::assert(empty($data_list))) { $count--; } }); } Swoole\Event::wait(); Assert::same($count, 0); echo "complete\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ // 'worker_num' => 1, 'log_file' => '/dev/null', 'send_yield' => true, 'send_timeout' => 10 ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function (Swoole\WebSocket\Server $serv, Swoole\Http\Request $req) { global $data_list; foreach ($data_list as $data) { $opcode = (int)explode('|', $data, 3)[1]; if (mt_rand(0, 1)) { $frame = new Swoole\WebSocket\Frame; $frame->opcode = $opcode; $frame->data = $data; $ret = $serv->push($req->fd, $frame); } else { $ret = $serv->push($req->fd, $data, $opcode); } if (!Assert::assert($ret)) { var_dump($serv->getLastError()); } } }); $serv->on('message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- complete ================================================ FILE: tests/swoole_websocket_server/send_encode_async.phpt ================================================ --TEST-- swoole_websocket_server: websocket server full test --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm, $data_list) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm) { $cli = new Co\http\client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $ret = $cli->upgrade('/'); if ($ret == false) { die("error=" . $cli->errCode); } global $data_list; $cli_data_list = $data_list; while (true) { $frame = $cli->recv(); list($id, $opcode) = explode('|', $frame->data, 3); Assert::assert($frame->finish); Assert::same($frame->opcode, (int)$opcode); Assert::same($frame->data, $cli_data_list[$id]); unset($cli_data_list[$id]); if (empty($cli_data_list)) { break; } } $cli->close(); }); } Swoole\Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $serv->set([ 'worker_num' => 1, 'log_file' => TEST_LOG_FILE, 'send_yield' => true, 'send_timeout' => 2, ]); $serv->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('open', function (Swoole\WebSocket\Server $serv, Swoole\Http\Request $req) { global $data_list; foreach ($data_list as $data) { $opcode = (int)explode('|', $data, 3)[1]; if (mt_rand(0, 1)) { $frame = new Swoole\WebSocket\Frame; $frame->opcode = $opcode; $frame->data = $data; $ret = $serv->push($req->fd, $frame); } else { $ret = $serv->push($req->fd, $data, $opcode); } if (!Assert::assert($ret)) { var_dump($serv->getLastError()); } } }); $serv->on('message', function (Swoole\WebSocket\Server $serv, Swoole\WebSocket\Frame $frame) { }); $serv->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/swoole_websocket_server/set_cookie_on_before_handshake_response.phpt ================================================ --TEST-- swoole_websocket_server: websocket server set cookie on beforeHandshakeResponse (#3270) --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); if (Assert::true($cli->upgrade('/'))) { Assert::same($cli->set_cookie_headers, [ 'abc=def' ]); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('beforeHandShakeResponse', function (Server $server, Request $request, Response $response) { $response->cookie('abc', 'def'); }); $server->on('message', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/set_cookie_on_handshake.phpt ================================================ --TEST-- swoole_websocket_server: websocket server set cookie on handshake (#3270) --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); if (Assert::true($cli->upgrade('/'))) { Assert::same($cli->headers['x-asdf'], 'asdf'); Assert::same($cli->set_cookie_headers, [ 'foo=bar', 'abc=def' ]); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); $server->on('handshake', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) { $secWebSocketKey = $request->header['sec-websocket-key']; $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) { $response->end(); return false; } $key = base64_encode(sha1( $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true )); $headers = [ 'Upgrade' => 'websocket', 'Connection' => 'Upgrade', 'Sec-WebSocket-Accept' => $key, 'Sec-WebSocket-Version' => '13', 'Set-Cookie' => 'foo=bar', 'X-asdf' => 'asdf' ]; // WebSocket connection to 'ws://127.0.0.1:9502/' // failed: Error during WebSocket handshake: // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket if (isset($request->header['sec-websocket-protocol'])) { $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol']; } foreach ($headers as $key => $val) { $response->header($key, $val); } $response->cookie('abc', 'def'); $response->status(101); $response->end(); return true; }); $server->on('message', function () { }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/swoole_websocket_server/websocket_compress_on_handshake.phpt ================================================ --TEST-- swoole_websocket_server: websocket compression with handshake --SKIPIF-- --FILE-- initFreePorts(); $pm->parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->setHeaders(['Sec-WebSocket-Extensions' => 'permessage-deflate; client_max_window_bits']); if (Assert::true($cli->upgrade('/'))) { Assert::contains($cli->headers['sec-websocket-extensions'], 'permessage-deflate'); } }); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set(['websocket_compression' => true]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); // test with Chrome $server->on('request', function (Request $request, Response $response) use ($pm) { $port = $pm->getFreePort(); $response->end(<< var wsServer = 'ws://127.0.0.1:{$port}'; var websocket = new WebSocket(wsServer); websocket.onopen = function (evt) { console.log("Connected to WebSocket server."); }; websocket.onclose = function (evt) { console.log("Disconnected"); }; websocket.onmessage = function (evt) { console.log('Retrieved data from server: ' + evt.data); }; websocket.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; HTML); }); $server->on('handshake', function (Request $request, Response $response) { $secWebSocketKey = $request->header['sec-websocket-key']; $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) { $response->end(); return false; } $key = base64_encode(sha1( $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true )); $headers = [ 'Upgrade' => 'websocket', 'Connection' => 'Upgrade', 'Sec-WebSocket-Accept' => $key, 'Sec-WebSocket-Version' => '13', ]; if (isset($request->header['sec-websocket-protocol'])) { $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol']; } foreach ($headers as $key => $val) { $response->header($key, $val); } $response->status(101); $response->end(); return true; }); $server->on('message', function ($serv, $frame) { $serv->push($frame->fd, "hello world"); }); $server->start(); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- DONE ================================================ FILE: tests/template ================================================ --TEST-- {{test_name}}: {{test_intro}} --SKIPIF-- --FILE-- parentFunc = function () use ($pm) { }; $pm->childFunc = function () use ($pm) { }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- ================================================ FILE: tests/test.sql ================================================ /* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 80011 Source Host : localhost:3306 Source Schema : test Target Server Type : MySQL Target Server Version : 80011 File Encoding : 65001 Date: 14/09/2018 16:13:17 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for ckl -- ---------------------------- DROP TABLE IF EXISTS `ckl`; CREATE TABLE `ckl` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain` varchar(128) NOT NULL, `path` varchar(128) NOT NULL, `name` varchar(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of ckl -- ---------------------------- BEGIN; INSERT INTO `ckl` VALUES (1, 'www.baidu.com', '/search', 'baidu'); INSERT INTO `ckl` VALUES (2, 'www.taobao.com', '/search', 'taobao'); INSERT INTO `ckl` VALUES (3, 'www.qq.com', '/search', 'qq'); COMMIT; -- ---------------------------- -- Table structure for custom -- ---------------------------- DROP TABLE IF EXISTS `custom`; CREATE TABLE `custom` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ---------------------------- -- Table structure for numbers -- ---------------------------- DROP TABLE IF EXISTS `numbers`; CREATE TABLE `numbers` ( `id` int(11) NOT NULL AUTO_INCREMENT, `tinyint` tinyint(4) NOT NULL, `utinyint` tinyint(255) unsigned NOT NULL, `smallint` smallint(6) NOT NULL, `usmallint` smallint(5) unsigned NOT NULL, `mediumint` mediumint(9) NOT NULL, `umediumint` mediumint(8) unsigned NOT NULL, `int` int(11) NOT NULL, `uint` int(10) unsigned NOT NULL, `bigint` bigint(20) NOT NULL, `ubigint` bigint(20) unsigned NOT NULL, `float` float NOT NULL, `double` double NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- ---------------------------- -- Records of numbers -- ---------------------------- BEGIN; INSERT INTO `numbers` VALUES (1, 127, 255, 32767, 65535, 8388607, 16777215, 2147483647, 4294967294, 9223372036854775807, 18446744073709551615, 1.23457, 1.2345678901234567); INSERT INTO `numbers` VALUES (2, -128, 123, -32768, 12345, -8388608, 123456, -2147483648, 123456, -9223372036854775808, 123456, -1.23457, -1.2345678901234567); INSERT INTO `numbers` VALUES (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.23, 1.23); COMMIT; -- ---------------------------- -- Table structure for userinfo -- ---------------------------- DROP TABLE IF EXISTS `userinfo`; CREATE TABLE `userinfo` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(40) NOT NULL, `level` int(11) NOT NULL, `passwd` varchar(40), `regtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `big_n` bigint(20) NOT NULL, `data` json NOT NULL, `lastlogin_ip` int(11) NOT NULL, `price` double NOT NULL, `mdate` date NOT NULL, `mtime` time NOT NULL, `mdatetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `year` year(4) NOT NULL DEFAULT '1970', `int8_t` tinyint(11) NOT NULL, `mshort` smallint(6) NOT NULL, `mtext` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=144 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of userinfo -- ---------------------------- BEGIN; INSERT INTO `userinfo` VALUES (1, 'jack', 199, 'xuyou', '2015-01-02 02:00:00', 999000, 'null', 1270, 0.22, '1970-01-01', '21:52:33', '2018-04-17 04:16:20', 1989, 127, 32767, ''); INSERT INTO `userinfo` VALUES (2, 'jack', 0, 'xuyou', '2016-05-20 00:00:00', 0, '{\"a\": 123}', 0, 0, '1970-01-01', '00:00:00', '1970-01-01 01:03:00', 1999, 0, 0, NULL); INSERT INTO `userinfo` VALUES (3, '韩天峰', 0, 'xuyou', '2016-05-20 19:08:47', 0, 'null', 0, 0, '1970-01-01', '00:00:00', '1970-01-01 00:00:00', 0000, 0, 0, ''); INSERT INTO `userinfo` VALUES (4, 'jack', 11, 'xuyou', '2016-05-20 19:17:33', 0, 'null', 0, 0, '1970-01-01', '00:00:00', '1970-01-01 00:00:00', 0000, 0, 0, NULL); INSERT INTO `userinfo` VALUES (5, 'rango22', 0, '123456', '2016-07-19 13:31:37', 0, 'null', 0, 0, '1970-01-01', '00:00:00', '1970-01-01 00:00:00', 0000, 0, 0, ''); INSERT INTO `userinfo` VALUES (6, 'hello', 99, NULL, '2017-07-03 19:37:37', 19999991, 'null', 7775533, 256.33, '2017-12-13', '09:51:29', '1970-01-01 00:00:00', 2015, 127, 32321, '我们都是中国人,你很好吗?'); INSERT INTO `userinfo` VALUES (7, 'twosee', 0, NULL, '2017-07-03 19:37:49', 99999999, '{}', 0, 0, '1997-06-04', '01:02:03', '1997-06-04 04:05:06.0708', 0000, 0, 0, ''); INSERT INTO `userinfo` VALUES (8, 'hello', 99, '123456', '2018-04-09 15:48:00', 99999999, 'null', 0, 0, '1970-01-01', '00:00:00', '1970-01-01 00:00:00', 0000, 0, 0, NULL); COMMIT; SET FOREIGN_KEY_CHECKS = 1; ================================================ FILE: thirdparty/boost/asm/LICENSE ================================================ Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: thirdparty/boost/asm/combined.S ================================================ #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #if defined(__x86_64__) #include "make_x86_64_sysv_elf_gas.S" #include "jump_x86_64_sysv_elf_gas.S" #elif defined(__ppc64__) #include "make_ppc64_sysv_elf_gas.S" #include "jump_ppc64_sysv_elf_gas.S" #elif defined(__arm64__) || defined(__aarch64__) #include "make_arm64_aapcs_elf_gas.S" #include "jump_arm64_aapcs_elf_gas.S" #elif defined(__loongarch64) #include "make_loongarch64_sysv_elf_gas.S" #include "jump_loongarch64_sysv_elf_gas.S" #else #error "No arch's" #endif #elif defined(__MACH__) #include "make_combined_sysv_macho_gas.S" #include "jump_combined_sysv_macho_gas.S" #else #error "not supports" #endif ================================================ FILE: thirdparty/boost/asm/jump_arm64_aapcs_elf_gas.S ================================================ /* Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | d8 | d9 | d10 | d11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | d12 | d13 | d14 | d15 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | x19 | x20 | x21 | x22 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | x23 | x24 | x25 | x26 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | x27 | x28 | FP | LR | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | | | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| | | * * ------------------------------------------------- * * | PC | align | | | * * ------------------------------------------------- * * * *******************************************************/ .file "jump_arm64_aapcs_elf_gas.S" .text .align 2 .global swoole_jump_fcontext .type swoole_jump_fcontext, %function swoole_jump_fcontext: # prepare stack for GP + FPU sub sp, sp, #0xb0 # save d8 - d15 stp d8, d9, [sp, #0x00] stp d10, d11, [sp, #0x10] stp d12, d13, [sp, #0x20] stp d14, d15, [sp, #0x30] # save x19-x30 stp x19, x20, [sp, #0x40] stp x21, x22, [sp, #0x50] stp x23, x24, [sp, #0x60] stp x25, x26, [sp, #0x70] stp x27, x28, [sp, #0x80] stp x29, x30, [sp, #0x90] # save LR as PC str x30, [sp, #0xa0] # store RSP (pointing to context-data) in X0 mov x4, sp # restore RSP (pointing to context-data) from X1 mov sp, x0 # load d8 - d15 ldp d8, d9, [sp, #0x00] ldp d10, d11, [sp, #0x10] ldp d12, d13, [sp, #0x20] ldp d14, d15, [sp, #0x30] # load x19-x30 ldp x19, x20, [sp, #0x40] ldp x21, x22, [sp, #0x50] ldp x23, x24, [sp, #0x60] ldp x25, x26, [sp, #0x70] ldp x27, x28, [sp, #0x80] ldp x29, x30, [sp, #0x90] # return transfer_t from jump # pass transfer_t as first arg in context function # X0 == FCTX, X1 == DATA mov x0, x4 # load pc ldr x4, [sp, #0xa0] # restore stack from GP + FPU add sp, sp, #0xb0 ret x4 .size swoole_jump_fcontext,.-swoole_jump_fcontext # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_arm64_aapcs_macho_gas.S ================================================ /* Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | d8 | d9 | d10 | d11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | d12 | d13 | d14 | d15 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | x19 | x20 | x21 | x22 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | x23 | x24 | x25 | x26 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | x27 | x28 | FP | LR | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | | | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| | | * * ------------------------------------------------- * * | PC | align | | | * * ------------------------------------------------- * * * *******************************************************/ .text .globl _swoole_jump_fcontext .balign 16 _swoole_jump_fcontext: ; prepare stack for GP + FPU sub sp, sp, #0xb0 ; save d8 - d15 stp d8, d9, [sp, #0x00] stp d10, d11, [sp, #0x10] stp d12, d13, [sp, #0x20] stp d14, d15, [sp, #0x30] ; save x19-x30 stp x19, x20, [sp, #0x40] stp x21, x22, [sp, #0x50] stp x23, x24, [sp, #0x60] stp x25, x26, [sp, #0x70] stp x27, x28, [sp, #0x80] stp fp, lr, [sp, #0x90] ; save LR as PC str lr, [sp, #0xa0] ; store RSP (pointing to context-data) in X0 mov x4, sp ; restore RSP (pointing to context-data) from X1 mov sp, x0 ; load d8 - d15 ldp d8, d9, [sp, #0x00] ldp d10, d11, [sp, #0x10] ldp d12, d13, [sp, #0x20] ldp d14, d15, [sp, #0x30] ; load x19-x30 ldp x19, x20, [sp, #0x40] ldp x21, x22, [sp, #0x50] ldp x23, x24, [sp, #0x60] ldp x25, x26, [sp, #0x70] ldp x27, x28, [sp, #0x80] ldp fp, lr, [sp, #0x90] ; return transfer_t from jump ; pass transfer_t as first arg in context function ; X0 == FCTX, X1 == DATA mov x0, x4 ; load pc ldr x4, [sp, #0xa0] ; restore stack from GP + FPU add sp, sp, #0xb0 ret x4 ================================================ FILE: thirdparty/boost/asm/jump_combined_sysv_macho_gas.S ================================================ /* Copyright Sergue E. Leontiev 2013. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ // Stub file for universal binary #if defined(__x86_64__) #include "jump_x86_64_sysv_macho_gas.S" #elif defined(__ppc64__) #include "jump_ppc64_sysv_macho_gas.S" #elif defined(__arm64__) #include "jump_arm64_aapcs_macho_gas.S" #else #error "No arch's" #endif ================================================ FILE: thirdparty/boost/asm/jump_loongarch64_sysv_elf_gas.S ================================================ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 8 | 16 | 24 | * * ------------------------------------------------- * * | FS0 | FS1 | FS2 | FS3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 40 | 48 | 56 | * * ------------------------------------------------- * * | FS4 | FS5 | FS6 | FS7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 72 | 80 | 88 | * * ------------------------------------------------- * * | S0 | S1 | S2 | S3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | S4 | S5 | S6 | S7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | S8 | FP | RA | PC | * * ------------------------------------------------- * * * * *****************************************************/ .file "jump_loongarch64_sysv_elf_gas.S" .text .globl swoole_jump_fcontext .align 2 .type swoole_jump_fcontext,@function swoole_jump_fcontext: # reserve space on stack addi.d $sp, $sp, -160 # save fs0 - fs7 fst.d $fs0, $sp, 0 fst.d $fs1, $sp, 8 fst.d $fs2, $sp, 16 fst.d $fs3, $sp, 24 fst.d $fs4, $sp, 32 fst.d $fs5, $sp, 40 fst.d $fs6, $sp, 48 fst.d $fs7, $sp, 56 # save s0 - s8, fp, ra st.d $s0, $sp, 64 st.d $s1, $sp, 72 st.d $s2, $sp, 80 st.d $s3, $sp, 88 st.d $s4, $sp, 96 st.d $s5, $sp, 104 st.d $s6, $sp, 112 st.d $s7, $sp, 120 st.d $s8, $sp, 128 st.d $fp, $sp, 136 st.d $ra, $sp, 144 # save RA as PC st.d $ra, $sp, 152 # store SP (pointing to context-data) in A2 move $a2, $sp # restore SP (pointing to context-data) from A0 move $sp, $a0 # load fs0 - fs7 fld.d $fs0, $sp, 0 fld.d $fs1, $sp, 8 fld.d $fs2, $sp, 16 fld.d $fs3, $sp, 24 fld.d $fs4, $sp, 32 fld.d $fs5, $sp, 40 fld.d $fs6, $sp, 48 fld.d $fs7, $sp, 56 #load s0 - s7 ld.d $s0, $sp, 64 ld.d $s1, $sp, 72 ld.d $s2, $sp, 80 ld.d $s3, $sp, 88 ld.d $s4, $sp, 96 ld.d $s5, $sp, 104 ld.d $s6, $sp, 112 ld.d $s7, $sp, 120 ld.d $s8, $sp, 128 ld.d $fp, $sp, 136 ld.d $ra, $sp, 144 # return transfer_t from jump # pass transfer_t as first arg in context function # a0 == FCTX, a1 == DATA move $a0, $a2 # load PC ld.d $a2, $sp, 152 # restore stack addi.d $sp, $sp, 160 # jump to context jr $a2 .size swoole_jump_fcontext, .-swoole_jump_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_mips64_n64_elf_gas.S ================================================ /* Copyright Jiaxun Yang 2018. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 8 | 16 | 24 | * * ------------------------------------------------- * * | F24 | F25 | F26 | F27 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 40 | 48 | 56 | * * ------------------------------------------------- * * | F28 | F29 | F30 | F31 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 72 | 80 | 88 | * * ------------------------------------------------- * * | S0 | S1 | S2 | S3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | S4 | S5 | S6 | S7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | FP | GP | RA | PC | * * ------------------------------------------------- * * * * *****************************************************/ .file "jump_mips64_n64_elf_gas.S" .text .globl swoole_jump_fcontext .align 3 .type swoole_jump_fcontext,@function .ent swoole_jump_fcontext swoole_jump_fcontext: # reserve space on stack daddiu $sp, $sp, -160 sd $s0, 64($sp) # save S0 sd $s1, 72($sp) # save S1 sd $s2, 80($sp) # save S2 sd $s3, 88($sp) # save S3 sd $s4, 96($sp) # save S4 sd $s5, 104($sp) # save S5 sd $s6, 112($sp) # save S6 sd $s7, 120($sp) # save S7 sd $fp, 128($sp) # save FP sd $ra, 144($sp) # save RA sd $ra, 152($sp) # save RA as PC #if defined(__mips_hard_float) s.d $f24, 0($sp) # save F24 s.d $f25, 8($sp) # save F25 s.d $f26, 16($sp) # save F26 s.d $f27, 24($sp) # save F27 s.d $f28, 32($sp) # save F28 s.d $f29, 40($sp) # save F29 s.d $f30, 48($sp) # save F30 s.d $f31, 56($sp) # save F31 #endif # store SP (pointing to old context-data) in v0 as return move $v0, $sp # get SP (pointing to new context-data) from a0 param move $sp, $a0 #if defined(__mips_hard_float) l.d $f24, 0($sp) # restore F24 l.d $f25, 8($sp) # restore F25 l.d $f26, 16($sp) # restore F26 l.d $f27, 24($sp) # restore F27 l.d $f28, 32($sp) # restore F28 l.d $f29, 40($sp) # restore F29 l.d $f30, 48($sp) # restore F30 l.d $f31, 56($sp) # restore F31 #endif ld $s0, 64($sp) # restore S0 ld $s1, 72($sp) # restore S1 ld $s2, 80($sp) # restore S2 ld $s3, 88($sp) # restore S3 ld $s4, 96($sp) # restore S4 ld $s5, 104($sp) # restore S5 ld $s6, 112($sp) # restore S6 ld $s7, 120($sp) # restore S7 ld $fp, 128($sp) # restore FP ld $ra, 144($sp) # restore RAa # load PC ld $t9, 152($sp) # adjust stack daddiu $sp, $sp, 160 move $a0, $v0 # move old sp from v0 to a0 as param move $v1, $a1 # move *data from a1 to v1 as return # jump to context jr $t9 .end swoole_jump_fcontext .size swoole_jump_fcontext, .-swoole_jump_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_ppc64_sysv_elf_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ .file "jump_ppc64_sysv_elf_gas.S" .globl swoole_jump_fcontext #if _CALL_ELF == 2 .text .align 2 swoole_jump_fcontext: addis %r2, %r12, .TOC.-swoole_jump_fcontext@ha addi %r2, %r2, .TOC.-swoole_jump_fcontext@l .localentry swoole_jump_fcontext, . - swoole_jump_fcontext #else .section ".opd","aw" .align 3 swoole_jump_fcontext: # ifdef _CALL_LINUX .quad .L.swoole_jump_fcontext,.TOC.@tocbase,0 .type swoole_jump_fcontext,@function .text .align 2 .L.swoole_jump_fcontext: # else .hidden .swoole_jump_fcontext .globl .swoole_jump_fcontext .quad .swoole_jump_fcontext,.TOC.@tocbase,0 .size swoole_jump_fcontext,24 .type .swoole_jump_fcontext,@function .text .align 2 .swoole_jump_fcontext: # endif #endif # reserve space on stack subi %r1, %r1, 184 #if _CALL_ELF != 2 std %r2, 0(%r1) # save TOC #endif std %r14, 8(%r1) # save R14 std %r15, 16(%r1) # save R15 std %r16, 24(%r1) # save R16 std %r17, 32(%r1) # save R17 std %r18, 40(%r1) # save R18 std %r19, 48(%r1) # save R19 std %r20, 56(%r1) # save R20 std %r21, 64(%r1) # save R21 std %r22, 72(%r1) # save R22 std %r23, 80(%r1) # save R23 std %r24, 88(%r1) # save R24 std %r25, 96(%r1) # save R25 std %r26, 104(%r1) # save R26 std %r27, 112(%r1) # save R27 std %r28, 120(%r1) # save R28 std %r29, 128(%r1) # save R29 std %r30, 136(%r1) # save R30 std %r31, 144(%r1) # save R31 #if _CALL_ELF != 2 std %r3, 152(%r1) # save hidden #endif # save CR mfcr %r0 std %r0, 160(%r1) # save LR mflr %r0 std %r0, 168(%r1) # save LR as PC std %r0, 176(%r1) # store RSP (pointing to context-data) in R6 mr %r6, %r1 #if _CALL_ELF == 2 # restore RSP (pointing to context-data) from R3 mr %r1, %r3 #else # restore RSP (pointing to context-data) from R4 mr %r1, %r4 ld %r2, 0(%r1) # restore TOC #endif ld %r14, 8(%r1) # restore R14 ld %r15, 16(%r1) # restore R15 ld %r16, 24(%r1) # restore R16 ld %r17, 32(%r1) # restore R17 ld %r18, 40(%r1) # restore R18 ld %r19, 48(%r1) # restore R19 ld %r20, 56(%r1) # restore R20 ld %r21, 64(%r1) # restore R21 ld %r22, 72(%r1) # restore R22 ld %r23, 80(%r1) # restore R23 ld %r24, 88(%r1) # restore R24 ld %r25, 96(%r1) # restore R25 ld %r26, 104(%r1) # restore R26 ld %r27, 112(%r1) # restore R27 ld %r28, 120(%r1) # restore R28 ld %r29, 128(%r1) # restore R29 ld %r30, 136(%r1) # restore R30 ld %r31, 144(%r1) # restore R31 #if _CALL_ELF != 2 ld %r3, 152(%r1) # restore hidden #endif # restore CR ld %r0, 160(%r1) mtcr %r0 # restore LR ld %r0, 168(%r1) mtlr %r0 # load PC ld %r12, 176(%r1) # restore CTR mtctr %r12 # adjust stack addi %r1, %r1, 184 #if _CALL_ELF == 2 # copy transfer_t into transfer_fn arg registers mr %r3, %r6 # arg pointer already in %r4 # jump to context bctr .size swoole_jump_fcontext, .-swoole_jump_fcontext #else # zero in r3 indicates first jump to context-function cmpdi %r3, 0 beq use_entry_arg # return transfer_t std %r6, 0(%r3) std %r5, 8(%r3) # jump to context bctr use_entry_arg: # copy transfer_t into transfer_fn arg registers mr %r3, %r6 mr %r4, %r5 # jump to context bctr # ifdef _CALL_LINUX .size .swoole_jump_fcontext, .-.L.swoole_jump_fcontext # else .size .swoole_jump_fcontext, .-.swoole_jump_fcontext # endif #endif /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_ppc64_sysv_macho_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | R13 | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | FCTX | DATA | | | * * ------------------------------------------------- * * * *******************************************************/ .text .align 2 .globl _swoole_jump_fcontext _swoole_jump_fcontext: ; reserve space on stack subi r1, r1, 184 std r14, 8(r1) ; save R14 std r15, 16(r1) ; save R15 std r16, 24(r1) ; save R16 std r17, 32(r1) ; save R17 std r18, 40(r1) ; save R18 std r19, 48(r1) ; save R19 std r20, 56(r1) ; save R20 std r21, 64(r1) ; save R21 std r22, 72(r1) ; save R22 std r23, 80(r1) ; save R23 std r24, 88(r1) ; save R24 std r25, 96(r1) ; save R25 std r26, 104(r1) ; save R26 std r27, 112(r1) ; save R27 std r28, 120(r1) ; save R28 std r29, 128(r1) ; save R29 std r30, 136(r1) ; save R30 std r31, 144(r1) ; save R31 std r3, 152(r1) ; save hidden ; save CR mfcr r0 std r0, 160(r1) ; save LR mflr r0 std r0, 168(r1) ; save LR as PC std r0, 176(r1) ; store RSP (pointing to context-data) in R6 mr r6, r1 ; restore RSP (pointing to context-data) from R4 mr r1, r4 ld r14, 8(r1) ; restore R14 ld r15, 16(r1) ; restore R15 ld r16, 24(r1) ; restore R16 ld r17, 32(r1) ; restore R17 ld r18, 40(r1) ; restore R18 ld r19, 48(r1) ; restore R19 ld r20, 56(r1) ; restore R20 ld r21, 64(r1) ; restore R21 ld r22, 72(r1) ; restore R22 ld r23, 80(r1) ; restore R23 ld r24, 88(r1) ; restore R24 ld r25, 96(r1) ; restore R25 ld r26, 104(r1) ; restore R26 ld r27, 112(r1) ; restore R27 ld r28, 120(r1) ; restore R28 ld r29, 128(r1) ; restore R29 ld r30, 136(r1) ; restore R30 ld r31, 144(r1) ; restore R31 ld r3, 152(r1) ; restore hidden ; restore CR ld r0, 160(r1) mtcr r0 ; restore LR ld r0, 168(r1) mtlr r0 ; load PC ld r12, 176(r1) ; restore CTR mtctr r12 ; adjust stack addi r1, r1, 184 ; zero in r3 indicates first jump to context-function cmpdi r3, 0 beq use_entry_arg ; return transfer_t std r6, 0(r3) std r5, 8(r3) ; jump to context bctr use_entry_arg: ; copy transfer_t into transfer_fn arg registers mr r3, r6 mr r4, r5 ; jump to context bctr ================================================ FILE: thirdparty/boost/asm/jump_ppc64_sysv_xcoff_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ .file "jump_ppc64_sysv_xcoff_gas.S" .toc .csect .text[PR], 5 .align 2 .globl swoole_jump_fcontext[DS] .globl .swoole_jump_fcontext .csect swoole_jump_fcontext[DS], 3 swoole_jump_fcontext: .llong .swoole_jump_fcontext[PR], TOC[tc0], 0 .csect .text[PR], 5 .swoole_jump_fcontext: # reserve space on stack subi 1, 1, 184 std 2, 0(1) # save TOC std 14, 8(1) # save R14 std 15, 16(1) # save R15 std 16, 24(1) # save R16 std 17, 32(1) # save R17 std 18, 40(1) # save R18 std 19, 48(1) # save R19 std 20, 56(1) # save R20 std 21, 64(1) # save R21 std 22, 72(1) # save R22 std 23, 80(1) # save R23 std 24, 88(1) # save R24 std 25, 96(1) # save R25 std 26, 104(1) # save R26 std 27, 112(1) # save R27 std 28, 120(1) # save R28 std 29, 128(1) # save R29 std 30, 136(1) # save R30 std 31, 144(1) # save R31 std 3, 152(1) # save hidden # save CR mfcr 0 std 0, 160(1) # save LR mflr 0 std 0, 168(1) # save LR as PC std 0, 176(1) # store RSP (pointing to context-data) in R6 mr 6, 1 # restore RSP (pointing to context-data) from R4 mr 1, 4 ld 2, 0(1) # restore TOC ld 14, 8(1) # restore R14 ld 15, 16(1) # restore R15 ld 16, 24(1) # restore R16 ld 17, 32(1) # restore R17 ld 18, 40(1) # restore R18 ld 19, 48(1) # restore R19 ld 20, 56(1) # restore R20 ld 21, 64(1) # restore R21 ld 22, 72(1) # restore R22 ld 23, 80(1) # restore R23 ld 24, 88(1) # restore R24 ld 25, 96(1) # restore R25 ld 26, 104(1) # restore R26 ld 27, 112(1) # restore R27 ld 28, 120(1) # restore R28 ld 29, 128(1) # restore R29 ld 30, 136(1) # restore R30 ld 31, 144(1) # restore R31 ld 3, 152(1) # restore hidden # restore CR ld 0, 160(1) mtcr 0 # restore LR ld 0, 168(1) mtlr 0 # load PC ld 0, 176(1) # restore CTR mtctr 0 # adjust stack addi 1, 1, 184 # zero in r3 indicates first jump to context-function cmpdi 3, 0 beq use_entry_arg # return transfer_t std 6, 0(3) std 5, 8(3) # jump to context bctr use_entry_arg: # copy transfer_t into transfer_fn arg registers mr 3, 6 mr 4, 5 # jump to context bctr ================================================ FILE: thirdparty/boost/asm/jump_riscv64_sysv_elf_gas.S ================================================ /* Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | fs0 | fs1 | fs2 | fs3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | fs4 | fs5 | fs6 | fs7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | fs8 | fs9 | fs10 | fs11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | s0 | s1 | s2 | s3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | s4 | s5 | s6 | s7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| 0xb0| 0xb4| 0xb8| 0xbc| * * ------------------------------------------------- * * | s8 | s9 | s10 | s11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | | | | | * * ------------------------------------------------- * * | 0xc0| 0xc4| 0xc8| 0xcc| | | | | * * ------------------------------------------------- * * | ra | pc | | | * * ------------------------------------------------- * * * *******************************************************/ .file "jump_riscv64_sysv_elf_gas.S" .text .align 1 .global swoole_jump_fcontext .type swoole_jump_fcontext, %function swoole_jump_fcontext: # prepare stack for GP + FPU addi sp, sp, -0xd0 # save fs0 - fs11 fsd fs0, 0x00(sp) fsd fs1, 0x08(sp) fsd fs2, 0x10(sp) fsd fs3, 0x18(sp) fsd fs4, 0x20(sp) fsd fs5, 0x28(sp) fsd fs6, 0x30(sp) fsd fs7, 0x38(sp) fsd fs8, 0x40(sp) fsd fs9, 0x48(sp) fsd fs10, 0x50(sp) fsd fs11, 0x58(sp) # save s0-s11, ra sd s0, 0x60(sp) sd s1, 0x68(sp) sd s2, 0x70(sp) sd s3, 0x78(sp) sd s4, 0x80(sp) sd s5, 0x88(sp) sd s6, 0x90(sp) sd s7, 0x98(sp) sd s8, 0xa0(sp) sd s9, 0xa8(sp) sd s10, 0xb0(sp) sd s11, 0xb8(sp) sd ra, 0xc0(sp) # save RA as PC sd ra, 0xc8(sp) # store SP (pointing to context-data) in A2 mv a2, sp # restore SP (pointing to context-data) from A0 mv sp, a0 # load fs0 - fs11 fld fs0, 0x00(sp) fld fs1, 0x08(sp) fld fs2, 0x10(sp) fld fs3, 0x18(sp) fld fs4, 0x20(sp) fld fs5, 0x28(sp) fld fs6, 0x30(sp) fld fs7, 0x38(sp) fld fs8, 0x40(sp) fld fs9, 0x48(sp) fld fs10, 0x50(sp) fld fs11, 0x58(sp) # load s0-s11,ra ld s0, 0x60(sp) ld s1, 0x68(sp) ld s2, 0x70(sp) ld s3, 0x78(sp) ld s4, 0x80(sp) ld s5, 0x88(sp) ld s6, 0x90(sp) ld s7, 0x98(sp) ld s8, 0xa0(sp) ld s9, 0xa8(sp) ld s10, 0xb0(sp) ld s11, 0xb8(sp) ld ra, 0xc0(sp) # return transfer_t from jump # pass transfer_t as first arg in context function # a0 == FCTX, a1 == DATA mv a0, a2 # load pc ld a2, 0xc8(sp) # restore stack from GP + FPU addi sp, sp, 0xd0 jr a2 .size swoole_jump_fcontext,.-swoole_jump_fcontext # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_sparc64_sysv_elf_gas.S ================================================ /* Copyright Martin Husemann 2013. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************************* * * * ------------------------------------------------------------- * * | Offset (in 4 or 8 byte units) | Content | * * ------------------------------------------------------------- * * | 0 | %sp | * * ------------------------------------------------------------- * * | 1 | %pc | * * ------------------------------------------------------------- * * | 2 | %i7 (return address) | * * ------------------------------------------------------------- * * | 3 | %g1 | * * ------------------------------------------------------------- * * | 4 | %g2 | * * ------------------------------------------------------------- * * | 5 | %g3 | * * ------------------------------------------------------------- * * | 6 | %g6 | * * ------------------------------------------------------------- * * | 7 | %g7 | * * ------------------------------------------------------------- * * The local and in registers are stored on the stack. * *******************************************************************/ #define OFF(N) (8*(N)) #define CCFSZ 176 // C Compiler Frame Size #define BIAS (2048-1) // Stack offset for 64 bit programs #define FC_SZ 448 // sizeof(fcontext_t) #define FC_STK 384 // offsetof(fcontext_t, fc_stack) #define FC_FPU 0 // offsetof(fcontext_t, fc_fp) #define FC_FSR 264 // offsetof(fcontext_t, fc_fp.fp_fsr) #define FC_FPRS 256 // offsetof(fcontext_t, fc_fp.fp_fprs) #define FC_GREG 320 // offsetof(fcontext_t, fc_greg) #define BLOCK_SIZE 64 .register %g2,#ignore .register %g3,#ignore .register %g6,#ignore .text .globl swoole_jump_fcontext .align 4 .type swoole_jump_fcontext,@function // intptr_t // swoole_jump_fcontext( fcontext_t * ofc, fcontext_t const* nfc, intptr_t vp, // bool preserve_fpu = true); swoole_jump_fcontext: // %o0 = pointer to old fcontext, save current state here // %o1 = new context to jump to // %o2 = new return value in context %o0 // %o3 = preserve fpu registers // Save current state in %o0 fcontext, then activate %o1. // If %o3, include fpu registers. flushw // make sure all shadow registers are up to date in the current stack // save current state to fcontext_t at %o0 stx %sp, [%o0 + FC_GREG + OFF(0)] // current stack pointer add %o7, 8, %o4 // calculate next instruction past call stx %o4, [%o0 + FC_GREG + OFF(1)] // and store it as %pc in save context stx %o7, [%o0 + FC_GREG + OFF(2)] stx %g1, [%o0 + FC_GREG + OFF(3)] stx %g2, [%o0 + FC_GREG + OFF(4)] stx %g3, [%o0 + FC_GREG + OFF(5)] stx %g6, [%o0 + FC_GREG + OFF(6)] stx %g7, [%o0 + FC_GREG + OFF(7)] // do we need to handle fpu? brz %o3, Lno_fpu nop add %o0, FC_FPU, %o5 stda %f0, [%o5] 0xf0 /* ASI_BLOCK_PRIMARY */ add %o5, BLOCK_SIZE, %o5 stda %f16, [%o5] 0xf0 add %o5, BLOCK_SIZE, %o5 stda %f32, [%o5] 0xf0 add %o5, BLOCK_SIZE, %o5 stda %f48, [%o5] 0xf0 stx %fsr, [%o0+FC_FSR] rd %fprs, %o4 stx %o4, [%o0+FC_FPRS] add %o1, FC_FPU, %o5 ldda [%o5] 0xf0 /* ASI_BLOCK_PRIMARY */, %f0 add %o5, BLOCK_SIZE, %o5 ldda [%o5] 0xf0, %f16 add %o5, BLOCK_SIZE, %o5 ldda [%o5] 0xf0, %f32 add %o5, BLOCK_SIZE, %o5 ldda [%o5] 0xf0, %f48 ldx [%o1+FC_FSR], %fsr ldx [%o1+FC_FPRS], %o4 wr %o4,0,%fprs Lno_fpu: // load new state from %o1 ldx [%o1 + FC_GREG + OFF(1)], %o4 ldx [%o1 + FC_GREG + OFF(2)], %o7 ldx [%o1 + FC_GREG + OFF(3)], %g1 ldx [%o1 + FC_GREG + OFF(4)], %g2 ldx [%o1 + FC_GREG + OFF(5)], %g3 ldx [%o1 + FC_GREG + OFF(6)], %g6 ldx [%o1 + FC_GREG + OFF(7)], %g7 // switch to new stack ldx [%o1 + FC_GREG + OFF(0)], %sp // and now reload from this stack the shadow regist bank contents ldx [%sp + BIAS + OFF(0)], %l0 ldx [%sp + BIAS + OFF(1)], %l1 ldx [%sp + BIAS + OFF(2)], %l2 ldx [%sp + BIAS + OFF(3)], %l3 ldx [%sp + BIAS + OFF(4)], %l4 ldx [%sp + BIAS + OFF(5)], %l5 ldx [%sp + BIAS + OFF(6)], %l6 ldx [%sp + BIAS + OFF(7)], %l7 ldx [%sp + BIAS + OFF(8)], %i0 ldx [%sp + BIAS + OFF(9)], %i1 ldx [%sp + BIAS + OFF(10)], %i2 ldx [%sp + BIAS + OFF(11)], %i3 ldx [%sp + BIAS + OFF(12)], %i4 ldx [%sp + BIAS + OFF(13)], %i5 ldx [%sp + BIAS + OFF(14)], %i6 ldx [%sp + BIAS + OFF(15)], %i7 // finally continue execution in new context jmp %o4 mov %o2, %o0 // return arg as result .size swoole_jump_fcontext,.-swoole_jump_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_x86_64_sysv_elf_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /**************************************************************************************** * * * ---------------------------------------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * * | fc_mxcsr|fc_x87_cw| guard | R12 | R13 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * * | R14 | R15 | RBX | RBP | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ---------------------------------------------------------------------------------- * * | 0x40 | 0x44 | | * * ---------------------------------------------------------------------------------- * * | RIP | | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ # if defined __CET__ # include # define SWOOLE_SHSTK_ENABLED (__CET__ & 0x2) # define SWOOLE_CONTEXT_SHADOW_STACK (SWOOLE_SHSTK_ENABLED && SHADOW_STACK_SYSCALL) # else # define _CET_ENDBR # endif .file "jump_x86_64_sysv_elf_gas.S" .text .globl swoole_jump_fcontext .type swoole_jump_fcontext,@function .align 16 swoole_jump_fcontext: _CET_ENDBR leaq -0x40(%rsp), %rsp /* prepare stack */ #if !defined(SWOOLE_USE_TSX) stmxcsr (%rsp) /* save MMX control- and status-word */ fnstcw 0x4(%rsp) /* save x87 control-word */ #endif #if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) movq %fs:0x28, %rcx /* read stack guard from TLS record */ movq %rcx, 0x8(%rsp) /* save stack guard */ #endif movq %r12, 0x10(%rsp) /* save R12 */ movq %r13, 0x18(%rsp) /* save R13 */ movq %r14, 0x20(%rsp) /* save R14 */ movq %r15, 0x28(%rsp) /* save R15 */ movq %rbx, 0x30(%rsp) /* save RBX */ movq %rbp, 0x38(%rsp) /* save RBP */ #if SWOOLE_CONTEXT_SHADOW_STACK /* grow the stack to reserve space for shadow stack pointer(SSP) */ leaq -0x8(%rsp), %rsp /* read the current SSP and store it */ rdsspq %rcx movq %rcx, (%rsp) #endif /* store RSP (pointing to context-data) in RAX */ movq %rsp, %rax /* restore RSP (pointing to context-data) from RDI */ movq %rdi, %rsp #if SWOOLE_CONTEXT_SHADOW_STACK /* first 8 bytes are SSP */ movq (%rsp), %rcx leaq 0x8(%rsp), %rsp /* Restore target(new) shadow stack */ rstorssp -8(%rcx) /* restore token for previous shadow stack is pushed */ /* on previous shadow stack after saveprevssp */ saveprevssp /* when return, swoole_jump_fcontext jump to restored return address */ /* (r8) instead of RET. This miss of RET implies us to unwind */ /* shadow stack accordingly. Otherwise mismatch occur */ movq $1, %rcx incsspq %rcx #endif movq 0x40(%rsp), %r8 /* restore return-address */ #if !defined(SWOOLE_USE_TSX) ldmxcsr (%rsp) /* restore MMX control- and status-word */ fldcw 0x4(%rsp) /* restore x87 control-word */ #endif #if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) movq 0x8(%rsp), %rdx /* load stack guard */ movq %rdx, %fs:0x28 /* restore stack guard to TLS record */ #endif movq 0x10(%rsp), %r12 /* restore R12 */ movq 0x18(%rsp), %r13 /* restore R13 */ movq 0x20(%rsp), %r14 /* restore R14 */ movq 0x28(%rsp), %r15 /* restore R15 */ movq 0x30(%rsp), %rbx /* restore RBX */ movq 0x38(%rsp), %rbp /* restore RBP */ leaq 0x48(%rsp), %rsp /* prepare stack */ /* return transfer_t from jump */ #if !defined(_ILP32) /* RAX == fctx, RDX == data */ movq %rsi, %rdx #else /* RAX == data:fctx */ salq $32, %rsi orq %rsi, %rax #endif /* pass transfer_t as first arg in context function */ #if !defined(_ILP32) /* RDI == fctx, RSI == data */ #else /* RDI == data:fctx */ #endif movq %rax, %rdi /* indirect jump to context */ jmp *%r8 .size swoole_jump_fcontext,.-swoole_jump_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/jump_x86_64_sysv_macho_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /**************************************************************************************** * * * ---------------------------------------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * * | R15 | RBX | RBP | RIP | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ .text .globl _swoole_jump_fcontext .align 8 _swoole_jump_fcontext: leaq -0x38(%rsp), %rsp /* prepare stack */ #if !defined(SWOOLE_USE_TSX) stmxcsr (%rsp) /* save MMX control- and status-word */ fnstcw 0x4(%rsp) /* save x87 control-word */ #endif movq %r12, 0x8(%rsp) /* save R12 */ movq %r13, 0x10(%rsp) /* save R13 */ movq %r14, 0x18(%rsp) /* save R14 */ movq %r15, 0x20(%rsp) /* save R15 */ movq %rbx, 0x28(%rsp) /* save RBX */ movq %rbp, 0x30(%rsp) /* save RBP */ /* store RSP (pointing to context-data) in RAX */ movq %rsp, %rax /* restore RSP (pointing to context-data) from RDI */ movq %rdi, %rsp movq 0x38(%rsp), %r8 /* restore return-address */ #if !defined(SWOOLE_USE_TSX) ldmxcsr (%rsp) /* restore MMX control- and status-word */ fldcw 0x4(%rsp) /* restore x87 control-word */ #endif movq 0x8(%rsp), %r12 /* restore R12 */ movq 0x10(%rsp), %r13 /* restore R13 */ movq 0x18(%rsp), %r14 /* restore R14 */ movq 0x20(%rsp), %r15 /* restore R15 */ movq 0x28(%rsp), %rbx /* restore RBX */ movq 0x30(%rsp), %rbp /* restore RBP */ leaq 0x40(%rsp), %rsp /* prepare stack */ /* return transfer_t from jump */ /* RAX == fctx, RDX == data */ movq %rsi, %rdx /* pass transfer_t as first arg in context function */ /* RDI == fctx, RSI == data */ movq %rax, %rdi /* indirect jump to context */ jmp *%r8 ================================================ FILE: thirdparty/boost/asm/make_arm64_aapcs_elf_gas.S ================================================ /* Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | d8 | d9 | d10 | d11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | d12 | d13 | d14 | d15 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | x19 | x20 | x21 | x22 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | x23 | x24 | x25 | x26 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | x27 | x28 | FP | LR | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | | | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| | | * * ------------------------------------------------- * * | PC | align | | | * * ------------------------------------------------- * * * *******************************************************/ .file "make_arm64_aapcs_elf_gas.S" .text .align 2 .global swoole_make_fcontext .type swoole_make_fcontext, %function swoole_make_fcontext: # shift address in x0 (allocated stack) to lower 16 byte boundary and x0, x0, ~0xF # reserve space for context-data on context-stack sub x0, x0, #0xb0 # third arg of swoole_make_fcontext() == address of context-function # store address as a PC to jump in str x2, [x0, #0xa0] # save address of finish as return-address for context-function # will be entered after context-function returns (LR register) adr x1, finish str x1, [x0, #0x98] ret x30 // return pointer to context-data (x0) finish: # exit code is zero mov x0, #0 # exit application bl _exit .size swoole_make_fcontext,.-swoole_make_fcontext # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_arm64_aapcs_macho_gas.S ================================================ /* Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | d8 | d9 | d10 | d11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | d12 | d13 | d14 | d15 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | x19 | x20 | x21 | x22 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | x23 | x24 | x25 | x26 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | x27 | x28 | FP | LR | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | | | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| | | * * ------------------------------------------------- * * | PC | align | | | * * ------------------------------------------------- * * * *******************************************************/ .text .globl _swoole_make_fcontext .balign 16 _swoole_make_fcontext: ; shift address in x0 (allocated stack) to lower 16 byte boundary and x0, x0, ~0xF ; reserve space for context-data on context-stack sub x0, x0, #0xb0 ; third arg of swoole_make_fcontext() == address of context-function ; store address as a PC to jump in str x2, [x0, #0xa0] adr x1, finish ; save address of finish as return-address for context-function ; will be entered after context-function returns (LR register) str x1, [x0, #0x98] ret lr ; return pointer to context-data (x0) finish: ; exit code is zero mov x0, #0 ; exit application bl __exit ================================================ FILE: thirdparty/boost/asm/make_combined_sysv_macho_gas.S ================================================ /* Copyright Sergue E. Leontiev 2013. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ // Stub file for universal binary #if defined(__x86_64__) #include "make_x86_64_sysv_macho_gas.S" #elif defined(__ppc64__) #include "make_ppc64_sysv_macho_gas.S" #elif defined(__arm64__) #include "make_arm64_aapcs_macho_gas.S" #else #error "No arch's" #endif ================================================ FILE: thirdparty/boost/asm/make_loongarch64_sysv_elf_gas.S ================================================ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 8 | 16 | 24 | * * ------------------------------------------------- * * | FS0 | FS1 | FS2 | FS3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 40 | 48 | 56 | * * ------------------------------------------------- * * | FS4 | FS5 | FS6 | FS7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 72 | 80 | 88 | * * ------------------------------------------------- * * | S0 | S1 | S2 | S3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | S4 | S5 | S6 | S7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | S8 | FP | RA | PC | * * ------------------------------------------------- * * * * *****************************************************/ .file "make_loongarch64_sysv_elf_gas.S" .text .globl swoole_make_fcontext .align 2 .type swoole_make_fcontext,@function swoole_make_fcontext: # shift address in A0 to lower 16 byte boundary bstrins.d $a0, $zero, 3, 0 # reserve space for context-data on context-stack addi.d $a0, $a0, -160 # third arg of swoole_make_fcontext() == address of context-function st.d $a2, $a0, 152 # save address of finish as return-address for context-function # will be entered after context-function returns la.local $a4, finish st.d $a4, $a0, 144 # return pointer to context-data jr $ra finish: # exit code is zero li.d $a0, 0 # call _exit(0) b %plt(_exit) .size swoole_make_fcontext, .-swoole_make_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_mips64_n64_elf_gas.S ================================================ /* Copyright Jiaxun Yang 2018. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 8 | 16 | 24 | * * ------------------------------------------------- * * | F24 | F25 | F26 | F27 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 40 | 48 | 56 | * * ------------------------------------------------- * * | F28 | F29 | F30 | F31 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 72 | 80 | 88 | * * ------------------------------------------------- * * | S0 | S1 | S2 | S3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | S4 | S5 | S6 | S7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | FP | GP | RA | PC | * * ------------------------------------------------- * * * * *****************************************************/ .file "make_mips64_n64_elf_gas.S" .text .globl swoole_make_fcontext .align 3 .type swoole_make_fcontext,@function .ent swoole_make_fcontext swoole_make_fcontext: #ifdef __PIC__ .set noreorder .cpload $t9 .set reorder #endif # shift address in A0 to lower 16 byte boundary li $v1, 0xfffffffffffffff0 and $v0, $v1, $a0 # reserve space for context-data on context-stack daddiu $v0, $v0, -160 # third arg of swoole_make_fcontext() == address of context-function sd $a2, 152($v0) # save global pointer in context-data sd $gp, 136($v0) # psudo instruction compute abs address of label finish based on GP dla $t9, finish # save address of finish as return-address for context-function # will be entered after context-function returns sd $t9, 144($v0) jr $ra # return pointer to context-data finish: # reload our gp register (needed for la) daddiu $t0, $sp, -160 ld $gp, 136($t0) # call _exit(0) # the previous function should have left the 16 bytes incoming argument # area on the stack which we reuse for calling _exit dla $t9, _exit move $a0, $zero jr $t9 .end swoole_make_fcontext .size swoole_make_fcontext, .-swoole_make_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_ppc64_sysv_elf_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ .file "make_ppc64_sysv_elf_gas.S" .globl swoole_make_fcontext #if _CALL_ELF == 2 .text .align 2 swoole_make_fcontext: addis %r2, %r12, .TOC.-swoole_make_fcontext@ha addi %r2, %r2, .TOC.-swoole_make_fcontext@l .localentry swoole_make_fcontext, . - swoole_make_fcontext #else .section ".opd","aw" .align 3 swoole_make_fcontext: # ifdef _CALL_LINUX .quad .L.swoole_make_fcontext,.TOC.@tocbase,0 .type swoole_make_fcontext,@function .text .align 2 .L.swoole_make_fcontext: # else .hidden .swoole_make_fcontext .globl .swoole_make_fcontext .quad .swoole_make_fcontext,.TOC.@tocbase,0 .size swoole_make_fcontext,24 .type .swoole_make_fcontext,@function .text .align 2 .swoole_make_fcontext: # endif #endif # save return address into R6 mflr %r6 # first arg of swoole_make_fcontext() == top address of context-stack # shift address in R3 to lower 16 byte boundary clrrdi %r3, %r3, 4 # reserve space for context-data on context-stack # including 64 byte of linkage + parameter area (R1 % 16 == 0) subi %r3, %r3, 248 # third arg of swoole_make_fcontext() == address of context-function # entry point (ELFv2) or descriptor (ELFv1) #if _CALL_ELF == 2 # save address of context-function entry point std %r5, 176(%r3) #else # save address of context-function entry point ld %r4, 0(%r5) std %r4, 176(%r3) # save TOC of context-function ld %r4, 8(%r5) std %r4, 0(%r3) #endif # set back-chain to zero li %r0, 0 std %r0, 184(%r3) #if _CALL_ELF != 2 # zero in r3 indicates first jump to context-function std %r0, 152(%r3) #endif # load LR mflr %r0 # jump to label 1 bl 1f 1: # load LR into R4 mflr %r4 # compute abs address of label finish addi %r4, %r4, finish - 1b # restore LR mtlr %r0 # save address of finish as return-address for context-function # will be entered after context-function returns std %r4, 168(%r3) # restore return address from R6 mtlr %r6 blr # return pointer to context-data finish: # save return address into R0 mflr %r0 # save return address on stack, set up stack frame std %r0, 8(%r1) # allocate stack space, R1 % 16 == 0 stdu %r1, -32(%r1) # exit code is zero li %r3, 0 # exit application bl _exit nop #if _CALL_ELF == 2 .size swoole_make_fcontext, .-swoole_make_fcontext #else # ifdef _CALL_LINUX .size .swoole_make_fcontext, .-.L.swoole_make_fcontext # else .size .swoole_make_fcontext, .-.swoole_make_fcontext # endif #endif /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_ppc64_sysv_macho_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | R13 | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | FCTX | DATA | | | * * ------------------------------------------------- * * * *******************************************************/ .text .globl _swoole_make_fcontext _swoole_make_fcontext: ; save return address into R6 mflr r6 ; first arg of swoole_make_fcontext() == top address of context-function ; shift address in R3 to lower 16 byte boundary clrrwi r3, r3, 4 ; reserve space for context-data on context-stack ; including 64 byte of linkage + parameter area (R1 16 == 0) subi r3, r3, 240 ; third arg of swoole_make_fcontext() == address of context-function stw r5, 176(r3) ; set back-chain to zero li r0, 0 std r0, 184(r3) ; compute address of returned transfer_t addi r0, r3, 224 mr r4, r0 std r4, 152(r3) ; load LR mflr r0 ; jump to label 1 bl l1 l1: ; load LR into R4 mflr r4 ; compute abs address of label finish addi r4, r4, lo16((finish - .) + 4) ; restore LR mtlr r0 ; save address of finish as return-address for context-function ; will be entered after context-function returns std r4, 168(r3) ; restore return address from R6 mtlr r6 blr ; return pointer to context-data finish: ; save return address into R0 mflr r0 ; save return address on stack, set up stack frame stw r0, 8(r1) ; allocate stack space, R1 16 == 0 stwu r1, -32(r1) ; set return value to zero li r3, 0 ; exit application bl __exit nop ================================================ FILE: thirdparty/boost/asm/make_ppc64_sysv_xcoff_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ .file "make_ppc64_sysv_xcoff_gas.S" .toc .csect .text[PR], 5 .align 2 .globl swoole_make_fcontext[DS] .globl .swoole_make_fcontext .csect swoole_make_fcontext[DS], 3 swoole_make_fcontext: .llong .swoole_make_fcontext[PR], TOC[tc0], 0 .csect .text[PR], 5 .swoole_make_fcontext: # save return address into R6 mflr 6 # first arg of swoole_make_fcontext() == top address of context-function # shift address in R3 to lower 16 byte boundary clrrdi 3, 3, 4 # reserve space for context-data on context-stack # including 64 byte of linkage + parameter area (R1 % 16 == 0) subi 3, 3, 248 # third arg of swoole_make_fcontext() == address of context-function descriptor ld 4, 0(5) std 4, 176(3) # save TOC of context-function ld 4, 8(5) std 4, 0(3) # set back-chain to zero li 0, 0 std 0, 184(3) # zero in r3 indicates first jump to context-function std 0, 152(3) # load LR mflr 0 # jump to label 1 bl .Label .Label: # load LR into R4 mflr 4 # compute abs address of label .L_finish addi 4, 4, .L_finish - .Label # restore LR mtlr 0 # save address of finish as return-address for context-function # will be entered after context-function returns std 4, 168(3) # restore return address from R6 mtlr 6 blr # return pointer to context-data .L_finish: # save return address into R0 mflr 0 # save return address on stack, set up stack frame std 0, 8(1) # allocate stack space, R1 % 16 == 0 stdu 1, -32(1) # exit code is zero li 3, 0 # exit application bl ._exit nop ================================================ FILE: thirdparty/boost/asm/make_riscv64_sysv_elf_gas.S ================================================ /* Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************* * * * ------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * * ------------------------------------------------- * * | fs0 | fs1 | fs2 | fs3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * * ------------------------------------------------- * * | fs4 | fs5 | fs6 | fs7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * * ------------------------------------------------- * * | fs8 | fs9 | fs10 | fs11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| * * ------------------------------------------------- * * | s0 | s1 | s2 | s3 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| * * ------------------------------------------------- * * | s4 | s5 | s6 | s7 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 0xa0| 0xa4| 0xa8| 0xac| 0xb0| 0xb4| 0xb8| 0xbc| * * ------------------------------------------------- * * | s8 | s9 | s10 | s11 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | | | | | * * ------------------------------------------------- * * | 0xc0| 0xc4| 0xc8| 0xcc| | | | | * * ------------------------------------------------- * * | ra | pc | | | * * ------------------------------------------------- * * * *******************************************************/ .file "make_riscv64_sysv_elf_gas.S" .text .align 1 .global swoole_make_fcontext .type swoole_make_fcontext, %function swoole_make_fcontext: # shift address in a0 (allocated stack) to lower 16 byte boundary andi a0, a0, ~0xF # reserve space for context-data on context-stack addi a0, a0, -0xd0 # third arg of swoole_make_fcontext() == address of context-function # store address as a PC to jump in sd a2, 0xc8(a0) # save address of finish as return-address for context-function # will be entered after context-function returns (RA register) lla a4, finish sd a4, 0xc0(a0) ret // return pointer to context-data (a0) finish: # exit code is zero li a0, 0 # exit application tail _exit@plt .size swoole_make_fcontext,.-swoole_make_fcontext # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_sparc64_sysv_elf_gas.S ================================================ /* Copyright Martin Husemann 2013. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /******************************************************************* * * * ------------------------------------------------------------- * * | Offset (in 4 or 8 byte units) | Content | * * ------------------------------------------------------------- * * | 0 | %sp | * * ------------------------------------------------------------- * * | 1 | %pc | * * ------------------------------------------------------------- * * | 2 | %i7 (return address) | * * ------------------------------------------------------------- * * | 3 | %g1 | * * ------------------------------------------------------------- * * | 4 | %g2 | * * ------------------------------------------------------------- * * | 5 | %g3 | * * ------------------------------------------------------------- * * | 6 | %g6 | * * ------------------------------------------------------------- * * | 7 | %g7 | * * ------------------------------------------------------------- * * The local and in registers are stored on the stack. * *******************************************************************/ #define OFF(N) (8*(N)) #define CCFSZ 176 // C Compiler Frame Size #define BIAS (2048-1) // Stack offset for 64 bit programs #define FC_SZ 448 // sizeof(fcontext_t) #define FC_STK 384 // offsetof(fcontext_t, fc_stack) #define FC_FPU 0 // offsetof(fcontext_t, fc_fp) #define FC_FSR 264 // offsetof(fcontext_t, fc_fp.fp_fsr) #define FC_FPRS 256 // offsetof(fcontext_t, fc_fp.fp_fprs) #define FC_GREG 320 // offsetof(fcontext_t, fc_greg) #define BLOCK_SIZE 64 .register %g2,#ignore .register %g3,#ignore .register %g6,#ignore .text .globl swoole_make_fcontext .align 4 .type swoole_make_fcontext,@function // fcontext_t * // swoole_make_fcontext( void * sp, std::size_t size, void (* fn)( intptr_t) ) swoole_make_fcontext: save %sp, -CCFSZ, %sp // %i0 initial stack pointer // %i1 stack size limit // %i2 function pointer for context start function sub %i0, FC_SZ, %i4 // allocate fcontext_t at on the new stack and keep pointer as return value andn %i4, BLOCK_SIZE-1, %i5 // force block ops usable alignement and keep pointer to fcontext in %i5 stx %i0, [%i5+FC_STK+OFF(0)] // save fs_stack.sp stx %i1, [%i5+FC_STK+OFF(1)] // save fs_stack.size sub %i5, CCFSZ+BIAS, %o1 // leave space for one register window (and offset stack for 64bit) stx %o1, [%i5+FC_GREG+OFF(0)] // save new stack pointer stx %i2, [%i5+FC_GREG+OFF(1)] // save new %pc (function pointer) stx %g1, [%i5+FC_GREG+OFF(3)] stx %g2, [%i5+FC_GREG+OFF(4)] stx %g3, [%i5+FC_GREG+OFF(5)] stx %g6, [%i5+FC_GREG+OFF(6)] stx %g7, [%i5+FC_GREG+OFF(7)] // synthesize "return address": jump to finish 1: rd %pc, %i4 add %i4, finish-1b-8, %i4 stx %i4, [%i5+FC_GREG+OFF(2)] ret restore %g0, %i5, %o0 // return fcontext_t finish: mov %g0, %o0 call _exit nop .size swoole_make_fcontext,.-swoole_make_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_x86_64_sysv_elf_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /**************************************************************************************** * * * ---------------------------------------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * * | fc_mxcsr|fc_x87_cw| guard | R12 | R13 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * * | R14 | R15 | RBX | RBP | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ---------------------------------------------------------------------------------- * * | 0x40 | 0x44 | | * * ---------------------------------------------------------------------------------- * * | RIP | | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ # if defined __CET__ # include # define SWOOLE_SHSTK_ENABLED (__CET__ & 0x2) # define SWOOLE_CONTEXT_SHADOW_STACK (SWOOLE_SHSTK_ENABLED && SHADOW_STACK_SYSCALL) # else # define _CET_ENDBR # endif .file "make_x86_64_sysv_elf_gas.S" .text .globl swoole_make_fcontext .type swoole_make_fcontext,@function .align 16 swoole_make_fcontext: _CET_ENDBR #if SWOOLE_CONTEXT_SHADOW_STACK /* the new shadow stack pointer (SSP) */ movq -0x8(%rdi), %r9 #endif /* first arg of swoole_make_fcontext() == top of context-stack */ movq %rdi, %rax /* shift address in RAX to lower 16 byte boundary */ andq $-16, %rax /* reserve space for context-data on context-stack */ /* on context-function entry: (RSP -0x8) % 16 == 0 */ leaq -0x48(%rax), %rax /* third arg of swoole_make_fcontext() == address of context-function */ /* stored in RBX */ movq %rdx, 0x30(%rax) /* save MMX control- and status-word */ stmxcsr (%rax) /* save x87 control-word */ fnstcw 0x4(%rax) #if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) /* save stack guard */ movq %fs:0x28, %rcx /* read stack guard from TLS record */ movq %rcx, 0x8(%rsp) /* save stack guard */ #endif /* compute abs address of label trampoline */ leaq trampoline(%rip), %rcx /* save address of trampoline as return-address for context-function */ /* will be entered after calling jump_fcontext() first time */ movq %rcx, 0x40(%rax) /* compute abs address of label finish */ leaq finish(%rip), %rcx /* save address of finish as return-address for context-function */ /* will be entered after context-function returns */ movq %rcx, 0x38(%rax) #if SWOOLE_CONTEXT_SHADOW_STACK /* Populate the shadow stack and normal stack */ /* get original SSP */ rdsspq %r8 /* restore new shadow stack */ rstorssp -0x8(%r9) /* save the restore token on the original shadow stack */ saveprevssp /* push the address of "jmp trampoline" to the new shadow stack */ /* as well as the stack */ call 1f jmp trampoline 1: /* save address of "jmp trampoline" as return-address */ /* for context-function */ pop 0x38(%rax) /* Get the new SSP. */ rdsspq %r9 /* restore original shadow stack */ rstorssp -0x8(%r8) /* save the restore token on the new shadow stack. */ saveprevssp /* reserve space for the new SSP */ leaq -0x8(%rax), %rax /* save the new SSP to this fcontext */ movq %r9, (%rax) #endif ret /* return pointer to context-data */ trampoline: _CET_ENDBR /* store return address on stack */ /* fix stack alignment */ #if SWOOLE_CONTEXT_SHADOW_STACK /* save address of "jmp *%rbp" as return-address */ /* on stack and shadow stack */ call 2f jmp *%rbp 2: #else push %rbp #endif /* jump to context-function */ jmp *%rbx finish: _CET_ENDBR /* exit code is zero */ xorq %rdi, %rdi /* exit application */ call _exit@PLT hlt .size swoole_make_fcontext,.-swoole_make_fcontext /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits ================================================ FILE: thirdparty/boost/asm/make_x86_64_sysv_macho_gas.S ================================================ /* Copyright Oliver Kowalke 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ /**************************************************************************************** * * * ---------------------------------------------------------------------------------- * * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * * | R15 | RBX | RBP | RIP | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ .text .globl _swoole_make_fcontext .align 8 _swoole_make_fcontext: /* first arg of swoole_make_fcontext() == top of context-stack */ movq %rdi, %rax /* shift address in RAX to lower 16 byte boundary */ andq $-16, %rax /* reserve space for context-data on context-stack */ /* on context-function entry: (RSP -0x8) % 16 == 0 */ leaq -0x40(%rax), %rax /* third arg of swoole_make_fcontext() == address of context-function */ /* stored in RBX */ movq %rdx, 0x28(%rax) /* save MMX control- and status-word */ stmxcsr (%rax) /* save x87 control-word */ fnstcw 0x4(%rax) /* compute abs address of label trampoline */ leaq trampoline(%rip), %rcx /* save address of trampoline as return-address for context-function */ /* will be entered after calling jump_fcontext() first time */ movq %rcx, 0x38(%rax) /* compute abs address of label finish */ leaq finish(%rip), %rcx /* save address of finish as return-address for context-function */ /* will be entered after context-function returns */ movq %rcx, 0x30(%rax) ret /* return pointer to context-data */ trampoline: /* store return address on stack */ /* fix stack alignment */ push %rbp /* jump to context-function */ jmp *%rbx finish: /* exit code is zero */ xorq %rdi, %rdi /* exit application */ call __exit hlt ================================================ FILE: thirdparty/hiredis/CHANGELOG.md ================================================ ### 1.0.0 (unreleased) **BREAKING CHANGES**: * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`. * Change `redisReply.len` to `size_t`, as it denotes the the size of a string User code should compare this to `size_t` values as well. If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. ### 0.14.0 (2018-09-25) * Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) * Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) * Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) * Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) * Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) * Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) * Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) * Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) * Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) * Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) * Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) * Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) * Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) * Fix libevent leak (zfz [515228]) * Clean up GCC warning (Ichito Nagata [2ec774]) * Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) * Solaris compilation fix (Donald Whyte [41b07d]) * Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) * Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) * libuv use after free fix (Paul Scott [cbb956]) * Properly close socket fd on reconnect attempt (WSL [64d1ec]) * Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) * Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) * Update libevent (Chris Xin [386802]) * Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) * Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) * Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) * Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) * Compatibility fix for strerror_r (Tom Lee [bb1747]) * Properly detect integer parse/overflow errors (Justin Brewer [93421f]) * Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) * Catch a buffer overflow when formatting the error message * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 * Fix warnings, when compiled with -Wshadow * Make hiredis compile in Cygwin on Windows, now CI-tested **BREAKING CHANGES**: * Remove backwards compatibility macro's This removes the following old function aliases, use the new name now: | Old | New | | --------------------------- | ---------------------- | | redisReplyReaderCreate | redisReaderCreate | | redisReplyReaderCreate | redisReaderCreate | | redisReplyReaderFree | redisReaderFree | | redisReplyReaderFeed | redisReaderFeed | | redisReplyReaderGetReply | redisReaderGetReply | | redisReplyReaderSetPrivdata | redisReaderSetPrivdata | | redisReplyReaderGetObject | redisReaderGetObject | | redisReplyReaderGetError | redisReaderGetError | * The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, due to debugging other software. By renaming we avoid unintentional name clashes. Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. ### 0.13.3 (2015-09-16) * Revert "Clear `REDIS_CONNECTED` flag when connection is closed". * Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) If the `REDIS_CONNECTED` flag is cleared, the async onDisconnect callback function will never be called. This causes problems as the disconnect is never reported back to the user. ### 0.13.2 (2015-08-25) * Prevent crash on pending replies in async code (Thanks, @switch-st) * Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) * Add MacOS X addapter (Thanks, @dizzus) * Add Qt adapter (Thanks, Pietro Cerutti) * Add Ivykis adapter (Thanks, Gergely Nagy) All adapters are provided as is and are only tested where possible. ### 0.13.1 (2015-05-03) This is a bug fix release. The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. Other non-C99 code can now use hiredis as usual again. Sorry for the inconvenience. * Fix memory leak in async reply handling (Salvatore Sanfilippo) * Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) ### 0.13.0 (2015-04-16) This release adds a minimal Windows compatibility layer. The parser, standalone since v0.12.0, can now be compiled on Windows (and thus used in other client libraries as well) * Windows compatibility layer for parser code (tzickel) * Properly escape data printed to PKGCONF file (Dan Skorupski) * Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) * Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) ### 0.12.1 (2015-01-26) * Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location * Fix `make test` as 32 bit build on 64 bit platform ### 0.12.0 (2015-01-22) * Add optional KeepAlive support * Try again on EINTR errors * Add libuv adapter * Add IPv6 support * Remove possibility of multiple close on same fd * Add ability to bind source address on connect * Add redisConnectFd() and redisFreeKeepFd() * Fix getaddrinfo() memory leak * Free string if it is unused (fixes memory leak) * Improve redisAppendCommandArgv performance 2.5x * Add support for SO_REUSEADDR * Fix redisvFormatCommand format parsing * Add GLib 2.0 adapter * Refactor reading code into read.c * Fix errno error buffers to not clobber errors * Generate pkgconf during build * Silence _BSD_SOURCE warnings * Improve digit counting for multibulk creation ### 0.11.0 * Increase the maximum multi-bulk reply depth to 7. * Increase the read buffer size from 2k to 16k. * Use poll(2) instead of select(2) to support large fds (>= 1024). ### 0.10.1 * Makefile overhaul. Important to check out if you override one or more variables using environment variables or via arguments to the "make" tool. * Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements being created by the default reply object functions. * Issue #43: Don't crash in an asynchronous context when Redis returns an error reply after the connection has been made (this happens when the maximum number of connections is reached). ### 0.10.0 * See commit log. ================================================ FILE: thirdparty/hiredis/COPYING ================================================ Copyright (c) 2009-2011, Salvatore Sanfilippo Copyright (c) 2010-2011, Pieter Noordhuis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: thirdparty/hiredis/README.md ================================================ [![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) **This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** # HIREDIS Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. It is minimalistic because it just adds minimal support for the protocol, but at the same time it uses a high level printf-alike API in order to make it much higher level than otherwise suggested by its minimal code base and the lack of explicit bindings for every Redis command. Apart from supporting sending commands and receiving replies, it comes with a reply parser that is decoupled from the I/O layer. It is a stream parser designed for easy reusability, which can for instance be used in higher level language bindings for efficient reply parsing. Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis version >= 1.2.0. The library comes with multiple APIs. There is the *synchronous API*, the *asynchronous API* and the *reply parsing API*. ## Upgrading to `1.0.0` Version 1.0.0 marks a stable release of hiredis. It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. It also bundles the updated `sds` library, to sync up with upstream and Redis. For most applications a recompile against the new hiredis should be enough. For code changes see the [Changelog](CHANGELOG.md). ## Upgrading from `<0.9.0` Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing code using hiredis should not be a big pain. The key thing to keep in mind when upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to the stateless 0.0.1 that only has a file descriptor to work with. ## Synchronous API To consume the synchronous API, there are only a few function calls that need to be introduced: ```c redisContext *redisConnect(const char *ip, int port); void *redisCommand(redisContext *c, const char *format, ...); void freeReplyObject(void *reply); ``` ### Connecting The function `redisConnect` is used to create a so-called `redisContext`. The context is where Hiredis holds state for a connection. The `redisContext` struct has an integer `err` field that is non-zero when the connection is in an error state. The field `errstr` will contain a string with a description of the error. More information on errors can be found in the **Errors** section. After trying to connect to Redis using `redisConnect` you should check the `err` field to see if establishing the connection was successful: ```c redisContext *c = redisConnect("127.0.0.1", 6379); if (c == NULL || c->err) { if (c) { printf("Error: %s\n", c->errstr); // handle error } else { printf("Can't allocate redis context\n"); } } ``` *Note: A `redisContext` is not thread-safe.* ### Sending commands There are several ways to issue commands to Redis. The first that will be introduced is `redisCommand`. This function takes a format similar to printf. In the simplest form, it is used like this: ```c reply = redisCommand(context, "SET foo bar"); ``` The specifier `%s` interpolates a string in the command, and uses `strlen` to determine the length of the string: ```c reply = redisCommand(context, "SET foo %s", value); ``` When you need to pass binary safe strings in a command, the `%b` specifier can be used. Together with a pointer to the string, it requires a `size_t` length argument of the string: ```c reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); ``` Internally, Hiredis splits the command in different arguments and will convert it to the protocol used to communicate with Redis. One or more spaces separates arguments, so you can use the specifiers anywhere in an argument: ```c reply = redisCommand(context, "SET key:%s %s", myid, value); ``` ### Using replies The return value of `redisCommand` holds a reply when the command was successfully executed. When an error occurs, the return value is `NULL` and the `err` field in the context will be set (see section on **Errors**). Once an error is returned the context cannot be reused and you should set up a new connection. The standard replies that `redisCommand` are of the type `redisReply`. The `type` field in the `redisReply` should be used to test what kind of reply was received: * **`REDIS_REPLY_STATUS`**: * The command replied with a status reply. The status string can be accessed using `reply->str`. The length of this string can be accessed using `reply->len`. * **`REDIS_REPLY_ERROR`**: * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. * **`REDIS_REPLY_INTEGER`**: * The command replied with an integer. The integer value can be accessed using the `reply->integer` field of type `long long`. * **`REDIS_REPLY_NIL`**: * The command replied with a **nil** object. There is no data to access. * **`REDIS_REPLY_STRING`**: * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. The length of this string can be accessed using `reply->len`. * **`REDIS_REPLY_ARRAY`**: * A multi bulk reply. The number of elements in the multi bulk reply is stored in `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well and can be accessed via `reply->element[..index..]`. Redis may reply with nested arrays but this is fully supported. Replies should be freed using the `freeReplyObject()` function. Note that this function will take care of freeing sub-reply objects contained in arrays and nested arrays, so there is no need for the user to free the sub replies (it is actually harmful and will corrupt the memory). **Important:** the current version of hiredis (0.10.0) frees replies when the asynchronous API is used. This means you should not call `freeReplyObject` when you use this API. The reply is cleaned up by hiredis _after_ the callback returns. This behavior will probably change in future releases, so make sure to keep an eye on the changelog when upgrading (see issue #39). ### Cleaning up To disconnect and free the context the following function can be used: ```c void redisFree(redisContext *c); ``` This function immediately closes the socket and then frees the allocations done in creating the context. ### Sending commands (cont'd) Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. It has the following prototype: ```c void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); ``` It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments need to be binary safe, the entire array of lengths `argvlen` should be provided. The return value has the same semantic as `redisCommand`. ### Pipelining To explain how Hiredis supports pipelining in a blocking connection, there needs to be understanding of the internal execution flow. When any of the functions in the `redisCommand` family is called, Hiredis first formats the command according to the Redis protocol. The formatted command is then put in the output buffer of the context. This output buffer is dynamic, so it can hold any number of commands. After the command is put in the output buffer, `redisGetReply` is called. This function has the following two execution paths: 1. The input buffer is non-empty: * Try to parse a single reply from the input buffer and return it * If no reply could be parsed, continue at *2* 2. The input buffer is empty: * Write the **entire** output buffer to the socket * Read from the socket until a single reply could be parsed The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply is expected on the socket. To pipeline commands, the only things that needs to be done is filling up the output buffer. For this cause, two commands can be used that are identical to the `redisCommand` family, apart from not returning a reply: ```c void redisAppendCommand(redisContext *c, const char *format, ...); void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); ``` After calling either function one or more times, `redisGetReply` can be used to receive the subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where the latter means an error occurred while reading a reply. Just as with the other commands, the `err` field in the context can be used to find out what the cause of this error is. The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and a single call to `read(2)`): ```c redisReply *reply; redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"GET foo"); redisGetReply(context,&reply); // reply for SET freeReplyObject(reply); redisGetReply(context,&reply); // reply for GET freeReplyObject(reply); ``` This API can also be used to implement a blocking subscriber: ```c reply = redisCommand(context,"SUBSCRIBE foo"); freeReplyObject(reply); while(redisGetReply(context,&reply) == REDIS_OK) { // consume message freeReplyObject(reply); } ``` ### Errors When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is returned. The `err` field inside the context will be non-zero and set to one of the following constants: * **`REDIS_ERR_IO`**: There was an I/O error while creating the connection, trying to write to the socket or read from the socket. If you included `errno.h` in your application, you can use the global `errno` variable to find out what is wrong. * **`REDIS_ERR_EOF`**: The server closed the connection which resulted in an empty read. * **`REDIS_ERR_PROTOCOL`**: There was an error while parsing the protocol. * **`REDIS_ERR_OTHER`**: Any other error. Currently, it is only used when a specified hostname to connect to cannot be resolved. In every case, the `errstr` field in the context will be set to hold a string representation of the error. ## Asynchronous API Hiredis comes with an asynchronous API that works easily with any event library. Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) and [libevent](http://monkey.org/~provos/libevent/). ### Connecting The function `redisAsyncConnect` can be used to establish a non-blocking connection to Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field should be checked after creation to see if there were errors creating the connection. Because the connection that will be created is non-blocking, the kernel is not able to instantly return if the specified host and port is able to accept a connection. *Note: A `redisAsyncContext` is not thread-safe.* ```c redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { printf("Error: %s\n", c->errstr); // handle error } ``` The asynchronous context can hold a disconnect callback function that is called when the connection is disconnected (either because of an error or per user request). This function should have the following prototype: ```c void(const redisAsyncContext *c, int status); ``` On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` field in the context can be accessed to find out the cause of the error. The context object is always freed after the disconnect callback fired. When a reconnect is needed, the disconnect callback is a good point to do so. Setting the disconnect callback can only be done once per context. For subsequent calls it will return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: ```c int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); ``` ### Sending commands and their callbacks In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. Therefore, unlike the synchronous API, there is only a single way to send commands. Because commands are sent to Redis asynchronously, issuing a command requires a callback function that is called when the reply is received. Reply callbacks should have the following prototype: ```c void(redisAsyncContext *c, void *reply, void *privdata); ``` The `privdata` argument can be used to curry arbitrary data to the callback from the point where the command is initially queued for execution. The functions that can be used to issue commands in an asynchronous context are: ```c int redisAsyncCommand( redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); int redisAsyncCommandArgv( redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); ``` Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is returned on calls to the `redisAsyncCommand` family. If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only valid for the duration of the callback. All pending callbacks are called with a `NULL` reply when the context encountered an error. ### Disconnecting An asynchronous connection can be terminated using: ```c void redisAsyncDisconnect(redisAsyncContext *ac); ``` When this function is called, the connection is **not** immediately terminated. Instead, new commands are no longer accepted and the connection is only terminated when all pending commands have been written to the socket, their respective replies have been read and their respective callbacks have been executed. After this, the disconnection callback is executed with the `REDIS_OK` status and the context object is freed. ### Hooking it up to event library *X* There are a few hooks that need to be set on the context object after it is created. See the `adapters/` directory for bindings to *libev* and *libevent*. ## Reply parsing API Hiredis comes with a reply parsing API that makes it easy for writing higher level language bindings. The reply parsing API consists of the following functions: ```c redisReader *redisReaderCreate(void); void redisReaderFree(redisReader *reader); int redisReaderFeed(redisReader *reader, const char *buf, size_t len); int redisReaderGetReply(redisReader *reader, void **reply); ``` The same set of functions are used internally by hiredis when creating a normal Redis context, the above API just exposes it to the user for a direct usage. ### Usage The function `redisReaderCreate` creates a `redisReader` structure that holds a buffer with unparsed data and state for the protocol parser. Incoming data -- most likely from a socket -- can be placed in the internal buffer of the `redisReader` using `redisReaderFeed`. This function will make a copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed when `redisReaderGetReply` is called. This function returns an integer status and a reply object (as described above) via `void **reply`. The returned status can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went wrong (either a protocol error, or an out of memory error). The parser limits the level of nesting for multi bulk payloads to 7. If the multi bulk nesting level is higher than this, the parser returns an error. ### Customizing replies The function `redisReaderGetReply` creates `redisReply` and makes the function argument `reply` point to the created `redisReply` variable. For instance, if the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` will hold the status as a vanilla C string. However, the functions that are responsible for creating instances of the `redisReply` can be customized by setting the `fn` field on the `redisReader` struct. This should be done immediately after creating the `redisReader`. For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) uses customized reply object functions to create Ruby objects. ### Reader max buffer Both when using the Reader API directly or when using it indirectly via a normal Redis context, the redisReader structure uses a buffer in order to accumulate data from the server. Usually this buffer is destroyed when it is empty and is larger than 16 KiB in order to avoid wasting memory in unused buffers However when working with very big payloads destroying the buffer may slow down performances considerably, so it is possible to modify the max size of an idle buffer changing the value of the `maxbuf` field of the reader structure to the desired value. The special value of 0 means that there is no maximum value for an idle buffer, so the buffer will never get freed. For instance if you have a normal Redis context you can set the maximum idle buffer to zero (unlimited) just with: ```c context->reader->maxbuf = 0; ``` This should be done only in order to maximize performances when working with large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again as soon as possible in order to prevent allocation of useless memory. ## AUTHORS Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and Jan-Erik Rediger (janerik at fnordig dot com) ================================================ FILE: thirdparty/hiredis/alloc.c ================================================ /* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "fmacros.h" #include "alloc.h" #include #include hiredisAllocFuncs hiredisAllocFns = { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; /* Override hiredis' allocators with ones supplied by the user */ hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) { hiredisAllocFuncs orig = hiredisAllocFns; hiredisAllocFns = *override; return orig; } /* Reset allocators to use libc defaults */ void hiredisResetAllocators(void) { hiredisAllocFns = (hiredisAllocFuncs) { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; } #ifdef _WIN32 void *hi_malloc(size_t size) { return hiredisAllocFns.mallocFn(size); } void *hi_calloc(size_t nmemb, size_t size) { return hiredisAllocFns.callocFn(nmemb, size); } void *hi_realloc(void *ptr, size_t size) { return hiredisAllocFns.reallocFn(ptr, size); } char *hi_strdup(const char *str) { return hiredisAllocFns.strdupFn(str); } void hi_free(void *ptr) { hiredisAllocFns.freeFn(ptr); } #endif ================================================ FILE: thirdparty/hiredis/alloc.h ================================================ /* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef HIREDIS_ALLOC_H #define HIREDIS_ALLOC_H #include /* for size_t */ #ifdef __cplusplus extern "C" { #endif /* Structure pointing to our actually configured allocators */ typedef struct hiredisAllocFuncs { void *(*mallocFn)(size_t); void *(*callocFn)(size_t,size_t); void *(*reallocFn)(void*,size_t); char *(*strdupFn)(const char*); void (*freeFn)(void*); } hiredisAllocFuncs; hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha); void hiredisResetAllocators(void); #ifndef _WIN32 /* Hiredis' configured allocator function pointer struct */ extern hiredisAllocFuncs hiredisAllocFns; static inline void *hi_malloc(size_t size) { return hiredisAllocFns.mallocFn(size); } static inline void *hi_calloc(size_t nmemb, size_t size) { return hiredisAllocFns.callocFn(nmemb, size); } static inline void *hi_realloc(void *ptr, size_t size) { return hiredisAllocFns.reallocFn(ptr, size); } static inline char *hi_strdup(const char *str) { return hiredisAllocFns.strdupFn(str); } static inline void hi_free(void *ptr) { hiredisAllocFns.freeFn(ptr); } #else void *hi_malloc(size_t size); void *hi_calloc(size_t nmemb, size_t size); void *hi_realloc(void *ptr, size_t size); char *hi_strdup(const char *str); void hi_free(void *ptr); #endif #ifdef __cplusplus } #endif #endif /* HIREDIS_ALLOC_H */ ================================================ FILE: thirdparty/hiredis/fmacros.h ================================================ #ifndef __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L #if defined(__APPLE__) && defined(__MACH__) /* Enable TCP_KEEPALIVE */ #define _DARWIN_C_SOURCE #endif #endif ================================================ FILE: thirdparty/hiredis/hiredis.c ================================================ /* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "fmacros.h" #include #include #include #include #include #include "hiredis.h" #include "net.h" #include "sds.h" #define SW_HOOK_POLL_FAKE #include "swoole_socket_hook.h" extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout); extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); static redisContextFuncs redisContextDefaultFuncs = { .free_privctx = NULL, .read_ = redisNetRead, .write_ = redisNetWrite }; static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createArrayObject(const redisReadTask *task, size_t elements); static void *createIntegerObject(const redisReadTask *task, long long value); static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); static void *createNilObject(const redisReadTask *task); static void *createBoolObject(const redisReadTask *task, int bval); /* Default set of functions to build the reply. Keep in mind that such a * function returning NULL is interpreted as OOM. */ static redisReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, createIntegerObject, createDoubleObject, createNilObject, createBoolObject, freeReplyObject }; /* Create a reply object */ static redisReply *createReplyObject(int type) { redisReply *r = hi_calloc(1,sizeof(*r)); if (r == NULL) return NULL; r->type = type; return r; } /* Free a reply object */ void freeReplyObject(void *reply) { redisReply *r = reply; size_t j; if (r == NULL) return; switch(r->type) { case REDIS_REPLY_INTEGER: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: case REDIS_REPLY_SET: case REDIS_REPLY_PUSH: if (r->element != NULL) { for (j = 0; j < r->elements; j++) freeReplyObject(r->element[j]); hi_free(r->element); } break; case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_VERB: case REDIS_REPLY_BIGNUM: hi_free(r->str); break; } hi_free(r); } static void *createStringObject(const redisReadTask *task, char *str, size_t len) { redisReply *r, *parent; char *buf; r = createReplyObject(task->type); if (r == NULL) return NULL; assert(task->type == REDIS_REPLY_ERROR || task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STRING || task->type == REDIS_REPLY_VERB || task->type == REDIS_REPLY_BIGNUM); /* Copy string value */ if (task->type == REDIS_REPLY_VERB) { buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ if (buf == NULL) goto oom; memcpy(r->vtype,str,3); r->vtype[3] = '\0'; memcpy(buf,str+4,len-4); buf[len-4] = '\0'; r->len = len - 4; } else { buf = hi_malloc(len+1); if (buf == NULL) goto oom; memcpy(buf,str,len); buf[len] = '\0'; r->len = len; } r->str = buf; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; oom: freeReplyObject(r); return NULL; } static void *createArrayObject(const redisReadTask *task, size_t elements) { redisReply *r, *parent; r = createReplyObject(task->type); if (r == NULL) return NULL; if (elements > 0) { if (SIZE_MAX / sizeof(redisReply*) < elements) return NULL; /* Don't overflow */ r->element = hi_calloc(elements,sizeof(redisReply*)); if (r->element == NULL) { freeReplyObject(r); return NULL; } } r->elements = elements; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createIntegerObject(const redisReadTask *task, long long value) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_INTEGER); if (r == NULL) return NULL; r->integer = value; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_DOUBLE); if (r == NULL) return NULL; r->dval = value; r->str = hi_malloc(len+1); if (r->str == NULL) { freeReplyObject(r); return NULL; } /* The double reply also has the original protocol string representing a * double as a null terminated string. This way the caller does not need * to format back for string conversion, especially since Redis does efforts * to make the string more human readable avoiding the calssical double * decimal string conversion artifacts. */ memcpy(r->str, str, len); r->str[len] = '\0'; r->len = len; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createNilObject(const redisReadTask *task) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_NIL); if (r == NULL) return NULL; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createBoolObject(const redisReadTask *task, int bval) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_BOOL); if (r == NULL) return NULL; r->integer = bval != 0; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } /* Return the number of digits of 'v' when converted to string in radix 10. * Implementation borrowed from link in redis/src/util.c:string2ll(). */ static uint32_t countDigits(uint64_t v) { uint32_t result = 1; for (;;) { if (v < 10) return result; if (v < 100) return result + 1; if (v < 1000) return result + 2; if (v < 10000) return result + 3; v /= 10000U; result += 4; } return result; } /* Helper that calculates the bulk length given a certain string length. */ static size_t bulklen(size_t len) { return 1+countDigits(len)+2+len+2; } int redisvFormatCommand(char **target, const char *format, va_list ap) { const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ sds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ char **curargv = NULL, **newargv = NULL; int argc = 0; int totlen = 0; int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ int j; /* Abort if there is not target to set */ if (target == NULL) return -1; /* Build the command string accordingly to protocol */ curarg = sdsempty(); if (curarg == NULL) return -1; while(*c != '\0') { if (*c != '%' || c[1] == '\0') { if (*c == ' ') { if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(sdslen(curarg)); /* curarg is put in argv so it can be overwritten. */ curarg = sdsempty(); if (curarg == NULL) goto memory_err; touched = 0; } } else { newarg = sdscatlen(curarg,c,1); if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; } } else { char *arg; size_t size; /* Set newarg so it can be checked even if it is not touched. */ newarg = curarg; switch(c[1]) { case 's': arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) newarg = sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) newarg = sdscatlen(curarg,arg,size); break; case '%': newarg = sdscat(curarg,"%"); break; default: /* Try to detect printf format */ { static const char intfmts[] = "diouxX"; static const char flags[] = "#0-+ "; char _format[16]; const char *_p = c+1; size_t _l = 0; va_list _cpy; /* Flags */ while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; /* Field width */ while (*_p != '\0' && isdigit(*_p)) _p++; /* Precision */ if (*_p == '.') { _p++; while (*_p != '\0' && isdigit(*_p)) _p++; } /* Copy va_list before consuming with va_arg */ va_copy(_cpy,ap); /* Integer conversion (without modifiers) */ if (strchr(intfmts,*_p) != NULL) { va_arg(ap,int); goto fmt_valid; } /* Double conversion (without modifiers) */ if (strchr("eEfFgGaA",*_p) != NULL) { va_arg(ap,double); goto fmt_valid; } /* Size: char */ if (_p[0] == 'h' && _p[1] == 'h') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* char gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: short */ if (_p[0] == 'h') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* short gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: long long */ if (_p[0] == 'l' && _p[1] == 'l') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long long); goto fmt_valid; } goto fmt_invalid; } /* Size: long */ if (_p[0] == 'l') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long); goto fmt_valid; } goto fmt_invalid; } fmt_invalid: va_end(_cpy); goto format_err; fmt_valid: _l = (_p+1)-c; if (_l < sizeof(_format)-2) { memcpy(_format,c,_l); _format[_l] = '\0'; newarg = sdscatvprintf(curarg,_format,_cpy); /* Update current position (note: outer blocks * increment c twice so compensate here) */ c = _p-1; } va_end(_cpy); break; } } if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; c++; } c++; } /* Add the last argument if needed */ if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(sdslen(curarg)); } else { sdsfree(curarg); } /* Clear curarg because it was put in curargv or was free'd. */ curarg = NULL; /* Add bytes needed to hold multi bulk count */ totlen += 1+countDigits(argc)+2; /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) goto memory_err; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); pos += sdslen(curargv[j]); sdsfree(curargv[j]); cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; hi_free(curargv); *target = cmd; return totlen; format_err: error_type = -2; goto cleanup; memory_err: error_type = -1; goto cleanup; cleanup: if (curargv) { while(argc--) sdsfree(curargv[argc]); hi_free(curargv); } sdsfree(curarg); hi_free(cmd); return error_type; } /* Format a command according to the Redis protocol. This function * takes a format similar to printf: * * %s represents a C null terminated string you want to interpolate * %b represents a binary safe string * * When using %b you need to provide both the pointer to the string * and the length in bytes as a size_t. Examples: * * len = redisFormatCommand(target, "GET %s", mykey); * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); */ int redisFormatCommand(char **target, const char *format, ...) { va_list ap; int len; va_start(ap,format); len = redisvFormatCommand(target,format,ap); va_end(ap); /* The API says "-1" means bad result, but we now also return "-2" in some * cases. Force the return value to always be -1. */ if (len < 0) len = -1; return len; } /* Format a command according to the Redis protocol using an sds string and * sdscatfmt for the processing of arguments. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, const size_t *argvlen) { sds cmd, aux; unsigned long long totlen; int j; size_t len; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate our total size */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Use an SDS string for command construction */ cmd = sdsempty(); if (cmd == NULL) return -1; /* We already know how much storage we need */ aux = sdsMakeRoomFor(cmd, totlen); if (aux == NULL) { sdsfree(cmd); return -1; } cmd = aux; /* Construct command */ cmd = sdscatfmt(cmd, "*%i\r\n", argc); for (j=0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); cmd = sdscatfmt(cmd, "$%u\r\n", len); cmd = sdscatlen(cmd, argv[j], len); cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } assert(sdslen(cmd)==totlen); *target = cmd; return totlen; } void redisFreeSdsCommand(sds cmd) { sdsfree(cmd); } /* Format a command according to the Redis protocol. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { char *cmd = NULL; /* final command */ int pos; /* position in final command */ size_t len; int totlen, j; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate number of bytes needed for the command */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) return -1; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); pos += sprintf(cmd+pos,"$%zu\r\n",len); memcpy(cmd+pos,argv[j],len); pos += len; cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; *target = cmd; return totlen; } void redisFreeCommand(char *cmd) { hi_free(cmd); } void __redisSetError(redisContext *c, int type, const char *str) { size_t len; c->err = type; if (str != NULL) { len = strlen(str); len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); memcpy(c->errstr,str,len); c->errstr[len] = '\0'; } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); strerror_r(errno, c->errstr, sizeof(c->errstr)); } } redisReader *redisReaderCreate(void) { return redisReaderCreateWithFunctions(&defaultFunctions); } static void redisPushAutoFree(void *privdata, void *reply) { (void)privdata; freeReplyObject(reply); } static redisContext *redisContextInit(void) { redisContext *c; c = hi_calloc(1, sizeof(*c)); if (c == NULL) return NULL; c->funcs = &redisContextDefaultFuncs; c->obuf = sdsempty(); c->reader = redisReaderCreate(); c->fd = REDIS_INVALID_FD; if (c->obuf == NULL || c->reader == NULL) { redisFree(c); return NULL; } return c; } void redisFree(redisContext *c) { if (c == NULL) return; redisNetClose(c); sdsfree(c->obuf); redisReaderFree(c->reader); hi_free(c->tcp.host); hi_free(c->tcp.source_addr); hi_free(c->unix_sock.path); hi_free(c->connect_timeout); hi_free(c->command_timeout); hi_free(c->saddr); if (c->privdata && c->free_privdata) c->free_privdata(c->privdata); if (c->funcs->free_privctx) c->funcs->free_privctx(c->privctx); memset(c, 0xff, sizeof(*c)); hi_free(c); } redisFD redisFreeKeepFd(redisContext *c) { redisFD fd = c->fd; c->fd = REDIS_INVALID_FD; redisFree(c); return fd; } int redisReconnect(redisContext *c) { c->err = 0; memset(c->errstr, '\0', strlen(c->errstr)); if (c->privctx && c->funcs->free_privctx) { c->funcs->free_privctx(c->privctx); c->privctx = NULL; } redisNetClose(c); sdsfree(c->obuf); redisReaderFree(c->reader); c->obuf = sdsempty(); c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } int ret = REDIS_ERR; if (c->connection_type == REDIS_CONN_TCP) { ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, c->connect_timeout, c->tcp.source_addr); } else if (c->connection_type == REDIS_CONN_UNIX) { ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout); } else { /* Something bad happened here and shouldn't have. There isn't enough information in the context to reconnect. */ __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); ret = REDIS_ERR; } if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { redisContextSetTimeout(c, *c->command_timeout); } return ret; } redisContext *redisConnectWithOptions(const redisOptions *options) { redisContext *c = redisContextInit(); if (c == NULL) { return NULL; } if (!(options->options & REDIS_OPT_NONBLOCK)) { c->flags |= REDIS_BLOCK; } if (options->options & REDIS_OPT_REUSEADDR) { c->flags |= REDIS_REUSEADDR; } if (options->options & REDIS_OPT_NOAUTOFREE) { c->flags |= REDIS_NO_AUTO_FREE; } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ if (options->push_cb != NULL) redisSetPushCallback(c, options->push_cb); else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE)) redisSetPushCallback(c, redisPushAutoFree); c->privdata = options->privdata; c->free_privdata = options->free_privdata; if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK || redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return c; } if (options->type == REDIS_CONN_TCP) { redisContextConnectBindTcp(c, options->endpoint.tcp.ip, options->endpoint.tcp.port, options->connect_timeout, options->endpoint.tcp.source_addr); } else if (options->type == REDIS_CONN_UNIX) { redisContextConnectUnix(c, options->endpoint.unix_socket, options->connect_timeout); } else if (options->type == REDIS_CONN_USERFD) { c->fd = options->endpoint.fd; c->flags |= REDIS_CONNECTED; } else { // Unknown type - FIXME - FREE return NULL; } if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { redisContextSetTimeout(c, *options->command_timeout); } return c; } /* Connect to a Redis instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redisContext *redisConnect(const char *ip, int port) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); return redisConnectWithOptions(&options); } redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.connect_timeout = &tv; return redisConnectWithOptions(&options); } redisContext *redisConnectNonBlock(const char *ip, int port) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; return redisConnectWithOptions(&options); } redisContext *redisConnectUnix(const char *path) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); return redisConnectWithOptions(&options); } redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); options.connect_timeout = &tv; return redisConnectWithOptions(&options); } redisContext *redisConnectUnixNonBlock(const char *path) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectFd(redisFD fd) { redisOptions options = {0}; options.type = REDIS_CONN_USERFD; options.endpoint.fd = fd; return redisConnectWithOptions(&options); } /* Set read/write timeout on a blocking socket. */ int redisSetTimeout(redisContext *c, const struct timeval tv) { if (c->flags & REDIS_BLOCK) return redisContextSetTimeout(c,tv); return REDIS_ERR; } /* Enable connection KeepAlive. */ int redisEnableKeepAlive(redisContext *c) { if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) return REDIS_ERR; return REDIS_OK; } /* Set a user provided RESP3 PUSH handler and return any old one set. */ redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) { redisPushFn *old = c->push_cb; c->push_cb = fn; return old; } /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * * After this function is called, you may use redisGetReplyFromReader to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; int nread; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; nread = c->funcs->read_(c, buf, sizeof(buf)); if (nread < 0) { return REDIS_ERR; } if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * successfully written to the socket. When the buffer is empty after the * write operation, "done" is set to 1 (if given). * * Returns REDIS_ERR if an error occurred trying to write and sets * c->errstr to hold the appropriate error string. */ int redisBufferWrite(redisContext *c, int *done) { /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (sdslen(c->obuf) > 0) { ssize_t nwritten = c->funcs->write_(c); if (nwritten < 0) { return REDIS_ERR; } else if (nwritten > 0) { if (nwritten == (ssize_t)sdslen(c->obuf)) { sdsfree(c->obuf); c->obuf = sdsempty(); if (c->obuf == NULL) goto oom; } else { if (sdsrange(c->obuf,nwritten,-1) < 0) goto oom; } } } if (done != NULL) *done = (sdslen(c->obuf) == 0); return REDIS_OK; oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } /* Internal helper that returns 1 if the reply was a RESP3 PUSH * message and we handled it with a user-provided callback. */ static int redisHandledPushReply(redisContext *c, void *reply) { if (reply && c->push_cb && redisIsPushReply(reply)) { c->push_cb(c->privdata, reply); return 1; } return 0; } /* Get a reply from our reader or set an error in the context. */ int redisGetReplyFromReader(redisContext *c, void **reply) { if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } /* Internal helper to get the next reply from our reader while handling * any PUSH messages we encounter along the way. This is separate from * redisGetReplyFromReader so as to not change its behavior. */ static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { do { if (redisGetReplyFromReader(c, reply) == REDIS_ERR) return REDIS_ERR; } while (redisHandledPushReply(c, *reply)); return REDIS_OK; } int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && (c->flags & REDIS_BLOCK)) { /* Write until done */ do { if (redisBufferWrite(c,&wdone) == REDIS_ERR) return REDIS_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; } while (aux == NULL); } /* Set reply or free it if we were passed NULL */ if (reply != NULL) { *reply = aux; } else { freeReplyObject(aux); } return REDIS_OK; } /* Helper function for the redisAppendCommand* family of functions. * * Write a formatted command to the output buffer. When this family * is used, you need to call redisGetReply yourself to retrieve * the reply (or replies in pub/sub). */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { sds newbuf; newbuf = sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } c->obuf = newbuf; return REDIS_OK; } int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { return REDIS_ERR; } return REDIS_OK; } int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { char *cmd; int len; len = redisvFormatCommand(&cmd,format,ap); if (len == -1) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } else if (len == -2) { __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); return REDIS_ERR; } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { hi_free(cmd); return REDIS_ERR; } hi_free(cmd); return REDIS_OK; } int redisAppendCommand(redisContext *c, const char *format, ...) { va_list ap; int ret; va_start(ap,format); ret = redisvAppendCommand(c,format,ap); va_end(ap); return ret; } int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { sds cmd; int len; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { sdsfree(cmd); return REDIS_ERR; } sdsfree(cmd); return REDIS_OK; } /* Helper function for the redisCommand* family of functions. * * Write a formatted command to the output buffer. If the given context is * blocking, immediately read the reply into the "reply" pointer. When the * context is non-blocking, the "reply" pointer will not be used and the * command is simply appended to the write buffer. * * Returns the reply when a reply was successfully retrieved. Returns NULL * otherwise. When NULL is returned in a blocking context, the error field * in the context will be set. */ static void *__redisBlockForReply(redisContext *c) { void *reply; if (c->flags & REDIS_BLOCK) { if (redisGetReply(c,&reply) != REDIS_OK) return NULL; return reply; } return NULL; } void *redisvCommand(redisContext *c, const char *format, va_list ap) { if (redisvAppendCommand(c,format,ap) != REDIS_OK) return NULL; return __redisBlockForReply(c); } void *redisCommand(redisContext *c, const char *format, ...) { va_list ap; va_start(ap,format); void *reply = redisvCommand(c,format,ap); va_end(ap); return reply; } void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) return NULL; return __redisBlockForReply(c); } ================================================ FILE: thirdparty/hiredis/hiredis.h ================================================ /* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __HIREDIS_H #define __HIREDIS_H #include "read.h" #include /* for va_list */ #ifndef _MSC_VER #include /* for struct timeval */ #else struct timeval; /* forward declaration */ typedef long long ssize_t; #endif #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ #include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 1 #define HIREDIS_MINOR 0 #define HIREDIS_PATCH 1 #define HIREDIS_SONAME 1.0.1-dev /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ #define REDIS_BLOCK 0x1 /* Connection may be disconnected before being free'd. The second bit * in the flags field is set when the context is connected. */ #define REDIS_CONNECTED 0x2 /* The async API might try to disconnect cleanly and flush the output * buffer and read all subsequent replies before disconnecting. * This flag means no new commands can come in and the connection * should be terminated once all replies have been read. */ #define REDIS_DISCONNECTING 0x4 /* Flag specific to the async API which means that the context should be clean * up as soon as possible. */ #define REDIS_FREEING 0x8 /* Flag that is set when an async callback is executed. */ #define REDIS_IN_CALLBACK 0x10 /* Flag that is set when the async context has one or more subscriptions. */ #define REDIS_SUBSCRIBED 0x20 /* Flag that is set when monitor mode is active */ #define REDIS_MONITORING 0x40 /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDIS_NO_AUTO_FREE 0x200 #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and * SO_REUSEADDR is being used. */ #define REDIS_CONNECT_RETRIES 10 /* Forward declarations for structs defined elsewhere */ struct redisAsyncContext; struct redisContext; /* RESP3 push helpers and callback prototypes */ #define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH) typedef void (redisPushFn)(void *, void *); typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *); #ifdef __cplusplus extern "C" { #endif /* This is the reply object returned by redisCommand() */ typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ double dval; /* The double when type is REDIS_REPLY_DOUBLE */ size_t len; /* Length of string */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval), and REDIS_REPLY_BIGNUM. */ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null terminated 3 character content type, such as "txt". */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ } redisReply; redisReader *redisReaderCreate(void); /* Function to free the reply objects hiredis returns by default. */ void freeReplyObject(void *reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); void redisFreeCommand(char *cmd); void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, REDIS_CONN_UNIX, REDIS_CONN_USERFD }; struct redisSsl; #define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_REUSEADDR 0x02 /** * Don't automatically free the async object on a connection failure, * or other implicit conditions. Only free on an explicit call to disconnect() or free() */ #define REDIS_OPT_NOAUTOFREE 0x04 /* Don't automatically intercept and free RESP3 PUSH replies. */ #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 /* In Unix systems a file descriptor is a regular signed int, with -1 * representing an invalid descriptor. In Windows it is a SOCKET * (32- or 64-bit unsigned integer depending on the architecture), where * all bits set (~0) is INVALID_SOCKET. */ #ifndef _WIN32 typedef int redisFD; #define REDIS_INVALID_FD -1 #else #ifdef _WIN64 typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ #else typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ #endif #define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ #endif typedef struct { /* * the type of connection to use. This also indicates which * `endpoint` member field to use */ int type; /* bit field of REDIS_OPT_xxx */ int options; /* timeout value for connect operation. If NULL, no timeout is used */ const struct timeval *connect_timeout; /* timeout value for commands. If NULL, no timeout is used. This can be * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */ const struct timeval *command_timeout; union { /** use this field for tcp/ip connections */ struct { const char *source_addr; const char *ip; int port; } tcp; /** use this field for unix domain sockets */ const char *unix_socket; /** * use this field to have hiredis operate an already-open * file descriptor */ redisFD fd; } endpoint; /* Optional user defined data/destructor */ void *privdata; void (*free_privdata)(void *); /* A user defined PUSH message callback */ redisPushFn *push_cb; redisAsyncPushFn *async_push_cb; } redisOptions; /** * Helper macros to initialize options to their specified fields. */ #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ (opts)->type = REDIS_CONN_TCP; \ (opts)->endpoint.tcp.ip = ip_; \ (opts)->endpoint.tcp.port = port_; #define REDIS_OPTIONS_SET_UNIX(opts, path) \ (opts)->type = REDIS_CONN_UNIX; \ (opts)->endpoint.unix_socket = path; #define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \ (opts)->privdata = data; \ (opts)->free_privdata = dtor; \ typedef struct redisContextFuncs { void (*free_privctx)(void *); ssize_t (*read_)(struct redisContext *, char *, size_t); ssize_t (*write_)(struct redisContext *); } redisContextFuncs; /* Context for a connection to Redis */ typedef struct redisContext { const redisContextFuncs *funcs; /* Function table */ int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ redisFD fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ enum redisConnectionType connection_type; struct timeval *connect_timeout; struct timeval *command_timeout; struct { char *host; char *source_addr; int port; } tcp; struct { char *path; } unix_sock; /* For non-blocking connect */ struct sockaddr *saddr; size_t addrlen; /* Optional data and corresponding destructor users can use to provide * context to a given redisContext. Not used by hiredis. */ void *privdata; void (*free_privdata)(void *); /* Internal context pointer presently used by hiredis to manage * SSL connections. */ void *privctx; /* An optional RESP3 PUSH handler */ redisPushFn *push_cb; } redisContext; redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectFd(redisFD fd); /** * Reconnect the given context using the saved information. * * This re-uses the exact same connect options as in the initial connection. * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. */ int redisReconnect(redisContext *c); redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn); int redisSetTimeout(redisContext *c, const struct timeval tv); int redisEnableKeepAlive(redisContext *c); void redisFree(redisContext *c); redisFD redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); /* In a blocking context, this function first checks if there are unconsumed * replies to return and returns one if so. Otherwise, it flushes the output * buffer to the socket and reads until it has a reply. In a non-blocking * context, it will return unconsumed replies until there are no more. */ int redisGetReply(redisContext *c, void **reply); int redisGetReplyFromReader(redisContext *c, void **reply); /* Write a formatted command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redisvAppendCommand(redisContext *c, const char *format, va_list ap); int redisAppendCommand(redisContext *c, const char *format, ...); int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); /* Issue a command to Redis. In a blocking context, it is identical to calling * redisAppendCommand, followed by redisGetReply. The function will return * NULL if there was an error in performing the request, otherwise it will * return the reply. In a non-blocking context, it is identical to calling * only redisAppendCommand and will always return NULL. */ void *redisvCommand(redisContext *c, const char *format, va_list ap); void *redisCommand(redisContext *c, const char *format, ...); void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); #ifdef __cplusplus } #endif #endif ================================================ FILE: thirdparty/hiredis/net.c ================================================ /* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "fmacros.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "net.h" #include "sds.h" #include "swoole_socket_hook.h" /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); void redisNetClose(redisContext *c) { if (c && c->fd != REDIS_INVALID_FD) { close(c->fd); c->fd = REDIS_INVALID_FD; } } ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { ssize_t nread = recv(c->fd, buf, bufcap, 0); if (nread == -1) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ return 0; } #if 0 else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { /* especially in windows */ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); return -1; } #endif else { __redisSetError(c, REDIS_ERR_IO, NULL); return -1; } } else if (nread == 0) { __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); return -1; } else { return nread; } } ssize_t redisNetWrite(redisContext *c) { ssize_t nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); if (nwritten < 0) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { __redisSetError(c, REDIS_ERR_IO, NULL); return -1; } } return nwritten; } static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { int errorno = errno; /* snprintf() may change errno */ char buf[128] = { 0 }; size_t len = 0; if (prefix != NULL) len = snprintf(buf,sizeof(buf),"%s: ",prefix); strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); __redisSetError(c,type,buf); } static int redisSetReuseAddr(redisContext *c) { int on = 1; if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } static int redisCreateSocket(redisContext *c, int type) { redisFD s; if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } c->fd = s; if (type == AF_INET) { if (redisSetReuseAddr(c) == REDIS_ERR) { return REDIS_ERR; } } return REDIS_OK; } static int redisSetBlocking(redisContext *c, int blocking) { #if 0 #ifndef _WIN32 int flags; /* Set the socket nonblocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); redisNetClose(c); return REDIS_ERR; } if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (fcntl(c->fd, F_SETFL, flags) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); redisNetClose(c); return REDIS_ERR; } #else u_long mode = blocking ? 0 : 1; if (ioctl(c->fd, FIONBIO, &mode) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); redisNetClose(c); return REDIS_ERR; } #endif /* _WIN32 */ #endif return REDIS_OK; } int redisKeepAlive(redisContext *c, int interval) { int val = 1; redisFD fd = c->fd; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = interval; #if defined(__APPLE__) && defined(__MACH__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } #else #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = interval/3; if (val == 0) val = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = 3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } #endif #endif return REDIS_OK; } int redisSetTcpNoDelay(redisContext *c) { int yes = 1; if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) static int redisContextTimeoutMsec(redisContext *c, long *result) { const struct timeval *timeout = c->connect_timeout; long msec = -1; /* Only use timeout when not NULL. */ if (timeout != NULL) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { *result = msec; return REDIS_ERR; } msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); if (msec < 0 || msec > INT_MAX) { msec = INT_MAX; } } *result = msec; return REDIS_OK; } static int redisContextWaitReady(redisContext *c, long msec) { struct pollfd wfd[1]; wfd[0].fd = c->fd; wfd[0].events = POLLOUT; if (errno == EINPROGRESS) { int res; if ((res = poll(wfd, 1, msec)) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); redisNetClose(c); return REDIS_ERR; } else if (res == 0) { errno = ETIMEDOUT; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { redisCheckSocketError(c); return REDIS_ERR; } return REDIS_OK; } __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } int redisCheckConnectDone(redisContext *c, int *completed) { int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); if (rc == 0) { *completed = 1; return REDIS_OK; } switch (errno) { case EISCONN: *completed = 1; return REDIS_OK; case EALREADY: case EINPROGRESS: case EWOULDBLOCK: *completed = 0; return REDIS_OK; default: return REDIS_ERR; } } int redisCheckSocketError(redisContext *c) { int err = 0, errno_saved = errno; socklen_t errlen = sizeof(err); if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); return REDIS_ERR; } if (err == 0) { err = errno_saved; } if (err) { errno = err; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } return REDIS_OK; } int redisContextSetTimeout(redisContext *c, const struct timeval tv) { #if 0 const void *to_ptr = &tv; size_t to_sz = sizeof(tv); if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; } if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); return REDIS_ERR; } #else double timeout = tv.tv_sec ; timeout += (double) tv.tv_usec / 1000 / 1000; if (swoole_coroutine_socket_set_timeout(c->fd, SO_RCVTIMEO, timeout) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; } if (swoole_coroutine_socket_set_timeout(c->fd, SO_SNDTIMEO, timeout) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; } #endif return REDIS_OK; } int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->connect_timeout == timeout) return REDIS_OK; /* Allocate context timeval if we need to */ if (c->connect_timeout == NULL) { c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout)); if (c->connect_timeout == NULL) return REDIS_ERR; } memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout)); return REDIS_OK; } int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->command_timeout == timeout) return REDIS_OK; /* Allocate context timeval if we need to */ if (c->command_timeout == NULL) { c->command_timeout = hi_malloc(sizeof(*c->command_timeout)); if (c->command_timeout == NULL) return REDIS_ERR; } memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout)); return REDIS_OK; } static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { redisFD s; int rv, n; char _port[6]; /* strlen("65535"); */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); int reuseaddr = (c->flags & REDIS_REUSEADDR); int reuses = 0; long timeout_msec = -1; servinfo = NULL; c->connection_type = REDIS_CONN_TCP; c->tcp.port = port; /* We need to take possession of the passed parameters * to make them reusable for a reconnect. * We also carefully check we don't free data we already own, * as in the case of the reconnect method. * * This is a bit ugly, but atleast it works and doesn't leak memory. **/ if (c->tcp.host != addr) { hi_free(c->tcp.host); c->tcp.host = hi_strdup(addr); if (c->tcp.host == NULL) goto oom; } if (timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); goto error; } if (source_addr == NULL) { hi_free(c->tcp.source_addr); c->tcp.source_addr = NULL; } else if (c->tcp.source_addr != source_addr) { hi_free(c->tcp.source_addr); c->tcp.source_addr = hi_strdup(source_addr); } snprintf(_port, 6, "%d", port); memset(&hints,0,sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; /* Try with IPv6 if no IPv4 address was found. We do it in this order since * in a Redis client you can't afford to test if you have IPv6 connectivity * as this would add latency to every connect. Otherwise a more sensible * route could be: Use IPv6 if both addresses are available and there is IPv6 * connectivity. */ if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { hints.ai_family = AF_INET6; if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); return REDIS_ERR; } } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) continue; c->fd = s; if (redisSetBlocking(c,0) != REDIS_OK) goto error; if (c->tcp.source_addr) { int bound = 0; /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { char buf[128]; snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } if (reuseaddr) { n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, sizeof(n)) < 0) { freeaddrinfo(bservinfo); goto error; } } for (b = bservinfo; b != NULL; b = b->ai_next) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { bound = 1; break; } } freeaddrinfo(bservinfo); if (!bound) { char buf[128]; snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } } /* For repeat connection */ hi_free(c->saddr); c->saddr = hi_malloc(p->ai_addrlen); if (c->saddr == NULL) goto oom; memcpy(c->saddr, p->ai_addr, p->ai_addrlen); c->addrlen = p->ai_addrlen; if (c->connect_timeout) { double timeout = c->connect_timeout->tv_sec ; timeout += (double) c->connect_timeout->tv_usec / 1000 / 1000; swoole_coroutine_socket_set_connect_timeout(c->fd, timeout); } if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { redisNetClose(c); continue; } else if (errno == EINPROGRESS) { if (blocking) { goto wait_for_ready; } /* This is ok. * Note that even when it's in blocking mode, we unset blocking * for `connect()` */ } else if (errno == EADDRNOTAVAIL && reuseaddr) { if (++reuses >= REDIS_CONNECT_RETRIES) { goto error; } else { redisNetClose(c); goto addrretry; } } else { wait_for_ready: if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) goto error; if (redisSetTcpNoDelay(c) != REDIS_OK) goto error; } } if (blocking && redisSetBlocking(c,1) != REDIS_OK) goto error; c->flags |= REDIS_CONNECTED; rv = REDIS_OK; goto end; } if (p == NULL) { char buf[128]; snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); error: rv = REDIS_ERR; end: if(servinfo) { freeaddrinfo(servinfo); } return rv; // Need to return REDIS_OK if alright } int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { return _redisContextConnectTcp(c, addr, port, timeout, NULL); } int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { return _redisContextConnectTcp(c, addr, port, timeout, source_addr); } int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { #ifndef _WIN32 int blocking = (c->flags & REDIS_BLOCK); struct sockaddr_un *sa; long timeout_msec = -1; if (redisCreateSocket(c,AF_UNIX) < 0) return REDIS_ERR; if (redisSetBlocking(c,0) != REDIS_OK) return REDIS_ERR; c->connection_type = REDIS_CONN_UNIX; if (c->unix_sock.path != path) { hi_free(c->unix_sock.path); c->unix_sock.path = hi_strdup(path); if (c->unix_sock.path == NULL) goto oom; } if (timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) return REDIS_ERR; /* Don't leak sockaddr if we're reconnecting */ if (c->saddr) hi_free(c->saddr); sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un))); if (sa == NULL) goto oom; c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) return REDIS_ERR; } } /* Reset socket to be blocking after connect(2). */ if (blocking && redisSetBlocking(c,1) != REDIS_OK) return REDIS_ERR; c->flags |= REDIS_CONNECTED; return REDIS_OK; #else /* We currently do not support Unix sockets for Windows. */ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ errno = EPROTONOSUPPORT; return REDIS_ERR; #endif /* _WIN32 */ oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } ================================================ FILE: thirdparty/hiredis/net.h ================================================ /* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __NET_H #define __NET_H #include "hiredis.h" void redisNetClose(redisContext *c); ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap); ssize_t redisNetWrite(redisContext *c); int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); int redisCheckConnectDone(redisContext *c, int *completed); int redisSetTcpNoDelay(redisContext *c); #endif ================================================ FILE: thirdparty/hiredis/read.c ================================================ /* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "fmacros.h" #include #include #ifndef _MSC_VER #include #include #endif #include #include #include #include #include #include "alloc.h" #include "read.h" #include "sds.h" /* Initial size of our nested reply stack and how much we grow it when needd */ #define REDIS_READER_STACK_SIZE 9 static void __redisReaderSetError(redisReader *r, int type, const char *str) { size_t len; if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); r->reply = NULL; } /* Clear input buffer on errors. */ sdsfree(r->buf); r->buf = NULL; r->pos = r->len = 0; /* Reset task stack. */ r->ridx = -1; /* Set error. */ r->err = type; len = strlen(str); len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); memcpy(r->errstr,str,len); r->errstr[len] = '\0'; } static size_t chrtos(char *buf, size_t size, char byte) { size_t len = 0; switch(byte) { case '\\': case '"': len = snprintf(buf,size,"\"\\%c\"",byte); break; case '\n': len = snprintf(buf,size,"\"\\n\""); break; case '\r': len = snprintf(buf,size,"\"\\r\""); break; case '\t': len = snprintf(buf,size,"\"\\t\""); break; case '\a': len = snprintf(buf,size,"\"\\a\""); break; case '\b': len = snprintf(buf,size,"\"\\b\""); break; default: if (isprint(byte)) len = snprintf(buf,size,"\"%c\"",byte); else len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); break; } return len; } static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { char cbuf[8], sbuf[128]; chrtos(cbuf,sizeof(cbuf),byte); snprintf(sbuf,sizeof(sbuf), "Protocol error, got %s as reply type byte", cbuf); __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); } static void __redisReaderSetErrorOOM(redisReader *r) { __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); } static char *readBytes(redisReader *r, unsigned int bytes) { char *p; if (r->len-r->pos >= bytes) { p = r->buf+r->pos; r->pos += bytes; return p; } return NULL; } /* Find pointer to \r\n. */ static char *seekNewline(char *s, size_t len) { char *ret; /* We cannot match with fewer than 2 bytes */ if (len < 2) return NULL; /* Search up to len - 1 characters */ len--; /* Look for the \r */ while ((ret = memchr(s, '\r', len)) != NULL) { if (ret[1] == '\n') { /* Found. */ break; } /* Continue searching. */ ret++; len -= ret - s; s = ret; } return ret; } /* Convert a string into a long long. Returns REDIS_OK if the string could be * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value * will be set to the parsed value when appropriate. * * Note that this function demands that the string strictly represents * a long long: no spaces or other characters before or after the string * representing the number are accepted, nor zeroes at the start if not * for the string "0" representing the zero number. * * Because of its strictness, it is safe to use this function to check if * you can convert a string into a long long, and obtain back the string * from the number without any loss in the string representation. */ static int string2ll(const char *s, size_t slen, long long *value) { const char *p = s; size_t plen = 0; int negative = 0; unsigned long long v; if (plen == slen) return REDIS_ERR; /* Special case: first and only digit is 0. */ if (slen == 1 && p[0] == '0') { if (value != NULL) *value = 0; return REDIS_OK; } if (p[0] == '-') { negative = 1; p++; plen++; /* Abort on only a negative sign. */ if (plen == slen) return REDIS_ERR; } /* First digit should be 1-9, otherwise the string should just be 0. */ if (p[0] >= '1' && p[0] <= '9') { v = p[0]-'0'; p++; plen++; } else if (p[0] == '0' && slen == 1) { *value = 0; return REDIS_OK; } else { return REDIS_ERR; } while (plen < slen && p[0] >= '0' && p[0] <= '9') { if (v > (ULLONG_MAX / 10)) /* Overflow. */ return REDIS_ERR; v *= 10; if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ return REDIS_ERR; v += p[0]-'0'; p++; plen++; } /* Return if not all bytes were used. */ if (plen < slen) return REDIS_ERR; if (negative) { if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ return REDIS_ERR; if (value != NULL) *value = -v; } else { if (v > LLONG_MAX) /* Overflow. */ return REDIS_ERR; if (value != NULL) *value = v; } return REDIS_OK; } static char *readLine(redisReader *r, int *_len) { char *p, *s; int len; p = r->buf+r->pos; s = seekNewline(p,(r->len-r->pos)); if (s != NULL) { len = s-(r->buf+r->pos); r->pos += len+2; /* skip \r\n */ if (_len) *_len = len; return p; } return NULL; } static void moveToNextTask(redisReader *r) { redisReadTask *cur, *prv; while (r->ridx >= 0) { /* Return a.s.a.p. when the stack is now empty. */ if (r->ridx == 0) { r->ridx--; return; } cur = r->task[r->ridx]; prv = r->task[r->ridx-1]; assert(prv->type == REDIS_REPLY_ARRAY || prv->type == REDIS_REPLY_MAP || prv->type == REDIS_REPLY_SET || prv->type == REDIS_REPLY_PUSH); if (cur->idx == prv->elements-1) { r->ridx--; } else { /* Reset the type because the next item can be anything */ assert(cur->idx < prv->elements); cur->type = -1; cur->elements = -1; cur->idx++; return; } } } static int processLineItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj; char *p; int len; if ((p = readLine(r,&len)) != NULL) { if (cur->type == REDIS_REPLY_INTEGER) { long long v; if (string2ll(p, len, &v) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad integer value"); return REDIS_ERR; } if (r->fn && r->fn->createInteger) { obj = r->fn->createInteger(cur,v); } else { obj = (void*)REDIS_REPLY_INTEGER; } } else if (cur->type == REDIS_REPLY_DOUBLE) { char buf[326], *eptr; double d; if ((size_t)len >= sizeof(buf)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Double value is too large"); return REDIS_ERR; } memcpy(buf,p,len); buf[len] = '\0'; if (len == 3 && strcasecmp(buf,"inf") == 0) { d = INFINITY; /* Positive infinite. */ } else if (len == 4 && strcasecmp(buf,"-inf") == 0) { d = -INFINITY; /* Negative infinite. */ } else { d = strtod((char*)buf,&eptr); /* RESP3 only allows "inf", "-inf", and finite values, while * strtod() allows other variations on infinity, NaN, * etc. We explicity handle our two allowed infinite cases * above, so strtod() should only result in finite values. */ if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad double value"); return REDIS_ERR; } } if (r->fn && r->fn->createDouble) { obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDIS_REPLY_DOUBLE; } } else if (cur->type == REDIS_REPLY_NIL) { if (len != 0) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad nil value"); return REDIS_ERR; } if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; } else if (cur->type == REDIS_REPLY_BOOL) { int bval; if (len != 1 || !strchr("tTfF", p[0])) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bool value"); return REDIS_ERR; } bval = p[0] == 't' || p[0] == 'T'; if (r->fn && r->fn->createBool) obj = r->fn->createBool(cur,bval); else obj = (void*)REDIS_REPLY_BOOL; } else if (cur->type == REDIS_REPLY_BIGNUM) { /* Ensure all characters are decimal digits (with possible leading * minus sign). */ int i; for (i = 0; i < len; i++) { /* XXX Consider: Allow leading '+'? Error on leading '0's? */ if (i == 0 && p[0] == '-') continue; if (p[i] < '0' || p[i] > '9') { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bignum value"); return REDIS_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)REDIS_REPLY_BIGNUM; } else { /* Type will be error or status. */ int i; for (i = 0; i < len; i++) { if (p[i] == '\r' || p[i] == '\n') { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad simple string value"); return REDIS_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)(size_t)(cur->type); } if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDIS_OK; } return REDIS_ERR; } static int processBulkItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj = NULL; char *p, *s; long long len; unsigned long bytelen; int success = 0; p = r->buf+r->pos; s = seekNewline(p,r->len-r->pos); if (s != NULL) { p = r->buf+r->pos; bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bulk string length"); return REDIS_ERR; } if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bulk string length out of range"); return REDIS_ERR; } if (len == -1) { /* The nil object can always be created. */ if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; success = 1; } else { /* Only continue when the buffer contains the entire bulk item. */ bytelen += len+2; /* include \r\n */ if (r->pos+bytelen <= r->len) { if ((cur->type == REDIS_REPLY_VERB && len < 4) || (cur->type == REDIS_REPLY_VERB && s[5] != ':')) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Verbatim string 4 bytes of content type are " "missing or incorrectly encoded."); return REDIS_ERR; } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,s+2,len); else obj = (void*)(long)cur->type; success = 1; } } /* Proceed when obj was created. */ if (success) { if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } r->pos += bytelen; /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDIS_OK; } } return REDIS_ERR; } static int redisReaderGrow(redisReader *r) { redisReadTask **aux; int newlen; /* Grow our stack size */ newlen = r->tasks + REDIS_READER_STACK_SIZE; aux = hi_realloc(r->task, sizeof(*r->task) * newlen); if (aux == NULL) goto oom; r->task = aux; /* Allocate new tasks */ for (; r->tasks < newlen; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } return REDIS_OK; oom: __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Process the array, map and set types. */ static int processAggregateItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj; char *p; long long elements; int root = 0, len; if (r->ridx == r->tasks - 1) { if (redisReaderGrow(r) == REDIS_ERR) return REDIS_ERR; } if ((p = readLine(r,&len)) != NULL) { if (string2ll(p, len, &elements) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad multi-bulk length"); return REDIS_ERR; } root = (r->ridx == 0); if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) || (r->maxelements > 0 && elements > r->maxelements)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Multi-bulk length out of range"); return REDIS_ERR; } if (elements == -1) { if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } moveToNextTask(r); } else { if (cur->type == REDIS_REPLY_MAP) elements *= 2; if (r->fn && r->fn->createArray) obj = r->fn->createArray(cur,elements); else obj = (void*)(long)cur->type; if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Modify task stack when there are more than 0 elements. */ if (elements > 0) { cur->elements = elements; cur->obj = obj; r->ridx++; r->task[r->ridx]->type = -1; r->task[r->ridx]->elements = -1; r->task[r->ridx]->idx = 0; r->task[r->ridx]->obj = NULL; r->task[r->ridx]->parent = cur; r->task[r->ridx]->privdata = r->privdata; } else { moveToNextTask(r); } } /* Set reply if this is the root object. */ if (root) r->reply = obj; return REDIS_OK; } return REDIS_ERR; } static int processItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; char *p; /* check if we need to read type */ if (cur->type < 0) { if ((p = readBytes(r,1)) != NULL) { switch (p[0]) { case '-': cur->type = REDIS_REPLY_ERROR; break; case '+': cur->type = REDIS_REPLY_STATUS; break; case ':': cur->type = REDIS_REPLY_INTEGER; break; case ',': cur->type = REDIS_REPLY_DOUBLE; break; case '_': cur->type = REDIS_REPLY_NIL; break; case '$': cur->type = REDIS_REPLY_STRING; break; case '*': cur->type = REDIS_REPLY_ARRAY; break; case '%': cur->type = REDIS_REPLY_MAP; break; case '~': cur->type = REDIS_REPLY_SET; break; case '#': cur->type = REDIS_REPLY_BOOL; break; case '=': cur->type = REDIS_REPLY_VERB; break; case '>': cur->type = REDIS_REPLY_PUSH; break; case '(': cur->type = REDIS_REPLY_BIGNUM; break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; } } else { /* could not consume 1 byte */ return REDIS_ERR; } } /* process typed item */ switch(cur->type) { case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_INTEGER: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: case REDIS_REPLY_BIGNUM: return processLineItem(r); case REDIS_REPLY_STRING: case REDIS_REPLY_VERB: return processBulkItem(r); case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: case REDIS_REPLY_SET: case REDIS_REPLY_PUSH: return processAggregateItem(r); default: assert(NULL); return REDIS_ERR; /* Avoid warning. */ } } redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *r; r = hi_calloc(1,sizeof(redisReader)); if (r == NULL) return NULL; r->buf = sdsempty(); if (r->buf == NULL) goto oom; r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task)); if (r->task == NULL) goto oom; for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } r->fn = fn; r->maxbuf = REDIS_READER_MAX_BUF; r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS; r->ridx = -1; return r; oom: redisReaderFree(r); return NULL; } void redisReaderFree(redisReader *r) { if (r == NULL) return; if (r->reply != NULL && r->fn && r->fn->freeObject) r->fn->freeObject(r->reply); if (r->task) { /* We know r->task[i] is allocated if i < r->tasks */ for (int i = 0; i < r->tasks; i++) { hi_free(r->task[i]); } hi_free(r->task); } sdsfree(r->buf); hi_free(r); } int redisReaderFeed(redisReader *r, const char *buf, size_t len) { sds newbuf; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDIS_ERR; /* Copy the provided buffer. */ if (buf != NULL && len >= 1) { /* Destroy internal buffer when it is empty and is quite large. */ if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { sdsfree(r->buf); r->buf = sdsempty(); if (r->buf == 0) goto oom; r->pos = 0; } newbuf = sdscatlen(r->buf,buf,len); if (newbuf == NULL) goto oom; r->buf = newbuf; r->len = sdslen(r->buf); } return REDIS_OK; oom: __redisReaderSetErrorOOM(r); return REDIS_ERR; } int redisReaderGetReply(redisReader *r, void **reply) { /* Default target pointer to NULL. */ if (reply != NULL) *reply = NULL; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDIS_ERR; /* When the buffer is empty, there will never be a reply. */ if (r->len == 0) return REDIS_OK; /* Set first item to process when the stack is empty. */ if (r->ridx == -1) { r->task[0]->type = -1; r->task[0]->elements = -1; r->task[0]->idx = -1; r->task[0]->obj = NULL; r->task[0]->parent = NULL; r->task[0]->privdata = r->privdata; r->ridx = 0; } /* Process items in reply. */ while (r->ridx >= 0) if (processItem(r) != REDIS_OK) break; /* Return ASAP when an error occurred. */ if (r->err) return REDIS_ERR; /* Discard part of the buffer when we've consumed at least 1k, to avoid * doing unnecessary calls to memmove() in sds.c. */ if (r->pos >= 1024) { if (sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; r->pos = 0; r->len = sdslen(r->buf); } /* Emit a reply when there is one. */ if (r->ridx == -1) { if (reply != NULL) { *reply = r->reply; } else if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); } r->reply = NULL; } return REDIS_OK; } ================================================ FILE: thirdparty/hiredis/read.h ================================================ /* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __HIREDIS_READ_H #define __HIREDIS_READ_H #include /* for size_t */ #define REDIS_ERR -1 #define REDIS_OK 0 /* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDIS_ERR_IO 1 /* Error in read or write */ #define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_TIMEOUT 6 /* Timed out */ #define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_INTEGER 3 #define REDIS_REPLY_NIL 4 #define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_DOUBLE 7 #define REDIS_REPLY_BOOL 8 #define REDIS_REPLY_MAP 9 #define REDIS_REPLY_SET 10 #define REDIS_REPLY_ATTR 11 #define REDIS_REPLY_PUSH 12 #define REDIS_REPLY_BIGNUM 13 #define REDIS_REPLY_VERB 14 /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF (1024*16) /* Default multi-bulk element limit */ #define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1) #ifdef __cplusplus extern "C" { #endif typedef struct redisReadTask { int type; long long elements; /* number of elements in multibulk container */ int idx; /* index in parent (array) object */ void *obj; /* holds user-generated value for a read task */ struct redisReadTask *parent; /* parent task */ void *privdata; /* user-settable arbitrary field */ } redisReadTask; typedef struct redisReplyObjectFunctions { void *(*createString)(const redisReadTask*, char*, size_t); void *(*createArray)(const redisReadTask*, size_t); void *(*createInteger)(const redisReadTask*, long long); void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createNil)(const redisReadTask*); void *(*createBool)(const redisReadTask*, int); void (*freeObject)(void*); } redisReplyObjectFunctions; typedef struct redisReader { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ char *buf; /* Read buffer */ size_t pos; /* Buffer cursor */ size_t len; /* Buffer length */ size_t maxbuf; /* Max length of unused buffer */ long long maxelements; /* Max multi-bulk elements */ redisReadTask **task; int tasks; int ridx; /* Index of current read task */ void *reply; /* Temporary reply pointer */ redisReplyObjectFunctions *fn; void *privdata; } redisReader; /* Public API for the protocol parser. */ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); void redisReaderFree(redisReader *r); int redisReaderFeed(redisReader *r, const char *buf, size_t len); int redisReaderGetReply(redisReader *r, void **reply); #define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) #define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) #define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) #ifdef __cplusplus } #endif #endif ================================================ FILE: thirdparty/hiredis/sds.c ================================================ /* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "fmacros.h" #include #include #include #include #include #include #include "sds.h" #include "sdsalloc.h" static inline int sdsHdrSize(char type) { switch(type&SDS_TYPE_MASK) { case SDS_TYPE_5: return sizeof(struct sdshdr5); case SDS_TYPE_8: return sizeof(struct sdshdr8); case SDS_TYPE_16: return sizeof(struct sdshdr16); case SDS_TYPE_32: return sizeof(struct sdshdr32); case SDS_TYPE_64: return sizeof(struct sdshdr64); } return 0; } static inline char sdsReqType(size_t string_size) { if (string_size < 32) return SDS_TYPE_5; if (string_size < 0xff) return SDS_TYPE_8; if (string_size < 0xffff) return SDS_TYPE_16; if (string_size < 0xffffffff) return SDS_TYPE_32; return SDS_TYPE_64; } static inline size_t sdsTypeMaxSize(char type) { if (type == SDS_TYPE_5) return (1<<5) - 1; if (type == SDS_TYPE_8) return (1<<8) - 1; if (type == SDS_TYPE_16) return (1<<16) - 1; #if (LONG_MAX == LLONG_MAX) if (type == SDS_TYPE_32) return (1ll<<32) - 1; #endif return -1; /* this is equivalent to the max SDS_TYPE_64 or SDS_TYPE_32 */ } /* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-terminated (all the sds strings are, always) so * even if you create an sds string with: * * mystring = sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the sds header. */ sds sdsnewlen(const void *init, size_t initlen) { void *sh; sds s; char type = sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ if (initlen > SIZE_MAX - hdrlen - 1) return NULL; /* Catch size_t overflow */ sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (!init) memset(sh, 0, hdrlen+initlen+1); s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { case SDS_TYPE_5: { *fp = type | (initlen << SDS_TYPE_BITS); break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; } /* Create an empty (zero length) sds string. Even in this case the string * always has an implicit null term. */ sds sdsempty(void) { return sdsnewlen("",0); } /* Create a new sds string starting from a null terminated C string. */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); } /* Duplicate an sds string. */ sds sdsdup(const sds s) { return sdsnewlen(s, sdslen(s)); } /* Free an sds string. No operation is performed if 's' is NULL. */ void sdsfree(sds s) { if (s == NULL) return; s_free((char*)s-sdsHdrSize(s[-1])); } /* Set the sds string length to the length as obtained with strlen(), so * considering as content only up to the first null term character. * * This function is useful when the sds string is hacked manually in some * way, like in the following example: * * s = sdsnew("foobar"); * s[2] = '\0'; * sdsupdatelen(s); * printf("%d\n", sdslen(s)); * * The output will be "2", but if we comment out the call to sdsupdatelen() * the output will be "6" as the string was modified but the logical length * remains 6 bytes. */ void sdsupdatelen(sds s) { int reallen = strlen(s); sdssetlen(s, reallen); } /* Modify an sds string in-place to make it empty (zero length). * However all the existing buffer is not discarded but set as free space * so that next append operations will not require allocations up to the * number of bytes previously available. */ void sdsclear(sds s) { sdssetlen(s, 0); s[0] = '\0'; } /* Enlarge the free space at the end of the sds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */ sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = sdslen(s); sh = (char*)s - sdsHdrSize(oldtype); /* Fix: Prevent Integer Overflow */ if (addlen > SIZE_MAX - len) return NULL; /* Prevent overflow */ newlen = len + addlen; if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; type = sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is * not able to remember empty space, so sdsMakeRoomFor() must be called * at every appending operation. */ if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); /* Fix: Ensure safe memory allocation */ if (hdrlen + newlen + 1 < newlen) return NULL; /* Prevent overflow */ if (oldtype == type) { newsh = s_realloc(sh, hdrlen + newlen + 1); if (newsh == NULL) return NULL; s = (char*)newsh + hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = s_malloc(hdrlen + newlen + 1); if (newsh == NULL) return NULL; memcpy((char*)newsh + hdrlen, s, len + 1); s_free(sh); s = (char*)newsh + hdrlen; s[-1] = type; sdssetlen(s, len); } /* Fix: Prevent setting a too-large allocation */ if (newlen > sdsTypeMaxSize(type)) newlen = sdsTypeMaxSize(type); sdssetalloc(s, newlen); return s; } /* Reallocate the sds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdsRemoveFreeSpace(sds s) { void *sh, *newsh; char type, oldtype = s[-1] & SDS_TYPE_MASK; int hdrlen; size_t len = sdslen(s); sh = (char*)s-sdsHdrSize(oldtype); type = sdsReqType(len); hdrlen = sdsHdrSize(type); if (oldtype==type) { newsh = s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { newsh = s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; sdssetlen(s, len); } sdssetalloc(s, len); return s; } /* Return the total size of the allocation of the specifed sds string, * including: * 1) The sds header before the pointer. * 2) The string. * 3) The free buffer at the end if any. * 4) The implicit null term. */ size_t sdsAllocSize(sds s) { size_t alloc = sdsalloc(s); return sdsHdrSize(s[-1])+alloc+1; } /* Return the pointer of the actual SDS allocation (normally SDS strings * are referenced by the start of the string buffer). */ void *sdsAllocPtr(sds s) { return (void*) (s-sdsHdrSize(s[-1])); } /* Increment the sds length and decrements the left free space at the * end of the string according to 'incr'. Also set the null term * in the new end of the string. * * This function is used in order to fix the string length after the * user calls sdsMakeRoomFor(), writes something after the end of * the current string, and finally needs to set the new length. * * Note: it is possible to use a negative increment in order to * right-trim the string. * * Usage example: * * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the * following schema, to cat bytes coming from the kernel to the end of an * sds string without copying into an intermediate buffer: * * oldlen = sdslen(s); * s = sdsMakeRoomFor(s, BUFFER_SIZE); * nread = read(fd, s+oldlen, BUFFER_SIZE); * ... check for nread <= 0 and handle it ... * sdsIncrLen(s, nread); */ void sdsIncrLen(sds s, int incr) { unsigned char flags = s[-1]; size_t len; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char oldlen = SDS_TYPE_5_LEN(flags); assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); len = oldlen+incr; break; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); len = (sh->len += incr); break; } default: len = 0; /* Just to avoid compilation warnings. */ } s[len] = '\0'; } /* Grow the sds to have the specified length. Bytes that were not part of * the original length of the sds will be set to zero. * * if the specified length is smaller than the current length, no operation * is performed. */ sds sdsgrowzero(sds s, size_t len) { size_t curlen = sdslen(s); if (len <= curlen) return s; s = sdsMakeRoomFor(s,len-curlen); if (s == NULL) return NULL; /* Make sure added region doesn't contain garbage */ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ sdssetlen(s, len); return s; } /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the * end of the specified sds string 's'. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatlen(sds s, const void *t, size_t len) { size_t curlen = sdslen(s); s = sdsMakeRoomFor(s,len); if (s == NULL) return NULL; memcpy(s+curlen, t, len); sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } /* Append the specified null termianted C string to the sds string 's'. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscat(sds s, const char *t) { return sdscatlen(s, t, strlen(t)); } /* Append the specified sds 't' to the existing sds 's'. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatsds(sds s, const sds t) { return sdscatlen(s, t, sdslen(t)); } /* Destructively modify the sds string 's' to hold the specified binary * safe string pointed by 't' of length 'len' bytes. */ sds sdscpylen(sds s, const char *t, size_t len) { if (sdsalloc(s) < len) { s = sdsMakeRoomFor(s,len-sdslen(s)); if (s == NULL) return NULL; } memcpy(s, t, len); s[len] = '\0'; sdssetlen(s, len); return s; } /* Like sdscpylen() but 't' must be a null-terminated string so that the length * of the string is obtained with strlen(). */ sds sdscpy(sds s, const char *t) { return sdscpylen(s, t, strlen(t)); } /* Helper for sdscatlonglong() doing the actual number -> string * conversion. 's' must point to a string with room for at least * SDS_LLSTR_SIZE bytes. * * The function returns the length of the null-terminated string * representation stored at 's'. */ #define SDS_LLSTR_SIZE 21 int sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; /* Generate the string representation, this method produces * an reversed string. */ v = (value < 0) ? -value : value; p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); if (value < 0) *p++ = '-'; /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Identical sdsll2str(), but for unsigned long long type. */ int sdsull2str(char *s, unsigned long long v) { char *p, aux; size_t l; /* Generate the string representation, this method produces * an reversed string. */ p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Create an sds string from a long long value. It is much faster than: * * sdscatprintf(sdsempty(),"%lld\n", value); */ sds sdsfromlonglong(long long value) { char buf[SDS_LLSTR_SIZE]; int len = sdsll2str(buf,value); return sdsnewlen(buf,len); } /* Like sdscatprintf() but gets va_list instead of being variadic. */ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { va_list cpy; char staticbuf[1024], *buf = staticbuf, *t; size_t buflen = strlen(fmt)*2; /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ if (buflen > sizeof(staticbuf)) { buf = s_malloc(buflen); if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); } /* Try with buffers two times bigger every time we fail to * fit the string in the current buffer size. */ while(1) { buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); va_end(cpy); if (buf[buflen-2] != '\0') { if (buf != staticbuf) s_free(buf); buflen *= 2; buf = s_malloc(buflen); if (buf == NULL) return NULL; continue; } break; } /* Finally concat the obtained string to the SDS string and return it. */ t = sdscat(s, buf); if (buf != staticbuf) s_free(buf); return t; } /* Append to the sds string 's' a string obtained using printf-alike format * specifier. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = sdsnew("Sum is: "); * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). * * Often you need to create a string from scratch with the printf-alike * format. When this is the need, just use sdsempty() as the target string: * * s = sdscatprintf(sdsempty(), "... your format ...", args); */ sds sdscatprintf(sds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); t = sdscatvprintf(s,fmt,ap); va_end(ap); return t; } /* This function is similar to sdscatprintf, but much faster as it does * not rely on sprintf() family functions implemented by the libc that * are often very slow. Moreover directly handling the sds string as * new data is concatenated provides a performance improvement. * * However this function only handles an incompatible subset of printf-alike * format specifiers: * * %s - C String * %S - SDS string * %i - signed int * %I - 64 bit signed integer (long long, int64_t) * %u - unsigned int * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. */ sds sdscatfmt(sds s, char const *fmt, ...) { const char *f = fmt; int i; va_list ap; va_start(ap,fmt); i = sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; size_t l; long long num; unsigned long long unum; /* Make sure there is always space for at least 1 char. */ if (sdsavail(s)==0) { s = sdsMakeRoomFor(s,1); if (s == NULL) goto fmt_error; } switch(*f) { case '%': next = *(f+1); f++; switch(next) { case 's': case 'S': str = va_arg(ap,char*); l = (next == 's') ? strlen(str) : sdslen(str); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); sdsinclen(s,l); i += l; break; case 'i': case 'I': if (next == 'i') num = va_arg(ap,int); else num = va_arg(ap,long long); { char buf[SDS_LLSTR_SIZE]; l = sdsll2str(buf,num); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; case 'u': case 'U': if (next == 'u') unum = va_arg(ap,unsigned int); else unum = va_arg(ap,unsigned long long); { char buf[SDS_LLSTR_SIZE]; l = sdsull2str(buf,unum); if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; sdsinclen(s,1); break; } break; default: s[i++] = *f; sdsinclen(s,1); break; } f++; } va_end(ap); /* Add null-term */ s[i] = '\0'; return s; fmt_error: va_end(ap); return NULL; } /* Remove the part of the string from left and from right composed just of * contiguous characters found in 'cset', that is a null terminted C string. * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); * s = sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ sds sdstrim(sds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; ep = end = s+sdslen(s)-1; while(sp <= end && strchr(cset, *sp)) sp++; while(ep > sp && strchr(cset, *ep)) ep--; len = (sp > ep) ? 0 : ((ep-sp)+1); if (s != sp) memmove(s, sp, len); s[len] = '\0'; sdssetlen(s,len); return s; } /* Turn the string into a smaller (or equal) string containing only the * substring specified by the 'start' and 'end' indexes. * * start and end can be negative, where -1 means the last character of the * string, -2 the penultimate character, and so forth. * * The interval is inclusive, so the start and end characters will be part * of the resulting string. * * The string is modified in-place. * * Return value: * -1 (error) if sdslen(s) is larger than maximum positive ssize_t value. * 0 on success. * * Example: * * s = sdsnew("Hello World"); * sdsrange(s,1,-1); => "ello World" */ int sdsrange(sds s, ssize_t start, ssize_t end) { size_t newlen, len = sdslen(s); if (len > SSIZE_MAX) return -1; if (len == 0) return 0; if (start < 0) { start = len+start; if (start < 0) start = 0; } if (end < 0) { end = len+end; if (end < 0) end = 0; } newlen = (start > end) ? 0 : (end-start)+1; if (newlen != 0) { if (start >= (ssize_t)len) { newlen = 0; } else if (end >= (ssize_t)len) { end = len-1; newlen = (start > end) ? 0 : (end-start)+1; } } else { start = 0; } if (start && newlen) memmove(s, s+start, newlen); s[newlen] = 0; sdssetlen(s,newlen); return 0; } /* Apply tolower() to every character of the sds string 's'. */ void sdstolower(sds s) { int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } /* Apply toupper() to every character of the sds string 's'. */ void sdstoupper(sds s) { int len = sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } /* Compare two sds strings s1 and s2 with memcmp(). * * Return value: * * positive if s1 > s2. * negative if s1 < s2. * 0 if s1 and s2 are exactly the same binary string. * * If two strings share exactly the same prefix, but one of the two has * additional characters, the longer string is considered to be greater than * the smaller one. */ int sdscmp(const sds s1, const sds s2) { size_t l1, l2, minlen; int cmp; l1 = sdslen(s1); l2 = sdslen(s2); minlen = (l1 < l2) ? l1 : l2; cmp = memcmp(s1,s2,minlen); if (cmp == 0) return l1-l2; return cmp; } /* Split 's' with separator in 'sep'. An array * of sds strings is returned. *count will be set * by reference to the number of tokens returned. * * On out of memory, zero length string, zero length * separator, NULL is returned. * * Note that 'sep' is able to split a string using * a multi-character separator. For example * sdssplit("foo_-_bar","_-_"); will return two * elements "foo" and "bar". * * This version of the function is binary-safe but * requires length arguments. sdssplit() is just the * same function but for zero-terminated strings. */ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; sds *tokens; if (seplen < 1 || len < 0) return NULL; tokens = s_malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; if (len == 0) { *count = 0; return tokens; } for (j = 0; j < (len-(seplen-1)); j++) { /* make sure there is room for the next element and the final one */ if (slots < elements+2) { sds *newtokens; slots *= 2; newtokens = s_realloc(tokens,sizeof(sds)*slots); if (newtokens == NULL) goto cleanup; tokens = newtokens; } /* search the separator */ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { tokens[elements] = sdsnewlen(s+start,j-start); if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; j = j+seplen-1; /* skip the separator */ } } /* Add the final element. We are sure there is room in the tokens array. */ tokens[elements] = sdsnewlen(s+start,len-start); if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; return tokens; cleanup: { int i; for (i = 0; i < elements; i++) sdsfree(tokens[i]); s_free(tokens); *count = 0; return NULL; } } /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ void sdsfreesplitres(sds *tokens, int count) { if (!tokens) return; while(count--) sdsfree(tokens[count]); s_free(tokens); } /* Append to the sds string "s" an escaped string representation where * all the non-printable characters (tested with isprint()) are turned into * escapes in the form "\n\r\a...." or "\x". * * After the call, the modified sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatrepr(sds s, const char *p, size_t len) { s = sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': s = sdscatprintf(s,"\\%c",*p); break; case '\n': s = sdscatlen(s,"\\n",2); break; case '\r': s = sdscatlen(s,"\\r",2); break; case '\t': s = sdscatlen(s,"\\t",2); break; case '\a': s = sdscatlen(s,"\\a",2); break; case '\b': s = sdscatlen(s,"\\b",2); break; default: if (isprint(*p)) s = sdscatprintf(s,"%c",*p); else s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); break; } p++; } return sdscatlen(s,"\"",1); } /* Helper function for sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ int hex_digit_to_int(char c) { switch(c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return 0; } } /* Split a line into arguments, where every argument can be in the * following programming-language REPL-alike form: * * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array * of sds is returned. * * The caller should free the resulting array of sds strings with * sdsfreesplitres(). * * Note that sdscatrepr() is able to convert back a string into * a quoted string in the same format sdssplitargs() is able to parse. * * The function returns the allocated tokens on success, even when the * input string is empty, or NULL if the input contains unbalanced * quotes or closed quotes followed by non space characters * as in: "foo"bar or "foo' */ sds *sdssplitargs(const char *line, int *argc) { const char *p = line; char *current = NULL; char **vector = NULL; *argc = 0; while(1) { /* skip blanks */ while(*p && isspace(*p)) p++; if (*p) { /* get a token */ int inq=0; /* set to 1 if we are in "quotes" */ int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; if (current == NULL) current = sdsempty(); while(!done) { if (inq) { if (*p == '\\' && *(p+1) == 'x' && isxdigit(*(p+2)) && isxdigit(*(p+3))) { unsigned char byte; byte = (hex_digit_to_int(*(p+2))*16)+ hex_digit_to_int(*(p+3)); current = sdscatlen(current,(char*)&byte,1); p += 3; } else if (*p == '\\' && *(p+1)) { char c; p++; switch(*p) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'b': c = '\b'; break; case 'a': c = '\a'; break; default: c = *p; break; } current = sdscatlen(current,&c,1); } else if (*p == '"') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace(*(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = sdscatlen(current,p,1); } } else if (insq) { if (*p == '\\' && *(p+1) == '\'') { p++; current = sdscatlen(current,"'",1); } else if (*p == '\'') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace(*(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = sdscatlen(current,p,1); } } else { switch(*p) { case ' ': case '\n': case '\r': case '\t': case '\0': done=1; break; case '"': inq=1; break; case '\'': insq=1; break; default: current = sdscatlen(current,p,1); break; } } if (*p) p++; } /* add the token to the vector */ { char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); if (new_vector == NULL) { s_free(vector); return NULL; } vector = new_vector; vector[*argc] = current; (*argc)++; current = NULL; } } else { /* Even on empty input string return something not NULL. */ if (vector == NULL) vector = s_malloc(sizeof(void*)); return vector; } } err: while((*argc)--) sdsfree(vector[*argc]); s_free(vector); if (current) sdsfree(current); *argc = 0; return NULL; } /* Modify the string substituting all the occurrences of the set of * characters specified in the 'from' string to the corresponding character * in the 'to' array. * * For instance: sdsmapchars(mystring, "ho", "01", 2) * will have the effect of turning the string "hello" into "0ell1". * * The function returns the sds string pointer, that is always the same * as the input pointer since no resize is needed. */ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { size_t j, i, l = sdslen(s); for (j = 0; j < l; j++) { for (i = 0; i < setlen; i++) { if (s[j] == from[i]) { s[j] = to[i]; break; } } } return s; } /* Join an array of C strings using the specified separator (also a C string). * Returns the result as an sds string. */ sds sdsjoin(char **argv, int argc, char *sep) { sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { join = sdscat(join, argv[j]); if (j != argc-1) join = sdscat(join,sep); } return join; } /* Like sdsjoin, but joins an array of SDS strings. */ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { sds join = sdsempty(); int j; for (j = 0; j < argc; j++) { join = sdscatsds(join, argv[j]); if (j != argc-1) join = sdscatlen(join,sep,seplen); } return join; } /* Wrappers to the allocators used by SDS. Note that SDS will actually * just use the macros defined into sdsalloc.h in order to avoid to pay * the overhead of function calls. Here we define these wrappers only for * the programs SDS is linked to, if they want to touch the SDS internals * even if they use a different allocator. */ void *sds_malloc(size_t size) { return s_malloc(size); } void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } void sds_free(void *ptr) { s_free(ptr); } #if defined(SDS_TEST_MAIN) #include #include "testhelp.h" #include "limits.h" #define UNUSED(x) (void)(x) int sdsTest(void) { { sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) sdsfree(x); x = sdsnewlen("foo",2); test_cond("Create a string with specified length", sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) x = sdscat(x,"bar"); test_cond("Strings concatenation", sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); x = sdscpy(x,"a"); test_cond("sdscpy() against an originally longer string", sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); test_cond("sdscpy() against an originally shorter string", sdslen(x) == 33 && memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) sdsfree(x); x = sdscatprintf(sdsempty(),"%d",123); test_cond("sdscatprintf() seems working in the base case", sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) sdsfree(x); x = sdsnew("--"); x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); test_cond("sdscatfmt() seems working in the base case", sdslen(x) == 60 && memcmp(x,"--Hello Hi! World -9223372036854775808," "9223372036854775807--",60) == 0) printf("[%s]\n",x); sdsfree(x); x = sdsnew("--"); x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); test_cond("sdscatfmt() seems working with unsigned numbers", sdslen(x) == 35 && memcmp(x,"--4294967295,18446744073709551615--",35) == 0) sdsfree(x); x = sdsnew(" x "); sdstrim(x," x"); test_cond("sdstrim() works when all chars match", sdslen(x) == 0) sdsfree(x); x = sdsnew(" x "); sdstrim(x," "); test_cond("sdstrim() works when a single char remains", sdslen(x) == 1 && x[0] == 'x') sdsfree(x); x = sdsnew("xxciaoyyy"); sdstrim(x,"xy"); test_cond("sdstrim() correctly trims characters", sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) y = sdsdup(x); sdsrange(y,1,1); test_cond("sdsrange(...,1,1)", sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,1,-1); test_cond("sdsrange(...,1,-1)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,-2,-1); test_cond("sdsrange(...,-2,-1)", sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,2,1); test_cond("sdsrange(...,2,1)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,1,100); test_cond("sdsrange(...,1,100)", sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) sdsfree(y); y = sdsdup(x); sdsrange(y,100,100); test_cond("sdsrange(...,100,100)", sdslen(y) == 0 && memcmp(y,"\0",1) == 0) sdsfree(y); sdsfree(x); x = sdsnew("foo"); y = sdsnew("foa"); test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) sdsfree(y); sdsfree(x); x = sdsnew("bar"); y = sdsnew("bar"); test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) sdsfree(y); sdsfree(x); x = sdsnew("aar"); y = sdsnew("bar"); test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) sdsfree(y); sdsfree(x); x = sdsnewlen("\a\n\0foo\r",7); y = sdscatrepr(sdsempty(),x,sdslen(x)); test_cond("sdscatrepr(...data...)", memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) { unsigned int oldfree; char *p; int step = 10, j, i; sdsfree(x); sdsfree(y); x = sdsnew("0"); test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); /* Run the test a few times in order to hit the first two * SDS header types. */ for (i = 0; i < 10; i++) { int oldlen = sdslen(x); x = sdsMakeRoomFor(x,step); int type = x[-1]&SDS_TYPE_MASK; test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); if (type != SDS_TYPE_5) { test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); oldfree = sdsavail(x); } p = x+oldlen; for (j = 0; j < step; j++) { p[j] = 'A'+j; } sdsIncrLen(x,step); } test_cond("sdsMakeRoomFor() content", memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); sdsfree(x); } } test_report() return 0; } #endif #ifdef SDS_TEST_MAIN int main(void) { return sdsTest(); } #endif ================================================ FILE: thirdparty/hiredis/sds.h ================================================ /* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef __SDS_H #define __SDS_H #define SDS_MAX_PREALLOC (1024*1024) #ifdef _MSC_VER #define __attribute__(x) typedef long long ssize_t; #define SSIZE_MAX (LLONG_MAX >> 1) #endif #include #include #include typedef char *sds; /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE_64 4 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) static inline size_t sdslen(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->len; case SDS_TYPE_16: return SDS_HDR(16,s)->len; case SDS_TYPE_32: return SDS_HDR(32,s)->len; case SDS_TYPE_64: return SDS_HDR(64,s)->len; } return 0; } static inline size_t sdsavail(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { return 0; } case SDS_TYPE_8: { SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } case SDS_TYPE_16: { SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } case SDS_TYPE_32: { SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } case SDS_TYPE_64: { SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } static inline void sdssetlen(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len = (uint8_t)newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->len = (uint16_t)newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->len = (uint32_t)newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->len = (uint64_t)newlen; break; } } static inline void sdsinclen(sds s, size_t inc) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break; case SDS_TYPE_8: SDS_HDR(8,s)->len += (uint8_t)inc; break; case SDS_TYPE_16: SDS_HDR(16,s)->len += (uint16_t)inc; break; case SDS_TYPE_32: SDS_HDR(32,s)->len += (uint32_t)inc; break; case SDS_TYPE_64: SDS_HDR(64,s)->len += (uint64_t)inc; break; } } /* sdsalloc() = sdsavail() + sdslen() */ static inline size_t sdsalloc(const sds s) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8,s)->alloc; case SDS_TYPE_16: return SDS_HDR(16,s)->alloc; case SDS_TYPE_32: return SDS_HDR(32,s)->alloc; case SDS_TYPE_64: return SDS_HDR(64,s)->alloc; } return 0; } static inline void sdssetalloc(sds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&SDS_TYPE_MASK) { case SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; case SDS_TYPE_8: SDS_HDR(8,s)->alloc = (uint8_t)newlen; break; case SDS_TYPE_16: SDS_HDR(16,s)->alloc = (uint16_t)newlen; break; case SDS_TYPE_32: SDS_HDR(32,s)->alloc = (uint32_t)newlen; break; case SDS_TYPE_64: SDS_HDR(64,s)->alloc = (uint64_t)newlen; break; } } sds sdsnewlen(const void *init, size_t initlen); sds sdsnew(const char *init); sds sdsempty(void); sds sdsdup(const sds s); void sdsfree(sds s); sds sdsgrowzero(sds s, size_t len); sds sdscatlen(sds s, const void *t, size_t len); sds sdscat(sds s, const char *t); sds sdscatsds(sds s, const sds t); sds sdscpylen(sds s, const char *t, size_t len); sds sdscpy(sds s, const char *t); sds sdscatvprintf(sds s, const char *fmt, va_list ap); #ifdef __GNUC__ sds sdscatprintf(sds s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #else sds sdscatprintf(sds s, const char *fmt, ...); #endif sds sdscatfmt(sds s, char const *fmt, ...); sds sdstrim(sds s, const char *cset); int sdsrange(sds s, ssize_t start, ssize_t end); void sdsupdatelen(sds s); void sdsclear(sds s); int sdscmp(const sds s1, const sds s2); sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); void sdsfreesplitres(sds *tokens, int count); void sdstolower(sds s); void sdstoupper(sds s); sds sdsfromlonglong(long long value); sds sdscatrepr(sds s, const char *p, size_t len); sds *sdssplitargs(const char *line, int *argc); sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); sds sdsjoin(char **argv, int argc, char *sep); sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ sds sdsMakeRoomFor(sds s, size_t addlen); void sdsIncrLen(sds s, int incr); sds sdsRemoveFreeSpace(sds s); size_t sdsAllocSize(sds s); void *sdsAllocPtr(sds s); /* Export the allocator used by SDS to the program using SDS. * Sometimes the program SDS is linked to, may use a different set of * allocators, but may want to allocate or free things that SDS will * respectively free or allocate. */ void *sds_malloc(size_t size); void *sds_realloc(void *ptr, size_t size); void sds_free(void *ptr); #ifdef REDIS_TEST int sdsTest(int argc, char *argv[]); #endif #endif ================================================ FILE: thirdparty/hiredis/sdsalloc.h ================================================ /* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* SDS allocator selection. * * This file is used in order to change the SDS allocator at compile time. * Just define the following defines to what you want to use. Also add * the include of your alternate allocator if needed (not needed in order * to use the default libc allocator). */ #include "alloc.h" #define s_malloc hi_malloc #define s_realloc hi_realloc #define s_free hi_free ================================================ FILE: thirdparty/llhttp/LICENSE ================================================ This software is licensed under the MIT License. Copyright Fedor Indutny, 2018. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: thirdparty/llhttp/LICENSE-MIT ================================================ This software is licensed under the MIT License. Copyright Fedor Indutny, 2018. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: thirdparty/llhttp/api.c ================================================ #include #include #include #include "llhttp.h" #define CALLBACK_MAYBE(PARSER, NAME) \ do { \ const llhttp_settings_t* settings; \ settings = (const llhttp_settings_t*) (PARSER)->settings; \ if (settings == NULL || settings->NAME == NULL) { \ err = 0; \ break; \ } \ err = settings->NAME((PARSER)); \ } while (0) #define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ do { \ const llhttp_settings_t* settings; \ settings = (const llhttp_settings_t*) (PARSER)->settings; \ if (settings == NULL || settings->NAME == NULL) { \ err = 0; \ break; \ } \ err = settings->NAME((PARSER), (START), (LEN)); \ if (err == -1) { \ err = HPE_USER; \ llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ } \ } while (0) void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings) { llhttp__internal_init(parser); parser->type = type; parser->settings = (void*) settings; } #if defined(__wasm__) extern int wasm_on_message_begin(llhttp_t * p); extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); extern int wasm_on_headers_complete(llhttp_t * p, int status_code, uint8_t upgrade, int should_keep_alive); extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); extern int wasm_on_message_complete(llhttp_t * p); static int wasm_on_headers_complete_wrap(llhttp_t* p) { return wasm_on_headers_complete(p, p->status_code, p->upgrade, llhttp_should_keep_alive(p)); } const llhttp_settings_t wasm_settings = { .on_message_begin = wasm_on_message_begin, .on_url = wasm_on_url, .on_status = wasm_on_status, .on_header_field = wasm_on_header_field, .on_header_value = wasm_on_header_value, .on_headers_complete = wasm_on_headers_complete_wrap, .on_body = wasm_on_body, .on_message_complete = wasm_on_message_complete, }; llhttp_t* llhttp_alloc(llhttp_type_t type) { llhttp_t* parser = malloc(sizeof(llhttp_t)); llhttp_init(parser, type, &wasm_settings); return parser; } void llhttp_free(llhttp_t* parser) { free(parser); } #endif // defined(__wasm__) /* Some getters required to get stuff from the parser */ uint8_t llhttp_get_type(llhttp_t* parser) { return parser->type; } uint8_t llhttp_get_http_major(llhttp_t* parser) { return parser->http_major; } uint8_t llhttp_get_http_minor(llhttp_t* parser) { return parser->http_minor; } uint8_t llhttp_get_method(llhttp_t* parser) { return parser->method; } int llhttp_get_status_code(llhttp_t* parser) { return parser->status_code; } uint8_t llhttp_get_upgrade(llhttp_t* parser) { return parser->upgrade; } void llhttp_reset(llhttp_t* parser) { llhttp_type_t type = parser->type; const llhttp_settings_t* settings = parser->settings; void* data = parser->data; uint16_t lenient_flags = parser->lenient_flags; llhttp__internal_init(parser); parser->type = type; parser->settings = (void*) settings; parser->data = data; parser->lenient_flags = lenient_flags; } llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { return llhttp__internal_execute(parser, data, data + len); } void llhttp_settings_init(llhttp_settings_t* settings) { memset(settings, 0, sizeof(*settings)); } llhttp_errno_t llhttp_finish(llhttp_t* parser) { int err; /* We're in an error state. Don't bother doing anything. */ if (parser->error != 0) { return 0; } switch (parser->finish) { case HTTP_FINISH_SAFE_WITH_CB: CALLBACK_MAYBE(parser, on_message_complete); if (err != HPE_OK) return err; /* FALLTHROUGH */ case HTTP_FINISH_SAFE: return HPE_OK; case HTTP_FINISH_UNSAFE: parser->reason = "Invalid EOF state"; return HPE_INVALID_EOF_STATE; default: abort(); } } void llhttp_pause(llhttp_t* parser) { if (parser->error != HPE_OK) { return; } parser->error = HPE_PAUSED; parser->reason = "Paused"; } void llhttp_resume(llhttp_t* parser) { if (parser->error != HPE_PAUSED) { return; } parser->error = 0; } void llhttp_resume_after_upgrade(llhttp_t* parser) { if (parser->error != HPE_PAUSED_UPGRADE) { return; } parser->error = 0; } llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { return parser->error; } const char* llhttp_get_error_reason(const llhttp_t* parser) { return parser->reason; } void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { parser->reason = reason; } const char* llhttp_get_error_pos(const llhttp_t* parser) { return parser->error_pos; } const char* llhttp_errno_name(llhttp_errno_t err) { #define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; switch (err) { HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) default: abort(); } #undef HTTP_ERRNO_GEN } const char* llhttp_method_name(llhttp_method_t method) { #define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; switch (method) { HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) default: abort(); } #undef HTTP_METHOD_GEN } const char* llhttp_status_name(llhttp_status_t status) { #define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; switch (status) { HTTP_STATUS_MAP(HTTP_STATUS_GEN) default: abort(); } #undef HTTP_STATUS_GEN } void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_HEADERS; } else { parser->lenient_flags &= ~LENIENT_HEADERS; } } void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; } else { parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; } } void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_KEEP_ALIVE; } else { parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; } } void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; } else { parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; } } void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_VERSION; } else { parser->lenient_flags &= ~LENIENT_VERSION; } } void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; } else { parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; } } void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; } else { parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; } } void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; } else { parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; } } void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; } else { parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF; } } void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) { if (enabled) { parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; } else { parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE; } } /* Callbacks */ int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_message_begin); return err; } int llhttp__on_protocol(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_protocol, p, endp - p); return err; } int llhttp__on_protocol_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_protocol_complete); return err; } int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); return err; } int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_url_complete); return err; } int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); return err; } int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_status_complete); return err; } int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); return err; } int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_method_complete); return err; } int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); return err; } int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_version_complete); return err; } int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); return err; } int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_header_field_complete); return err; } int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); return err; } int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_header_value_complete); return err; } int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_headers_complete); return err; } int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_message_complete); return err; } int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); return err; } int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_chunk_header); return err; } int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); return err; } int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_chunk_extension_name_complete); return err; } int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { int err; SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); return err; } int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_chunk_extension_value_complete); return err; } int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_chunk_complete); return err; } int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { int err; CALLBACK_MAYBE(s, on_reset); return err; } /* Private */ void llhttp__debug(llhttp_t* s, const char* p, const char* endp, const char* msg) { if (p == endp) { fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, s->flags, msg); } else { fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, s->type, s->flags, *p, msg); } } ================================================ FILE: thirdparty/llhttp/http.c ================================================ #include #ifndef LLHTTP__TEST # include "llhttp.h" #else # define llhttp_t llparse_t #endif /* */ int llhttp_message_needs_eof(const llhttp_t* parser); int llhttp_should_keep_alive(const llhttp_t* parser); int llhttp__before_headers_complete(llhttp_t* parser, const char* p, const char* endp) { /* Set this here so that on_headers_complete() callbacks can see it */ if ((parser->flags & F_UPGRADE) && (parser->flags & F_CONNECTION_UPGRADE)) { /* For responses, "Upgrade: foo" and "Connection: upgrade" are * mandatory only when it is a 101 Switching Protocols response, * otherwise it is purely informational, to announce support. */ parser->upgrade = (parser->type == HTTP_REQUEST || parser->status_code == 101); } else { parser->upgrade = (parser->method == HTTP_CONNECT); } return 0; } /* Return values: * 0 - No body, `restart`, message_complete * 1 - CONNECT request, `restart`, message_complete, and pause * 2 - chunk_size_start * 3 - body_identity * 4 - body_identity_eof * 5 - invalid transfer-encoding for request */ int llhttp__after_headers_complete(llhttp_t* parser, const char* p, const char* endp) { int hasBody; hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; if ( (parser->upgrade && (parser->method == HTTP_CONNECT || (parser->flags & F_SKIPBODY) || !hasBody)) || /* See RFC 2616 section 4.4 - 1xx e.g. Continue */ (parser->type == HTTP_RESPONSE && parser->status_code == 101) ) { /* Exit, the rest of the message is in a different protocol. */ return 1; } if (parser->type == HTTP_RESPONSE && parser->status_code == 100) { /* No body, restart as the message is complete */ return 0; } /* See RFC 2616 section 4.4 */ if ( parser->flags & F_SKIPBODY || /* response to a HEAD request */ ( parser->type == HTTP_RESPONSE && ( parser->status_code == 102 || /* Processing */ parser->status_code == 103 || /* Early Hints */ parser->status_code == 204 || /* No Content */ parser->status_code == 304 /* Not Modified */ ) ) ) { return 0; } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header, prepare for a chunk */ return 2; } else if (parser->flags & F_TRANSFER_ENCODING) { if (parser->type == HTTP_REQUEST && (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { /* RFC 7230 3.3.3 */ /* If a Transfer-Encoding header field * is present in a request and the chunked transfer coding is not * the final encoding, the message body length cannot be determined * reliably; the server MUST respond with the 400 (Bad Request) * status code and then close the connection. */ return 5; } else { /* RFC 7230 3.3.3 */ /* If a Transfer-Encoding header field is present in a response and * the chunked transfer coding is not the final encoding, the * message body length is determined by reading the connection until * it is closed by the server. */ return 4; } } else { if (!(parser->flags & F_CONTENT_LENGTH)) { if (!llhttp_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ return 0; } else { /* Read body until EOF */ return 4; } } else if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ return 0; } else { /* Content-Length header given and non-zero */ return 3; } } } int llhttp__after_message_complete(llhttp_t* parser, const char* p, const char* endp) { int should_keep_alive; should_keep_alive = llhttp_should_keep_alive(parser); parser->finish = HTTP_FINISH_SAFE; parser->flags = 0; /* NOTE: this is ignored in loose parsing mode */ return should_keep_alive; } int llhttp_message_needs_eof(const llhttp_t* parser) { if (parser->type == HTTP_REQUEST) { return 0; } /* See RFC 2616 section 4.4 */ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ parser->status_code == 204 || /* No Content */ parser->status_code == 304 || /* Not Modified */ (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ return 0; } /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ if ((parser->flags & F_TRANSFER_ENCODING) && (parser->flags & F_CHUNKED) == 0) { return 1; } if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { return 0; } return 1; } int llhttp_should_keep_alive(const llhttp_t* parser) { if (parser->http_major > 0 && parser->http_minor > 0) { /* HTTP/1.1 */ if (parser->flags & F_CONNECTION_CLOSE) { return 0; } } else { /* HTTP/1.0 or earlier */ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { return 0; } } return !llhttp_message_needs_eof(parser); } ================================================ FILE: thirdparty/llhttp/llhttp.c ================================================ #include #include #include #ifdef __SSE4_2__ #ifdef _MSC_VER #include #else /* !_MSC_VER */ #include #endif /* _MSC_VER */ #endif /* __SSE4_2__ */ #ifdef __ARM_NEON__ #include #endif /* __ARM_NEON__ */ #ifdef __wasm__ #include #endif /* __wasm__ */ #ifdef _MSC_VER #define ALIGN(n) _declspec(align(n)) #define UNREACHABLE __assume(0) #else /* !_MSC_VER */ #define ALIGN(n) __attribute__((aligned(n))) #define UNREACHABLE __builtin_unreachable() #endif /* _MSC_VER */ #include "llhttp.h" typedef int (*llhttp__internal__span_cb)( llhttp__internal_t*, const char*, const char*); static const unsigned char llparse_blob0[] = { 'o', 'n' }; static const unsigned char llparse_blob1[] = { 'e', 'c', 't', 'i', 'o', 'n' }; static const unsigned char llparse_blob2[] = { 'l', 'o', 's', 'e' }; static const unsigned char llparse_blob3[] = { 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e' }; static const unsigned char llparse_blob4[] = { 'p', 'g', 'r', 'a', 'd', 'e' }; static const unsigned char llparse_blob5[] = { 'c', 'h', 'u', 'n', 'k', 'e', 'd' }; #ifdef __SSE4_2__ static const unsigned char ALIGN(16) llparse_blob6[] = { 0x9, 0x9, ' ', '~', 0x80, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; #endif /* __SSE4_2__ */ #ifdef __SSE4_2__ static const unsigned char ALIGN(16) llparse_blob7[] = { '!', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A', 'Z', '^', 'z', '|', '|' }; #endif /* __SSE4_2__ */ #ifdef __SSE4_2__ static const unsigned char ALIGN(16) llparse_blob8[] = { '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; #endif /* __SSE4_2__ */ static const unsigned char llparse_blob9[] = { 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h' }; static const unsigned char llparse_blob10[] = { 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'i', 'o', 'n' }; static const unsigned char llparse_blob11[] = { 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g' }; static const unsigned char llparse_blob12[] = { 'p', 'g', 'r', 'a', 'd', 'e' }; static const unsigned char llparse_blob13[] = { 'T', 'T', 'P' }; static const unsigned char llparse_blob14[] = { 0xd, 0xa, 0xd, 0xa, 'S', 'M', 0xd, 0xa, 0xd, 0xa }; static const unsigned char llparse_blob15[] = { 'C', 'E' }; static const unsigned char llparse_blob16[] = { 'T', 'S', 'P' }; static const unsigned char llparse_blob17[] = { 'N', 'O', 'U', 'N', 'C', 'E' }; static const unsigned char llparse_blob18[] = { 'I', 'N', 'D' }; static const unsigned char llparse_blob19[] = { 'E', 'C', 'K', 'O', 'U', 'T' }; static const unsigned char llparse_blob20[] = { 'N', 'E', 'C', 'T' }; static const unsigned char llparse_blob21[] = { 'E', 'T', 'E' }; static const unsigned char llparse_blob22[] = { 'C', 'R', 'I', 'B', 'E' }; static const unsigned char llparse_blob23[] = { 'L', 'U', 'S', 'H' }; static const unsigned char llparse_blob24[] = { 'E', 'T' }; static const unsigned char llparse_blob25[] = { 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' }; static const unsigned char llparse_blob26[] = { 'E', 'A', 'D' }; static const unsigned char llparse_blob27[] = { 'N', 'K' }; static const unsigned char llparse_blob28[] = { 'C', 'K' }; static const unsigned char llparse_blob29[] = { 'S', 'E', 'A', 'R', 'C', 'H' }; static const unsigned char llparse_blob30[] = { 'R', 'G', 'E' }; static const unsigned char llparse_blob31[] = { 'C', 'T', 'I', 'V', 'I', 'T', 'Y' }; static const unsigned char llparse_blob32[] = { 'L', 'E', 'N', 'D', 'A', 'R' }; static const unsigned char llparse_blob33[] = { 'V', 'E' }; static const unsigned char llparse_blob34[] = { 'O', 'T', 'I', 'F', 'Y' }; static const unsigned char llparse_blob35[] = { 'P', 'T', 'I', 'O', 'N', 'S' }; static const unsigned char llparse_blob36[] = { 'C', 'H' }; static const unsigned char llparse_blob37[] = { 'S', 'E' }; static const unsigned char llparse_blob38[] = { 'A', 'Y' }; static const unsigned char llparse_blob39[] = { 'S', 'T' }; static const unsigned char llparse_blob40[] = { 'I', 'N', 'D' }; static const unsigned char llparse_blob41[] = { 'A', 'T', 'C', 'H' }; static const unsigned char llparse_blob42[] = { 'G', 'E' }; static const unsigned char llparse_blob43[] = { 'U', 'E', 'R', 'Y' }; static const unsigned char llparse_blob44[] = { 'I', 'N', 'D' }; static const unsigned char llparse_blob45[] = { 'O', 'R', 'D' }; static const unsigned char llparse_blob46[] = { 'I', 'R', 'E', 'C', 'T' }; static const unsigned char llparse_blob47[] = { 'O', 'R', 'T' }; static const unsigned char llparse_blob48[] = { 'R', 'C', 'H' }; static const unsigned char llparse_blob49[] = { 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' }; static const unsigned char llparse_blob50[] = { 'U', 'R', 'C', 'E' }; static const unsigned char llparse_blob51[] = { 'B', 'S', 'C', 'R', 'I', 'B', 'E' }; static const unsigned char llparse_blob52[] = { 'A', 'R', 'D', 'O', 'W', 'N' }; static const unsigned char llparse_blob53[] = { 'A', 'C', 'E' }; static const unsigned char llparse_blob54[] = { 'I', 'N', 'D' }; static const unsigned char llparse_blob55[] = { 'N', 'K' }; static const unsigned char llparse_blob56[] = { 'C', 'K' }; static const unsigned char llparse_blob57[] = { 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E' }; static const unsigned char llparse_blob58[] = { 'T', 'T', 'P' }; static const unsigned char llparse_blob59[] = { 'C', 'E' }; static const unsigned char llparse_blob60[] = { 'T', 'S', 'P' }; static const unsigned char llparse_blob61[] = { 'A', 'D' }; static const unsigned char llparse_blob62[] = { 'T', 'P', '/' }; enum llparse_match_status_e { kMatchComplete, kMatchPause, kMatchMismatch }; typedef enum llparse_match_status_e llparse_match_status_t; struct llparse_match_s { llparse_match_status_t status; const unsigned char* current; }; typedef struct llparse_match_s llparse_match_t; static llparse_match_t llparse__match_sequence_to_lower( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp, const unsigned char* seq, uint32_t seq_len) { uint32_t index; llparse_match_t res; index = s->_index; for (; p != endp; p++) { unsigned char current; current = ((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p)); if (current == seq[index]) { if (++index == seq_len) { res.status = kMatchComplete; goto reset; } } else { res.status = kMatchMismatch; goto reset; } } s->_index = index; res.status = kMatchPause; res.current = p; return res; reset: s->_index = 0; res.current = p; return res; } static llparse_match_t llparse__match_sequence_to_lower_unsafe( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp, const unsigned char* seq, uint32_t seq_len) { uint32_t index; llparse_match_t res; index = s->_index; for (; p != endp; p++) { unsigned char current; current = ((*p) | 0x20); if (current == seq[index]) { if (++index == seq_len) { res.status = kMatchComplete; goto reset; } } else { res.status = kMatchMismatch; goto reset; } } s->_index = index; res.status = kMatchPause; res.current = p; return res; reset: s->_index = 0; res.current = p; return res; } static llparse_match_t llparse__match_sequence_id( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp, const unsigned char* seq, uint32_t seq_len) { uint32_t index; llparse_match_t res; index = s->_index; for (; p != endp; p++) { unsigned char current; current = *p; if (current == seq[index]) { if (++index == seq_len) { res.status = kMatchComplete; goto reset; } } else { res.status = kMatchMismatch; goto reset; } } s->_index = index; res.status = kMatchPause; res.current = p; return res; reset: s->_index = 0; res.current = p; return res; } enum llparse_state_e { s_error, s_n_llhttp__internal__n_closed, s_n_llhttp__internal__n_invoke_llhttp__after_message_complete, s_n_llhttp__internal__n_pause_1, s_n_llhttp__internal__n_invoke_is_equal_upgrade, s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2, s_n_llhttp__internal__n_chunk_data_almost_done_1, s_n_llhttp__internal__n_chunk_data_almost_done, s_n_llhttp__internal__n_consume_content_length, s_n_llhttp__internal__n_span_start_llhttp__on_body, s_n_llhttp__internal__n_invoke_is_equal_content_length, s_n_llhttp__internal__n_chunk_size_almost_done, s_n_llhttp__internal__n_invoke_test_lenient_flags_9, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2, s_n_llhttp__internal__n_invoke_test_lenient_flags_10, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1, s_n_llhttp__internal__n_chunk_extension_quoted_value_done, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2, s_n_llhttp__internal__n_error_30, s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair, s_n_llhttp__internal__n_error_31, s_n_llhttp__internal__n_chunk_extension_quoted_value, s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3, s_n_llhttp__internal__n_error_33, s_n_llhttp__internal__n_chunk_extension_value, s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value, s_n_llhttp__internal__n_error_34, s_n_llhttp__internal__n_chunk_extension_name, s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name, s_n_llhttp__internal__n_chunk_extensions, s_n_llhttp__internal__n_chunk_size_otherwise, s_n_llhttp__internal__n_chunk_size, s_n_llhttp__internal__n_chunk_size_digit, s_n_llhttp__internal__n_invoke_update_content_length_1, s_n_llhttp__internal__n_consume_content_length_1, s_n_llhttp__internal__n_span_start_llhttp__on_body_1, s_n_llhttp__internal__n_eof, s_n_llhttp__internal__n_span_start_llhttp__on_body_2, s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete, s_n_llhttp__internal__n_error_5, s_n_llhttp__internal__n_headers_almost_done, s_n_llhttp__internal__n_header_field_colon_discard_ws, s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete, s_n_llhttp__internal__n_span_start_llhttp__on_header_value, s_n_llhttp__internal__n_header_value_discard_lws, s_n_llhttp__internal__n_header_value_discard_ws_almost_done, s_n_llhttp__internal__n_header_value_lws, s_n_llhttp__internal__n_header_value_almost_done, s_n_llhttp__internal__n_invoke_test_lenient_flags_17, s_n_llhttp__internal__n_header_value_lenient, s_n_llhttp__internal__n_error_54, s_n_llhttp__internal__n_header_value_otherwise, s_n_llhttp__internal__n_header_value_connection_token, s_n_llhttp__internal__n_header_value_connection_ws, s_n_llhttp__internal__n_header_value_connection_1, s_n_llhttp__internal__n_header_value_connection_2, s_n_llhttp__internal__n_header_value_connection_3, s_n_llhttp__internal__n_header_value_connection, s_n_llhttp__internal__n_error_56, s_n_llhttp__internal__n_error_57, s_n_llhttp__internal__n_header_value_content_length_ws, s_n_llhttp__internal__n_header_value_content_length, s_n_llhttp__internal__n_error_59, s_n_llhttp__internal__n_error_58, s_n_llhttp__internal__n_header_value_te_token_ows, s_n_llhttp__internal__n_header_value, s_n_llhttp__internal__n_header_value_te_token, s_n_llhttp__internal__n_header_value_te_chunked_last, s_n_llhttp__internal__n_header_value_te_chunked, s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1, s_n_llhttp__internal__n_header_value_discard_ws, s_n_llhttp__internal__n_invoke_load_header_state, s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete, s_n_llhttp__internal__n_header_field_general_otherwise, s_n_llhttp__internal__n_header_field_general, s_n_llhttp__internal__n_header_field_colon, s_n_llhttp__internal__n_header_field_3, s_n_llhttp__internal__n_header_field_4, s_n_llhttp__internal__n_header_field_2, s_n_llhttp__internal__n_header_field_1, s_n_llhttp__internal__n_header_field_5, s_n_llhttp__internal__n_header_field_6, s_n_llhttp__internal__n_header_field_7, s_n_llhttp__internal__n_header_field, s_n_llhttp__internal__n_span_start_llhttp__on_header_field, s_n_llhttp__internal__n_header_field_start, s_n_llhttp__internal__n_headers_start, s_n_llhttp__internal__n_url_to_http_09, s_n_llhttp__internal__n_url_skip_to_http09, s_n_llhttp__internal__n_url_skip_lf_to_http09_1, s_n_llhttp__internal__n_url_skip_lf_to_http09, s_n_llhttp__internal__n_req_pri_upgrade, s_n_llhttp__internal__n_req_http_complete_crlf, s_n_llhttp__internal__n_req_http_complete, s_n_llhttp__internal__n_invoke_load_method_1, s_n_llhttp__internal__n_invoke_llhttp__on_version_complete, s_n_llhttp__internal__n_error_67, s_n_llhttp__internal__n_error_74, s_n_llhttp__internal__n_req_http_minor, s_n_llhttp__internal__n_error_75, s_n_llhttp__internal__n_req_http_dot, s_n_llhttp__internal__n_error_76, s_n_llhttp__internal__n_req_http_major, s_n_llhttp__internal__n_span_start_llhttp__on_version, s_n_llhttp__internal__n_req_after_protocol, s_n_llhttp__internal__n_invoke_load_method, s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete, s_n_llhttp__internal__n_error_82, s_n_llhttp__internal__n_req_after_http_start_1, s_n_llhttp__internal__n_invoke_load_method_2, s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1, s_n_llhttp__internal__n_req_after_http_start_2, s_n_llhttp__internal__n_invoke_load_method_3, s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2, s_n_llhttp__internal__n_req_after_http_start_3, s_n_llhttp__internal__n_req_after_http_start, s_n_llhttp__internal__n_span_start_llhttp__on_protocol, s_n_llhttp__internal__n_req_http_start, s_n_llhttp__internal__n_url_to_http, s_n_llhttp__internal__n_url_skip_to_http, s_n_llhttp__internal__n_url_fragment, s_n_llhttp__internal__n_span_end_stub_query_3, s_n_llhttp__internal__n_url_query, s_n_llhttp__internal__n_url_query_or_fragment, s_n_llhttp__internal__n_url_path, s_n_llhttp__internal__n_span_start_stub_path_2, s_n_llhttp__internal__n_span_start_stub_path, s_n_llhttp__internal__n_span_start_stub_path_1, s_n_llhttp__internal__n_url_server_with_at, s_n_llhttp__internal__n_url_server, s_n_llhttp__internal__n_url_schema_delim_1, s_n_llhttp__internal__n_url_schema_delim, s_n_llhttp__internal__n_span_end_stub_schema, s_n_llhttp__internal__n_url_schema, s_n_llhttp__internal__n_url_start, s_n_llhttp__internal__n_span_start_llhttp__on_url_1, s_n_llhttp__internal__n_url_entry_normal, s_n_llhttp__internal__n_span_start_llhttp__on_url, s_n_llhttp__internal__n_url_entry_connect, s_n_llhttp__internal__n_req_spaces_before_url, s_n_llhttp__internal__n_req_first_space_before_url, s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1, s_n_llhttp__internal__n_after_start_req_2, s_n_llhttp__internal__n_after_start_req_3, s_n_llhttp__internal__n_after_start_req_1, s_n_llhttp__internal__n_after_start_req_4, s_n_llhttp__internal__n_after_start_req_6, s_n_llhttp__internal__n_after_start_req_8, s_n_llhttp__internal__n_after_start_req_9, s_n_llhttp__internal__n_after_start_req_7, s_n_llhttp__internal__n_after_start_req_5, s_n_llhttp__internal__n_after_start_req_12, s_n_llhttp__internal__n_after_start_req_13, s_n_llhttp__internal__n_after_start_req_11, s_n_llhttp__internal__n_after_start_req_10, s_n_llhttp__internal__n_after_start_req_14, s_n_llhttp__internal__n_after_start_req_17, s_n_llhttp__internal__n_after_start_req_16, s_n_llhttp__internal__n_after_start_req_15, s_n_llhttp__internal__n_after_start_req_18, s_n_llhttp__internal__n_after_start_req_20, s_n_llhttp__internal__n_after_start_req_21, s_n_llhttp__internal__n_after_start_req_19, s_n_llhttp__internal__n_after_start_req_23, s_n_llhttp__internal__n_after_start_req_24, s_n_llhttp__internal__n_after_start_req_26, s_n_llhttp__internal__n_after_start_req_28, s_n_llhttp__internal__n_after_start_req_29, s_n_llhttp__internal__n_after_start_req_27, s_n_llhttp__internal__n_after_start_req_25, s_n_llhttp__internal__n_after_start_req_30, s_n_llhttp__internal__n_after_start_req_22, s_n_llhttp__internal__n_after_start_req_31, s_n_llhttp__internal__n_after_start_req_32, s_n_llhttp__internal__n_after_start_req_35, s_n_llhttp__internal__n_after_start_req_36, s_n_llhttp__internal__n_after_start_req_34, s_n_llhttp__internal__n_after_start_req_37, s_n_llhttp__internal__n_after_start_req_38, s_n_llhttp__internal__n_after_start_req_42, s_n_llhttp__internal__n_after_start_req_43, s_n_llhttp__internal__n_after_start_req_41, s_n_llhttp__internal__n_after_start_req_40, s_n_llhttp__internal__n_after_start_req_39, s_n_llhttp__internal__n_after_start_req_45, s_n_llhttp__internal__n_after_start_req_44, s_n_llhttp__internal__n_after_start_req_33, s_n_llhttp__internal__n_after_start_req_46, s_n_llhttp__internal__n_after_start_req_49, s_n_llhttp__internal__n_after_start_req_50, s_n_llhttp__internal__n_after_start_req_51, s_n_llhttp__internal__n_after_start_req_52, s_n_llhttp__internal__n_after_start_req_48, s_n_llhttp__internal__n_after_start_req_47, s_n_llhttp__internal__n_after_start_req_55, s_n_llhttp__internal__n_after_start_req_57, s_n_llhttp__internal__n_after_start_req_58, s_n_llhttp__internal__n_after_start_req_56, s_n_llhttp__internal__n_after_start_req_54, s_n_llhttp__internal__n_after_start_req_59, s_n_llhttp__internal__n_after_start_req_60, s_n_llhttp__internal__n_after_start_req_53, s_n_llhttp__internal__n_after_start_req_62, s_n_llhttp__internal__n_after_start_req_63, s_n_llhttp__internal__n_after_start_req_61, s_n_llhttp__internal__n_after_start_req_66, s_n_llhttp__internal__n_after_start_req_68, s_n_llhttp__internal__n_after_start_req_69, s_n_llhttp__internal__n_after_start_req_67, s_n_llhttp__internal__n_after_start_req_70, s_n_llhttp__internal__n_after_start_req_65, s_n_llhttp__internal__n_after_start_req_64, s_n_llhttp__internal__n_after_start_req, s_n_llhttp__internal__n_span_start_llhttp__on_method_1, s_n_llhttp__internal__n_res_line_almost_done, s_n_llhttp__internal__n_invoke_test_lenient_flags_30, s_n_llhttp__internal__n_res_status, s_n_llhttp__internal__n_span_start_llhttp__on_status, s_n_llhttp__internal__n_res_status_code_otherwise, s_n_llhttp__internal__n_res_status_code_digit_3, s_n_llhttp__internal__n_res_status_code_digit_2, s_n_llhttp__internal__n_res_status_code_digit_1, s_n_llhttp__internal__n_res_after_version, s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1, s_n_llhttp__internal__n_error_93, s_n_llhttp__internal__n_error_107, s_n_llhttp__internal__n_res_http_minor, s_n_llhttp__internal__n_error_108, s_n_llhttp__internal__n_res_http_dot, s_n_llhttp__internal__n_error_109, s_n_llhttp__internal__n_res_http_major, s_n_llhttp__internal__n_span_start_llhttp__on_version_1, s_n_llhttp__internal__n_res_after_protocol, s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3, s_n_llhttp__internal__n_error_115, s_n_llhttp__internal__n_res_after_start_1, s_n_llhttp__internal__n_res_after_start_2, s_n_llhttp__internal__n_res_after_start_3, s_n_llhttp__internal__n_res_after_start, s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1, s_n_llhttp__internal__n_invoke_llhttp__on_method_complete, s_n_llhttp__internal__n_req_or_res_method_2, s_n_llhttp__internal__n_invoke_update_type_1, s_n_llhttp__internal__n_req_or_res_method_3, s_n_llhttp__internal__n_req_or_res_method_1, s_n_llhttp__internal__n_req_or_res_method, s_n_llhttp__internal__n_span_start_llhttp__on_method, s_n_llhttp__internal__n_start_req_or_res, s_n_llhttp__internal__n_invoke_load_type, s_n_llhttp__internal__n_invoke_update_finish, s_n_llhttp__internal__n_start, }; typedef enum llparse_state_e llparse_state_t; int llhttp__on_method( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_url( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_protocol( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_version( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_header_field( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_header_value( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_body( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_chunk_extension_name( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_chunk_extension_value( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_status( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_load_initial_message_completed( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->initial_message_completed; } int llhttp__on_reset( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_update_finish( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->finish = 2; return 0; } int llhttp__on_message_begin( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_load_type( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->type; } int llhttp__internal__c_store_method( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { state->method = match; return 0; } int llhttp__on_method_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_is_equal_method( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->method == 5; } int llhttp__internal__c_update_http_major( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->http_major = 0; return 0; } int llhttp__internal__c_update_http_minor( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->http_minor = 9; return 0; } int llhttp__on_url_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_test_lenient_flags( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 1) == 1; } int llhttp__internal__c_test_lenient_flags_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 256) == 256; } int llhttp__internal__c_test_flags( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->flags & 128) == 128; } int llhttp__on_chunk_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_message_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_is_equal_upgrade( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->upgrade == 1; } int llhttp__after_message_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_update_content_length( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->content_length = 0; return 0; } int llhttp__internal__c_update_initial_message_completed( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->initial_message_completed = 1; return 0; } int llhttp__internal__c_update_finish_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->finish = 0; return 0; } int llhttp__internal__c_test_lenient_flags_2( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 4) == 4; } int llhttp__internal__c_test_lenient_flags_3( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 32) == 32; } int llhttp__before_headers_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_headers_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__after_headers_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_mul_add_content_length( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { /* Multiplication overflow */ if (state->content_length > 0xffffffffffffffffULL / 16) { return 1; } state->content_length *= 16; /* Addition overflow */ if (match >= 0) { if (state->content_length > 0xffffffffffffffffULL - match) { return 1; } } else { if (state->content_length < 0ULL - match) { return 1; } } state->content_length += match; return 0; } int llhttp__internal__c_test_lenient_flags_4( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 512) == 512; } int llhttp__on_chunk_header( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_is_equal_content_length( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->content_length == 0; } int llhttp__internal__c_test_lenient_flags_7( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 128) == 128; } int llhttp__internal__c_or_flags( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 128; return 0; } int llhttp__internal__c_test_lenient_flags_8( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 64) == 64; } int llhttp__on_chunk_extension_name_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__on_chunk_extension_value_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_update_finish_3( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->finish = 1; return 0; } int llhttp__internal__c_or_flags_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 64; return 0; } int llhttp__internal__c_update_upgrade( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->upgrade = 1; return 0; } int llhttp__internal__c_store_header_state( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { state->header_state = match; return 0; } int llhttp__on_header_field_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_load_header_state( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->header_state; } int llhttp__internal__c_test_flags_4( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->flags & 512) == 512; } int llhttp__internal__c_test_lenient_flags_22( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 2) == 2; } int llhttp__internal__c_or_flags_5( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 1; return 0; } int llhttp__internal__c_update_header_state( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 1; return 0; } int llhttp__on_header_value_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_or_flags_6( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 2; return 0; } int llhttp__internal__c_or_flags_7( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 4; return 0; } int llhttp__internal__c_or_flags_8( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 8; return 0; } int llhttp__internal__c_update_header_state_3( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 6; return 0; } int llhttp__internal__c_update_header_state_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 0; return 0; } int llhttp__internal__c_update_header_state_6( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 5; return 0; } int llhttp__internal__c_update_header_state_7( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 7; return 0; } int llhttp__internal__c_test_flags_2( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->flags & 32) == 32; } int llhttp__internal__c_mul_add_content_length_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { /* Multiplication overflow */ if (state->content_length > 0xffffffffffffffffULL / 10) { return 1; } state->content_length *= 10; /* Addition overflow */ if (match >= 0) { if (state->content_length > 0xffffffffffffffffULL - match) { return 1; } } else { if (state->content_length < 0ULL - match) { return 1; } } state->content_length += match; return 0; } int llhttp__internal__c_or_flags_17( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 32; return 0; } int llhttp__internal__c_test_flags_3( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->flags & 8) == 8; } int llhttp__internal__c_test_lenient_flags_20( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 8) == 8; } int llhttp__internal__c_or_flags_18( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 512; return 0; } int llhttp__internal__c_and_flags( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags &= -9; return 0; } int llhttp__internal__c_update_header_state_8( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->header_state = 8; return 0; } int llhttp__internal__c_or_flags_20( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->flags |= 16; return 0; } int llhttp__on_protocol_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_load_method( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->method; } int llhttp__internal__c_store_http_major( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { state->http_major = match; return 0; } int llhttp__internal__c_store_http_minor( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { state->http_minor = match; return 0; } int llhttp__internal__c_test_lenient_flags_24( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return (state->lenient_flags & 16) == 16; } int llhttp__on_version_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_load_http_major( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->http_major; } int llhttp__internal__c_load_http_minor( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { return state->http_minor; } int llhttp__internal__c_update_status_code( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->status_code = 0; return 0; } int llhttp__internal__c_mul_add_status_code( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp, int match) { /* Multiplication overflow */ if (state->status_code > 0xffff / 10) { return 1; } state->status_code *= 10; /* Addition overflow */ if (match >= 0) { if (state->status_code > 0xffff - match) { return 1; } } else { if (state->status_code < 0 - match) { return 1; } } state->status_code += match; return 0; } int llhttp__on_status_complete( llhttp__internal_t* s, const unsigned char* p, const unsigned char* endp); int llhttp__internal__c_update_type( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->type = 1; return 0; } int llhttp__internal__c_update_type_1( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { state->type = 2; return 0; } int llhttp__internal_init(llhttp__internal_t* state) { memset(state, 0, sizeof(*state)); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start; return 0; } static llparse_state_t llhttp__internal__run( llhttp__internal_t* state, const unsigned char* p, const unsigned char* endp) { int match; switch ((llparse_state_t) (intptr_t) state->_current) { case s_n_llhttp__internal__n_closed: s_n_llhttp__internal__n_closed: { if (p == endp) { return s_n_llhttp__internal__n_closed; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_closed; } case 13: { p++; goto s_n_llhttp__internal__n_closed; } default: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: { switch (llhttp__after_message_complete(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_update_content_length; default: goto s_n_llhttp__internal__n_invoke_update_finish_1; } UNREACHABLE; } case s_n_llhttp__internal__n_pause_1: s_n_llhttp__internal__n_pause_1: { state->error = 0x16; state->reason = "Pause on CONNECT/Upgrade"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_invoke_is_equal_upgrade: s_n_llhttp__internal__n_invoke_is_equal_upgrade: { switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; default: goto s_n_llhttp__internal__n_pause_1; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: { switch (llhttp__on_message_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_is_equal_upgrade; case 21: goto s_n_llhttp__internal__n_pause_13; default: goto s_n_llhttp__internal__n_error_38; } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_data_almost_done_1: s_n_llhttp__internal__n_chunk_data_almost_done_1: { if (p == endp) { return s_n_llhttp__internal__n_chunk_data_almost_done_1; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; } } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_data_almost_done: s_n_llhttp__internal__n_chunk_data_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_chunk_data_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_6; } case 13: { p++; goto s_n_llhttp__internal__n_chunk_data_almost_done_1; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; } } UNREACHABLE; } case s_n_llhttp__internal__n_consume_content_length: s_n_llhttp__internal__n_consume_content_length: { size_t avail; uint64_t need; avail = endp - p; need = state->content_length; if (avail >= need) { p += need; state->content_length = 0; goto s_n_llhttp__internal__n_span_end_llhttp__on_body; } state->content_length -= avail; return s_n_llhttp__internal__n_consume_content_length; UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_body: s_n_llhttp__internal__n_span_start_llhttp__on_body: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_body; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_body; goto s_n_llhttp__internal__n_consume_content_length; UNREACHABLE; } case s_n_llhttp__internal__n_invoke_is_equal_content_length: s_n_llhttp__internal__n_invoke_is_equal_content_length: { switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_start_llhttp__on_body; default: goto s_n_llhttp__internal__n_invoke_or_flags; } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_size_almost_done: s_n_llhttp__internal__n_chunk_size_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_chunk_size_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_8; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_test_lenient_flags_9: s_n_llhttp__internal__n_invoke_test_lenient_flags_9: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_chunk_size_almost_done; default: goto s_n_llhttp__internal__n_error_20; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: { switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_9; case 21: goto s_n_llhttp__internal__n_pause_5; default: goto s_n_llhttp__internal__n_error_19; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: { switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_size_almost_done; case 21: goto s_n_llhttp__internal__n_pause_6; default: goto s_n_llhttp__internal__n_error_21; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: { switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_extensions; case 21: goto s_n_llhttp__internal__n_pause_7; default: goto s_n_llhttp__internal__n_error_22; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_test_lenient_flags_10: s_n_llhttp__internal__n_invoke_test_lenient_flags_10: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_chunk_size_almost_done; default: goto s_n_llhttp__internal__n_error_25; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: { switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10; case 21: goto s_n_llhttp__internal__n_pause_8; default: goto s_n_llhttp__internal__n_error_24; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: { switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_size_almost_done; case 21: goto s_n_llhttp__internal__n_pause_9; default: goto s_n_llhttp__internal__n_error_26; } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extension_quoted_value_done: s_n_llhttp__internal__n_chunk_extension_quoted_value_done: { if (p == endp) { return s_n_llhttp__internal__n_chunk_extension_quoted_value_done; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_11; } case 13: { p++; goto s_n_llhttp__internal__n_chunk_size_almost_done; } case ';': { p++; goto s_n_llhttp__internal__n_chunk_extensions; } default: { goto s_n_llhttp__internal__n_error_29; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: { switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_extension_quoted_value_done; case 21: goto s_n_llhttp__internal__n_pause_10; default: goto s_n_llhttp__internal__n_error_27; } UNREACHABLE; } case s_n_llhttp__internal__n_error_30: s_n_llhttp__internal__n_error_30: { state->error = 0x2; state->reason = "Invalid quoted-pair in chunk extensions quoted value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; if (p == endp) { return s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_chunk_extension_quoted_value; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_31: s_n_llhttp__internal__n_error_31: { state->error = 0x2; state->reason = "Invalid character in chunk extensions quoted value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extension_quoted_value: s_n_llhttp__internal__n_chunk_extension_quoted_value: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; if (p == endp) { return s_n_llhttp__internal__n_chunk_extension_quoted_value; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_chunk_extension_quoted_value; } case 2: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2; } case 3: { p++; goto s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: { switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_extensions; case 21: goto s_n_llhttp__internal__n_pause_11; default: goto s_n_llhttp__internal__n_error_32; } UNREACHABLE; } case s_n_llhttp__internal__n_error_33: s_n_llhttp__internal__n_error_33: { state->error = 0x2; state->reason = "Invalid character in chunk extensions value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extension_value: s_n_llhttp__internal__n_chunk_extension_value: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 5, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_chunk_extension_value; } switch (lookup_table[(uint8_t) *p]) { case 1: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1; } case 3: { p++; goto s_n_llhttp__internal__n_chunk_extension_value; } case 4: { p++; goto s_n_llhttp__internal__n_chunk_extension_quoted_value; } case 5: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_chunk_extension_value; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3; UNREACHABLE; } case s_n_llhttp__internal__n_error_34: s_n_llhttp__internal__n_error_34: { state->error = 0x2; state->reason = "Invalid character in chunk extensions name"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extension_name: s_n_llhttp__internal__n_chunk_extension_name: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 4, 0, 5, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_chunk_extension_name; } switch (lookup_table[(uint8_t) *p]) { case 1: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1; } case 3: { p++; goto s_n_llhttp__internal__n_chunk_extension_name; } case 4: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2; } case 5: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_chunk_extension_name; goto s_n_llhttp__internal__n_chunk_extension_name; UNREACHABLE; } case s_n_llhttp__internal__n_chunk_extensions: s_n_llhttp__internal__n_chunk_extensions: { if (p == endp) { return s_n_llhttp__internal__n_chunk_extensions; } switch (*p) { case 13: { p++; goto s_n_llhttp__internal__n_error_17; } case ' ': { p++; goto s_n_llhttp__internal__n_error_18; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; } } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_size_otherwise: s_n_llhttp__internal__n_chunk_size_otherwise: { if (p == endp) { return s_n_llhttp__internal__n_chunk_size_otherwise; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; } case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_5; } case 13: { p++; goto s_n_llhttp__internal__n_chunk_size_almost_done; } case ' ': { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; } case ';': { p++; goto s_n_llhttp__internal__n_chunk_extensions; } default: { goto s_n_llhttp__internal__n_error_35; } } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_size: s_n_llhttp__internal__n_chunk_size: { if (p == endp) { return s_n_llhttp__internal__n_chunk_size; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'A': { p++; match = 10; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'B': { p++; match = 11; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'C': { p++; match = 12; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'D': { p++; match = 13; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'E': { p++; match = 14; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'F': { p++; match = 15; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'a': { p++; match = 10; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'b': { p++; match = 11; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'c': { p++; match = 12; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'd': { p++; match = 13; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'e': { p++; match = 14; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'f': { p++; match = 15; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } default: { goto s_n_llhttp__internal__n_chunk_size_otherwise; } } UNREACHABLE; } case s_n_llhttp__internal__n_chunk_size_digit: s_n_llhttp__internal__n_chunk_size_digit: { if (p == endp) { return s_n_llhttp__internal__n_chunk_size_digit; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'A': { p++; match = 10; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'B': { p++; match = 11; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'C': { p++; match = 12; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'D': { p++; match = 13; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'E': { p++; match = 14; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'F': { p++; match = 15; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'a': { p++; match = 10; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'b': { p++; match = 11; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'c': { p++; match = 12; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'd': { p++; match = 13; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'e': { p++; match = 14; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } case 'f': { p++; match = 15; goto s_n_llhttp__internal__n_invoke_mul_add_content_length; } default: { goto s_n_llhttp__internal__n_error_37; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_update_content_length_1: s_n_llhttp__internal__n_invoke_update_content_length_1: { switch (llhttp__internal__c_update_content_length(state, p, endp)) { default: goto s_n_llhttp__internal__n_chunk_size_digit; } UNREACHABLE; } case s_n_llhttp__internal__n_consume_content_length_1: s_n_llhttp__internal__n_consume_content_length_1: { size_t avail; uint64_t need; avail = endp - p; need = state->content_length; if (avail >= need) { p += need; state->content_length = 0; goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1; } state->content_length -= avail; return s_n_llhttp__internal__n_consume_content_length_1; UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_body_1: s_n_llhttp__internal__n_span_start_llhttp__on_body_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_body_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_body; goto s_n_llhttp__internal__n_consume_content_length_1; UNREACHABLE; } case s_n_llhttp__internal__n_eof: s_n_llhttp__internal__n_eof: { if (p == endp) { return s_n_llhttp__internal__n_eof; } p++; goto s_n_llhttp__internal__n_eof; UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_body_2: s_n_llhttp__internal__n_span_start_llhttp__on_body_2: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_body_2; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_body; goto s_n_llhttp__internal__n_eof; UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: { switch (llhttp__after_headers_complete(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1; case 2: goto s_n_llhttp__internal__n_invoke_update_content_length_1; case 3: goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1; case 4: goto s_n_llhttp__internal__n_invoke_update_finish_3; case 5: goto s_n_llhttp__internal__n_error_39; default: goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete; } UNREACHABLE; } case s_n_llhttp__internal__n_error_5: s_n_llhttp__internal__n_error_5: { state->error = 0xa; state->reason = "Invalid header field char"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_headers_almost_done: s_n_llhttp__internal__n_headers_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_headers_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_flags_1; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_12; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_colon_discard_ws: s_n_llhttp__internal__n_header_field_colon_discard_ws: { if (p == endp) { return s_n_llhttp__internal__n_header_field_colon_discard_ws; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_header_field_colon_discard_ws; } default: { goto s_n_llhttp__internal__n_header_field_colon; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: { switch (llhttp__on_header_value_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_header_field_start; case 21: goto s_n_llhttp__internal__n_pause_18; default: goto s_n_llhttp__internal__n_error_48; } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_header_value: s_n_llhttp__internal__n_span_start_llhttp__on_header_value: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_header_value; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_header_value; goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value; UNREACHABLE; } case s_n_llhttp__internal__n_header_value_discard_lws: s_n_llhttp__internal__n_header_value_discard_lws: { if (p == endp) { return s_n_llhttp__internal__n_header_value_discard_lws; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; } case ' ': { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; } default: { goto s_n_llhttp__internal__n_invoke_load_header_state_1; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_discard_ws_almost_done: s_n_llhttp__internal__n_header_value_discard_ws_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_header_value_discard_ws_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_header_value_discard_lws; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_16; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_lws: s_n_llhttp__internal__n_header_value_lws: { if (p == endp) { return s_n_llhttp__internal__n_header_value_lws; } switch (*p) { case 9: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; } case ' ': { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; } default: { goto s_n_llhttp__internal__n_invoke_load_header_state_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_almost_done: s_n_llhttp__internal__n_header_value_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_header_value_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_header_value_lws; } default: { goto s_n_llhttp__internal__n_error_53; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_test_lenient_flags_17: s_n_llhttp__internal__n_invoke_test_lenient_flags_17: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_almost_done; default: goto s_n_llhttp__internal__n_error_51; } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_lenient: s_n_llhttp__internal__n_header_value_lenient: { if (p == endp) { return s_n_llhttp__internal__n_header_value_lenient; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4; } case 13: { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5; } default: { p++; goto s_n_llhttp__internal__n_header_value_lenient; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_54: s_n_llhttp__internal__n_error_54: { state->error = 0xa; state->reason = "Invalid header value char"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_header_value_otherwise: s_n_llhttp__internal__n_header_value_otherwise: { if (p == endp) { return s_n_llhttp__internal__n_header_value_otherwise; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; } case 13: { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_19; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection_token: s_n_llhttp__internal__n_header_value_connection_token: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; if (p == endp) { return s_n_llhttp__internal__n_header_value_connection_token; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_header_value_connection_token; } case 2: { p++; goto s_n_llhttp__internal__n_header_value_connection; } default: { goto s_n_llhttp__internal__n_header_value_otherwise; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection_ws: s_n_llhttp__internal__n_header_value_connection_ws: { if (p == endp) { return s_n_llhttp__internal__n_header_value_connection_ws; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_header_value_otherwise; } case 13: { goto s_n_llhttp__internal__n_header_value_otherwise; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_connection_ws; } case ',': { p++; goto s_n_llhttp__internal__n_invoke_load_header_state_6; } default: { goto s_n_llhttp__internal__n_invoke_update_header_state_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection_1: s_n_llhttp__internal__n_header_value_connection_1: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_value_connection_1; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob2, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_invoke_update_header_state_3; } case kMatchPause: { return s_n_llhttp__internal__n_header_value_connection_1; } case kMatchMismatch: { goto s_n_llhttp__internal__n_header_value_connection_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection_2: s_n_llhttp__internal__n_header_value_connection_2: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_value_connection_2; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob3, 9); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_invoke_update_header_state_6; } case kMatchPause: { return s_n_llhttp__internal__n_header_value_connection_2; } case kMatchMismatch: { goto s_n_llhttp__internal__n_header_value_connection_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection_3: s_n_llhttp__internal__n_header_value_connection_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_value_connection_3; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob4, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_invoke_update_header_state_7; } case kMatchPause: { return s_n_llhttp__internal__n_header_value_connection_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_header_value_connection_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_connection: s_n_llhttp__internal__n_header_value_connection: { if (p == endp) { return s_n_llhttp__internal__n_header_value_connection; } switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { case 9: { p++; goto s_n_llhttp__internal__n_header_value_connection; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_connection; } case 'c': { p++; goto s_n_llhttp__internal__n_header_value_connection_1; } case 'k': { p++; goto s_n_llhttp__internal__n_header_value_connection_2; } case 'u': { p++; goto s_n_llhttp__internal__n_header_value_connection_3; } default: { goto s_n_llhttp__internal__n_header_value_connection_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_56: s_n_llhttp__internal__n_error_56: { state->error = 0xb; state->reason = "Content-Length overflow"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_error_57: s_n_llhttp__internal__n_error_57: { state->error = 0xb; state->reason = "Invalid character in Content-Length"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_header_value_content_length_ws: s_n_llhttp__internal__n_header_value_content_length_ws: { if (p == endp) { return s_n_llhttp__internal__n_header_value_content_length_ws; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_invoke_or_flags_17; } case 13: { goto s_n_llhttp__internal__n_invoke_or_flags_17; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_content_length_ws; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_content_length: s_n_llhttp__internal__n_header_value_content_length: { if (p == endp) { return s_n_llhttp__internal__n_header_value_content_length; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; } default: { goto s_n_llhttp__internal__n_header_value_content_length_ws; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_59: s_n_llhttp__internal__n_error_59: { state->error = 0xf; state->reason = "Invalid `Transfer-Encoding` header value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_error_58: s_n_llhttp__internal__n_error_58: { state->error = 0xf; state->reason = "Invalid `Transfer-Encoding` header value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_header_value_te_token_ows: s_n_llhttp__internal__n_header_value_te_token_ows: { if (p == endp) { return s_n_llhttp__internal__n_header_value_te_token_ows; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_header_value_te_token_ows; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_te_token_ows; } default: { goto s_n_llhttp__internal__n_header_value_te_chunked; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value: s_n_llhttp__internal__n_header_value: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; if (p == endp) { return s_n_llhttp__internal__n_header_value; } #ifdef __SSE4_2__ if (endp - p >= 16) { __m128i ranges; __m128i input; int match_len; /* Load input */ input = _mm_loadu_si128((__m128i const*) p); ranges = _mm_loadu_si128((__m128i const*) llparse_blob6); /* Find first character that does not match `ranges` */ match_len = _mm_cmpestri(ranges, 6, input, 16, _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | _SIDD_NEGATIVE_POLARITY); if (match_len != 0) { p += match_len; goto s_n_llhttp__internal__n_header_value; } goto s_n_llhttp__internal__n_header_value_otherwise; } #endif /* __SSE4_2__ */ #ifdef __ARM_NEON__ while (endp - p >= 16) { uint8x16_t input; uint8x16_t single; uint8x16_t mask; uint8x8_t narrow; uint64_t match_mask; int match_len; /* Load input */ input = vld1q_u8(p); /* Find first character that does not match `ranges` */ single = vceqq_u8(input, vdupq_n_u8(0x9)); mask = single; single = vandq_u16( vcgeq_u8(input, vdupq_n_u8(' ')), vcleq_u8(input, vdupq_n_u8('~')) ); mask = vorrq_u16(mask, single); single = vandq_u16( vcgeq_u8(input, vdupq_n_u8(0x80)), vcleq_u8(input, vdupq_n_u8(0xff)) ); mask = vorrq_u16(mask, single); narrow = vshrn_n_u16(mask, 4); match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0); match_len = __builtin_ctzll(match_mask) >> 2; if (match_len != 16) { p += match_len; goto s_n_llhttp__internal__n_header_value_otherwise; } p += 16; } if (p == endp) { return s_n_llhttp__internal__n_header_value; } #endif /* __ARM_NEON__ */ #ifdef __wasm_simd128__ while (endp - p >= 16) { v128_t input; v128_t mask; v128_t single; int match_len; /* Load input */ input = wasm_v128_load(p); /* Find first character that does not match `ranges` */ single = wasm_i8x16_eq(input, wasm_u8x16_const_splat(0x9)); mask = single; single = wasm_v128_and( wasm_i8x16_ge(input, wasm_u8x16_const_splat(' ')), wasm_i8x16_le(input, wasm_u8x16_const_splat('~')) ); mask = wasm_v128_or(mask, single); single = wasm_v128_and( wasm_i8x16_ge(input, wasm_u8x16_const_splat(0x80)), wasm_i8x16_le(input, wasm_u8x16_const_splat(0xff)) ); mask = wasm_v128_or(mask, single); match_len = __builtin_ctz( ~wasm_i8x16_bitmask(mask) ); if (match_len != 16) { p += match_len; goto s_n_llhttp__internal__n_header_value_otherwise; } p += 16; } if (p == endp) { return s_n_llhttp__internal__n_header_value; } #endif /* __wasm_simd128__ */ switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_header_value; } default: { goto s_n_llhttp__internal__n_header_value_otherwise; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_te_token: s_n_llhttp__internal__n_header_value_te_token: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; if (p == endp) { return s_n_llhttp__internal__n_header_value_te_token; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_header_value_te_token; } case 2: { p++; goto s_n_llhttp__internal__n_header_value_te_token_ows; } default: { goto s_n_llhttp__internal__n_invoke_update_header_state_9; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_te_chunked_last: s_n_llhttp__internal__n_header_value_te_chunked_last: { if (p == endp) { return s_n_llhttp__internal__n_header_value_te_chunked_last; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_invoke_update_header_state_8; } case 13: { goto s_n_llhttp__internal__n_invoke_update_header_state_8; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_te_chunked_last; } case ',': { goto s_n_llhttp__internal__n_invoke_load_type_1; } default: { goto s_n_llhttp__internal__n_header_value_te_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_value_te_chunked: s_n_llhttp__internal__n_header_value_te_chunked: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_value_te_chunked; } match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob5, 7); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_header_value_te_chunked_last; } case kMatchPause: { return s_n_llhttp__internal__n_header_value_te_chunked; } case kMatchMismatch: { goto s_n_llhttp__internal__n_header_value_te_token; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_header_value; goto s_n_llhttp__internal__n_invoke_load_header_state_3; UNREACHABLE; } case s_n_llhttp__internal__n_header_value_discard_ws: s_n_llhttp__internal__n_header_value_discard_ws: { if (p == endp) { return s_n_llhttp__internal__n_header_value_discard_ws; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_header_value_discard_ws; } case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_14; } case 13: { p++; goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done; } case ' ': { p++; goto s_n_llhttp__internal__n_header_value_discard_ws; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_header_state: s_n_llhttp__internal__n_invoke_load_header_state: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 2: goto s_n_llhttp__internal__n_invoke_test_flags_4; case 3: goto s_n_llhttp__internal__n_invoke_test_flags_5; default: goto s_n_llhttp__internal__n_header_value_discard_ws; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: { switch (llhttp__on_header_field_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_header_state; case 21: goto s_n_llhttp__internal__n_pause_19; default: goto s_n_llhttp__internal__n_error_45; } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_general_otherwise: s_n_llhttp__internal__n_header_field_general_otherwise: { if (p == endp) { return s_n_llhttp__internal__n_header_field_general_otherwise; } switch (*p) { case ':': { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2; } default: { goto s_n_llhttp__internal__n_error_62; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_general: s_n_llhttp__internal__n_header_field_general: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_header_field_general; } #ifdef __SSE4_2__ if (endp - p >= 16) { __m128i ranges; __m128i input; int match_len; /* Load input */ input = _mm_loadu_si128((__m128i const*) p); ranges = _mm_loadu_si128((__m128i const*) llparse_blob7); /* Find first character that does not match `ranges` */ match_len = _mm_cmpestri(ranges, 16, input, 16, _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | _SIDD_NEGATIVE_POLARITY); if (match_len != 0) { p += match_len; goto s_n_llhttp__internal__n_header_field_general; } ranges = _mm_loadu_si128((__m128i const*) llparse_blob8); /* Find first character that does not match `ranges` */ match_len = _mm_cmpestri(ranges, 2, input, 16, _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | _SIDD_NEGATIVE_POLARITY); if (match_len != 0) { p += match_len; goto s_n_llhttp__internal__n_header_field_general; } goto s_n_llhttp__internal__n_header_field_general_otherwise; } #endif /* __SSE4_2__ */ switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_header_field_general; } default: { goto s_n_llhttp__internal__n_header_field_general_otherwise; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_colon: s_n_llhttp__internal__n_header_field_colon: { if (p == endp) { return s_n_llhttp__internal__n_header_field_colon; } switch (*p) { case ' ': { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_13; } case ':': { goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1; } default: { goto s_n_llhttp__internal__n_invoke_update_header_state_10; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_3: s_n_llhttp__internal__n_header_field_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_3; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob1, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_header_state; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_4: s_n_llhttp__internal__n_header_field_4: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_4; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob9, 10); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_header_state; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_4; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_2: s_n_llhttp__internal__n_header_field_2: { if (p == endp) { return s_n_llhttp__internal__n_header_field_2; } switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { case 'n': { p++; goto s_n_llhttp__internal__n_header_field_3; } case 't': { p++; goto s_n_llhttp__internal__n_header_field_4; } default: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_1: s_n_llhttp__internal__n_header_field_1: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_1; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob0, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_header_field_2; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_1; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_5: s_n_llhttp__internal__n_header_field_5: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_5; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob10, 15); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_header_state; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_5; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_6: s_n_llhttp__internal__n_header_field_6: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_6; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob11, 16); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_header_state; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_6; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field_7: s_n_llhttp__internal__n_header_field_7: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_header_field_7; } match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob12, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_header_state; } case kMatchPause: { return s_n_llhttp__internal__n_header_field_7; } case kMatchMismatch: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_header_field: s_n_llhttp__internal__n_header_field: { if (p == endp) { return s_n_llhttp__internal__n_header_field; } switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { case 'c': { p++; goto s_n_llhttp__internal__n_header_field_1; } case 'p': { p++; goto s_n_llhttp__internal__n_header_field_5; } case 't': { p++; goto s_n_llhttp__internal__n_header_field_6; } case 'u': { p++; goto s_n_llhttp__internal__n_header_field_7; } default: { goto s_n_llhttp__internal__n_invoke_update_header_state_11; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_header_field: s_n_llhttp__internal__n_span_start_llhttp__on_header_field: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_header_field; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_header_field; goto s_n_llhttp__internal__n_header_field; UNREACHABLE; } case s_n_llhttp__internal__n_header_field_start: s_n_llhttp__internal__n_header_field_start: { if (p == endp) { return s_n_llhttp__internal__n_header_field_start; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_1; } case 13: { p++; goto s_n_llhttp__internal__n_headers_almost_done; } case ':': { goto s_n_llhttp__internal__n_error_44; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field; } } UNREACHABLE; } case s_n_llhttp__internal__n_headers_start: s_n_llhttp__internal__n_headers_start: { if (p == endp) { return s_n_llhttp__internal__n_headers_start; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags; } default: { goto s_n_llhttp__internal__n_header_field_start; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_to_http_09: s_n_llhttp__internal__n_url_to_http_09: { if (p == endp) { return s_n_llhttp__internal__n_url_to_http_09; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { goto s_n_llhttp__internal__n_invoke_update_http_major; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_skip_to_http09: s_n_llhttp__internal__n_url_skip_to_http09: { if (p == endp) { return s_n_llhttp__internal__n_url_skip_to_http09; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { p++; goto s_n_llhttp__internal__n_url_to_http_09; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_skip_lf_to_http09_1: s_n_llhttp__internal__n_url_skip_lf_to_http09_1: { if (p == endp) { return s_n_llhttp__internal__n_url_skip_lf_to_http09_1; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_url_to_http_09; } default: { goto s_n_llhttp__internal__n_error_63; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_skip_lf_to_http09: s_n_llhttp__internal__n_url_skip_lf_to_http09: { if (p == endp) { return s_n_llhttp__internal__n_url_skip_lf_to_http09; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } case 13: { p++; goto s_n_llhttp__internal__n_url_skip_lf_to_http09_1; } default: { goto s_n_llhttp__internal__n_error_63; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_pri_upgrade: s_n_llhttp__internal__n_req_pri_upgrade: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_pri_upgrade; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob14, 10); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_error_72; } case kMatchPause: { return s_n_llhttp__internal__n_req_pri_upgrade; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_73; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_http_complete_crlf: s_n_llhttp__internal__n_req_http_complete_crlf: { if (p == endp) { return s_n_llhttp__internal__n_req_http_complete_crlf; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_headers_start; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_26; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_http_complete: s_n_llhttp__internal__n_req_http_complete: { if (p == endp) { return s_n_llhttp__internal__n_req_http_complete; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_25; } case 13: { p++; goto s_n_llhttp__internal__n_req_http_complete_crlf; } default: { goto s_n_llhttp__internal__n_error_71; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_method_1: s_n_llhttp__internal__n_invoke_load_method_1: { switch (llhttp__internal__c_load_method(state, p, endp)) { case 34: goto s_n_llhttp__internal__n_req_pri_upgrade; default: goto s_n_llhttp__internal__n_req_http_complete; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: { switch (llhttp__on_version_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_method_1; case 21: goto s_n_llhttp__internal__n_pause_21; default: goto s_n_llhttp__internal__n_error_68; } UNREACHABLE; } case s_n_llhttp__internal__n_error_67: s_n_llhttp__internal__n_error_67: { state->error = 0x9; state->reason = "Invalid HTTP version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_error_74: s_n_llhttp__internal__n_error_74: { state->error = 0x9; state->reason = "Invalid minor version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_req_http_minor: s_n_llhttp__internal__n_req_http_minor: { if (p == endp) { return s_n_llhttp__internal__n_req_http_minor; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_store_http_minor; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_store_http_minor; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_2; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_75: s_n_llhttp__internal__n_error_75: { state->error = 0x9; state->reason = "Expected dot"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_req_http_dot: s_n_llhttp__internal__n_req_http_dot: { if (p == endp) { return s_n_llhttp__internal__n_req_http_dot; } switch (*p) { case '.': { p++; goto s_n_llhttp__internal__n_req_http_minor; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_76: s_n_llhttp__internal__n_error_76: { state->error = 0x9; state->reason = "Invalid major version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_req_http_major: s_n_llhttp__internal__n_req_http_major: { if (p == endp) { return s_n_llhttp__internal__n_req_http_major; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_store_http_major; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_store_http_major; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_4; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_version: s_n_llhttp__internal__n_span_start_llhttp__on_version: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_version; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_version; goto s_n_llhttp__internal__n_req_http_major; UNREACHABLE; } case s_n_llhttp__internal__n_req_after_protocol: s_n_llhttp__internal__n_req_after_protocol: { if (p == endp) { return s_n_llhttp__internal__n_req_after_protocol; } switch (*p) { case '/': { p++; goto s_n_llhttp__internal__n_span_start_llhttp__on_version; } default: { goto s_n_llhttp__internal__n_error_77; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_method: s_n_llhttp__internal__n_invoke_load_method: { switch (llhttp__internal__c_load_method(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_req_after_protocol; case 1: goto s_n_llhttp__internal__n_req_after_protocol; case 2: goto s_n_llhttp__internal__n_req_after_protocol; case 3: goto s_n_llhttp__internal__n_req_after_protocol; case 4: goto s_n_llhttp__internal__n_req_after_protocol; case 5: goto s_n_llhttp__internal__n_req_after_protocol; case 6: goto s_n_llhttp__internal__n_req_after_protocol; case 7: goto s_n_llhttp__internal__n_req_after_protocol; case 8: goto s_n_llhttp__internal__n_req_after_protocol; case 9: goto s_n_llhttp__internal__n_req_after_protocol; case 10: goto s_n_llhttp__internal__n_req_after_protocol; case 11: goto s_n_llhttp__internal__n_req_after_protocol; case 12: goto s_n_llhttp__internal__n_req_after_protocol; case 13: goto s_n_llhttp__internal__n_req_after_protocol; case 14: goto s_n_llhttp__internal__n_req_after_protocol; case 15: goto s_n_llhttp__internal__n_req_after_protocol; case 16: goto s_n_llhttp__internal__n_req_after_protocol; case 17: goto s_n_llhttp__internal__n_req_after_protocol; case 18: goto s_n_llhttp__internal__n_req_after_protocol; case 19: goto s_n_llhttp__internal__n_req_after_protocol; case 20: goto s_n_llhttp__internal__n_req_after_protocol; case 21: goto s_n_llhttp__internal__n_req_after_protocol; case 22: goto s_n_llhttp__internal__n_req_after_protocol; case 23: goto s_n_llhttp__internal__n_req_after_protocol; case 24: goto s_n_llhttp__internal__n_req_after_protocol; case 25: goto s_n_llhttp__internal__n_req_after_protocol; case 26: goto s_n_llhttp__internal__n_req_after_protocol; case 27: goto s_n_llhttp__internal__n_req_after_protocol; case 28: goto s_n_llhttp__internal__n_req_after_protocol; case 29: goto s_n_llhttp__internal__n_req_after_protocol; case 30: goto s_n_llhttp__internal__n_req_after_protocol; case 31: goto s_n_llhttp__internal__n_req_after_protocol; case 32: goto s_n_llhttp__internal__n_req_after_protocol; case 33: goto s_n_llhttp__internal__n_req_after_protocol; case 34: goto s_n_llhttp__internal__n_req_after_protocol; case 46: goto s_n_llhttp__internal__n_req_after_protocol; default: goto s_n_llhttp__internal__n_error_66; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete: s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete: { switch (llhttp__on_protocol_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_method; case 21: goto s_n_llhttp__internal__n_pause_22; default: goto s_n_llhttp__internal__n_error_65; } UNREACHABLE; } case s_n_llhttp__internal__n_error_82: s_n_llhttp__internal__n_error_82: { state->error = 0x8; state->reason = "Expected HTTP/, RTSP/ or ICE/"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_req_after_http_start_1: s_n_llhttp__internal__n_req_after_http_start_1: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_after_http_start_1; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob13, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol; } case kMatchPause: { return s_n_llhttp__internal__n_req_after_http_start_1; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_method_2: s_n_llhttp__internal__n_invoke_load_method_2: { switch (llhttp__internal__c_load_method(state, p, endp)) { case 33: goto s_n_llhttp__internal__n_req_after_protocol; default: goto s_n_llhttp__internal__n_error_79; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1: s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1: { switch (llhttp__on_protocol_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_method_2; case 21: goto s_n_llhttp__internal__n_pause_23; default: goto s_n_llhttp__internal__n_error_78; } UNREACHABLE; } case s_n_llhttp__internal__n_req_after_http_start_2: s_n_llhttp__internal__n_req_after_http_start_2: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_after_http_start_2; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_1; } case kMatchPause: { return s_n_llhttp__internal__n_req_after_http_start_2; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_method_3: s_n_llhttp__internal__n_invoke_load_method_3: { switch (llhttp__internal__c_load_method(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_req_after_protocol; case 3: goto s_n_llhttp__internal__n_req_after_protocol; case 6: goto s_n_llhttp__internal__n_req_after_protocol; case 35: goto s_n_llhttp__internal__n_req_after_protocol; case 36: goto s_n_llhttp__internal__n_req_after_protocol; case 37: goto s_n_llhttp__internal__n_req_after_protocol; case 38: goto s_n_llhttp__internal__n_req_after_protocol; case 39: goto s_n_llhttp__internal__n_req_after_protocol; case 40: goto s_n_llhttp__internal__n_req_after_protocol; case 41: goto s_n_llhttp__internal__n_req_after_protocol; case 42: goto s_n_llhttp__internal__n_req_after_protocol; case 43: goto s_n_llhttp__internal__n_req_after_protocol; case 44: goto s_n_llhttp__internal__n_req_after_protocol; case 45: goto s_n_llhttp__internal__n_req_after_protocol; default: goto s_n_llhttp__internal__n_error_81; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2: s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2: { switch (llhttp__on_protocol_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_method_3; case 21: goto s_n_llhttp__internal__n_pause_24; default: goto s_n_llhttp__internal__n_error_80; } UNREACHABLE; } case s_n_llhttp__internal__n_req_after_http_start_3: s_n_llhttp__internal__n_req_after_http_start_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_after_http_start_3; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_2; } case kMatchPause: { return s_n_llhttp__internal__n_req_after_http_start_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_after_http_start: s_n_llhttp__internal__n_req_after_http_start: { if (p == endp) { return s_n_llhttp__internal__n_req_after_http_start; } switch (*p) { case 'H': { p++; goto s_n_llhttp__internal__n_req_after_http_start_1; } case 'I': { p++; goto s_n_llhttp__internal__n_req_after_http_start_2; } case 'R': { p++; goto s_n_llhttp__internal__n_req_after_http_start_3; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_protocol: s_n_llhttp__internal__n_span_start_llhttp__on_protocol: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_protocol; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_protocol; goto s_n_llhttp__internal__n_req_after_http_start; UNREACHABLE; } case s_n_llhttp__internal__n_req_http_start: s_n_llhttp__internal__n_req_http_start: { if (p == endp) { return s_n_llhttp__internal__n_req_http_start; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_req_http_start; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_protocol; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_to_http: s_n_llhttp__internal__n_url_to_http: { if (p == endp) { return s_n_llhttp__internal__n_url_to_http; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_skip_to_http: s_n_llhttp__internal__n_url_skip_to_http: { if (p == endp) { return s_n_llhttp__internal__n_url_skip_to_http; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { p++; goto s_n_llhttp__internal__n_url_to_http; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_fragment: s_n_llhttp__internal__n_url_fragment: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_fragment; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6; } case 3: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7; } case 4: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8; } case 5: { p++; goto s_n_llhttp__internal__n_url_fragment; } default: { goto s_n_llhttp__internal__n_error_83; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_end_stub_query_3: s_n_llhttp__internal__n_span_end_stub_query_3: { if (p == endp) { return s_n_llhttp__internal__n_span_end_stub_query_3; } p++; goto s_n_llhttp__internal__n_url_fragment; UNREACHABLE; } case s_n_llhttp__internal__n_url_query: s_n_llhttp__internal__n_url_query: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_query; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9; } case 3: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10; } case 4: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11; } case 5: { p++; goto s_n_llhttp__internal__n_url_query; } case 6: { goto s_n_llhttp__internal__n_span_end_stub_query_3; } default: { goto s_n_llhttp__internal__n_error_84; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_query_or_fragment: s_n_llhttp__internal__n_url_query_or_fragment: { if (p == endp) { return s_n_llhttp__internal__n_url_query_or_fragment; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 10: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } case 13: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4; } case ' ': { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5; } case '#': { p++; goto s_n_llhttp__internal__n_url_fragment; } case '?': { p++; goto s_n_llhttp__internal__n_url_query; } default: { goto s_n_llhttp__internal__n_error_85; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_path: s_n_llhttp__internal__n_url_path: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_path; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { p++; goto s_n_llhttp__internal__n_url_path; } default: { goto s_n_llhttp__internal__n_url_query_or_fragment; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_stub_path_2: s_n_llhttp__internal__n_span_start_stub_path_2: { if (p == endp) { return s_n_llhttp__internal__n_span_start_stub_path_2; } p++; goto s_n_llhttp__internal__n_url_path; UNREACHABLE; } case s_n_llhttp__internal__n_span_start_stub_path: s_n_llhttp__internal__n_span_start_stub_path: { if (p == endp) { return s_n_llhttp__internal__n_span_start_stub_path; } p++; goto s_n_llhttp__internal__n_url_path; UNREACHABLE; } case s_n_llhttp__internal__n_span_start_stub_path_1: s_n_llhttp__internal__n_span_start_stub_path_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_stub_path_1; } p++; goto s_n_llhttp__internal__n_url_path; UNREACHABLE; } case s_n_llhttp__internal__n_url_server_with_at: s_n_llhttp__internal__n_url_server_with_at: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_server_with_at; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12; } case 3: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13; } case 4: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14; } case 5: { p++; goto s_n_llhttp__internal__n_url_server; } case 6: { goto s_n_llhttp__internal__n_span_start_stub_path_1; } case 7: { p++; goto s_n_llhttp__internal__n_url_query; } case 8: { p++; goto s_n_llhttp__internal__n_error_86; } default: { goto s_n_llhttp__internal__n_error_87; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_server: s_n_llhttp__internal__n_url_server: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_server; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url; } case 3: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1; } case 4: { goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2; } case 5: { p++; goto s_n_llhttp__internal__n_url_server; } case 6: { goto s_n_llhttp__internal__n_span_start_stub_path; } case 7: { p++; goto s_n_llhttp__internal__n_url_query; } case 8: { p++; goto s_n_llhttp__internal__n_url_server_with_at; } default: { goto s_n_llhttp__internal__n_error_88; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_schema_delim_1: s_n_llhttp__internal__n_url_schema_delim_1: { if (p == endp) { return s_n_llhttp__internal__n_url_schema_delim_1; } switch (*p) { case '/': { p++; goto s_n_llhttp__internal__n_url_server; } default: { goto s_n_llhttp__internal__n_error_89; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_schema_delim: s_n_llhttp__internal__n_url_schema_delim: { if (p == endp) { return s_n_llhttp__internal__n_url_schema_delim; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 10: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } case 13: { p++; goto s_n_llhttp__internal__n_error_2; } case ' ': { p++; goto s_n_llhttp__internal__n_error_2; } case '/': { p++; goto s_n_llhttp__internal__n_url_schema_delim_1; } default: { goto s_n_llhttp__internal__n_error_89; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_end_stub_schema: s_n_llhttp__internal__n_span_end_stub_schema: { if (p == endp) { return s_n_llhttp__internal__n_span_end_stub_schema; } p++; goto s_n_llhttp__internal__n_url_schema_delim; UNREACHABLE; } case s_n_llhttp__internal__n_url_schema: s_n_llhttp__internal__n_url_schema: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_schema; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_end_stub_schema; } case 3: { p++; goto s_n_llhttp__internal__n_url_schema; } default: { goto s_n_llhttp__internal__n_error_90; } } UNREACHABLE; } case s_n_llhttp__internal__n_url_start: s_n_llhttp__internal__n_url_start: { static uint8_t lookup_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (p == endp) { return s_n_llhttp__internal__n_url_start; } switch (lookup_table[(uint8_t) *p]) { case 1: { p++; goto s_n_llhttp__internal__n_error_2; } case 2: { goto s_n_llhttp__internal__n_span_start_stub_path_2; } case 3: { goto s_n_llhttp__internal__n_url_schema; } default: { goto s_n_llhttp__internal__n_error_91; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_url_1: s_n_llhttp__internal__n_span_start_llhttp__on_url_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_url_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_url; goto s_n_llhttp__internal__n_url_start; UNREACHABLE; } case s_n_llhttp__internal__n_url_entry_normal: s_n_llhttp__internal__n_url_entry_normal: { if (p == endp) { return s_n_llhttp__internal__n_url_entry_normal; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_url: s_n_llhttp__internal__n_span_start_llhttp__on_url: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_url; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_url; goto s_n_llhttp__internal__n_url_server; UNREACHABLE; } case s_n_llhttp__internal__n_url_entry_connect: s_n_llhttp__internal__n_url_entry_connect: { if (p == endp) { return s_n_llhttp__internal__n_url_entry_connect; } switch (*p) { case 9: { p++; goto s_n_llhttp__internal__n_error_2; } case 12: { p++; goto s_n_llhttp__internal__n_error_2; } default: { goto s_n_llhttp__internal__n_span_start_llhttp__on_url; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_spaces_before_url: s_n_llhttp__internal__n_req_spaces_before_url: { if (p == endp) { return s_n_llhttp__internal__n_req_spaces_before_url; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_req_spaces_before_url; } default: { goto s_n_llhttp__internal__n_invoke_is_equal_method; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_first_space_before_url: s_n_llhttp__internal__n_req_first_space_before_url: { if (p == endp) { return s_n_llhttp__internal__n_req_first_space_before_url; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_req_spaces_before_url; } default: { goto s_n_llhttp__internal__n_error_92; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: { switch (llhttp__on_method_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_req_first_space_before_url; case 21: goto s_n_llhttp__internal__n_pause_29; default: goto s_n_llhttp__internal__n_error_111; } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_2: s_n_llhttp__internal__n_after_start_req_2: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_2; } switch (*p) { case 'L': { p++; match = 19; goto s_n_llhttp__internal__n_invoke_store_method_1; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_3: s_n_llhttp__internal__n_after_start_req_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_3; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 36; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_1: s_n_llhttp__internal__n_after_start_req_1: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_1; } switch (*p) { case 'C': { p++; goto s_n_llhttp__internal__n_after_start_req_2; } case 'N': { p++; goto s_n_llhttp__internal__n_after_start_req_3; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_4: s_n_llhttp__internal__n_after_start_req_4: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_4; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 16; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_4; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_6: s_n_llhttp__internal__n_after_start_req_6: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_6; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 22; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_6; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_8: s_n_llhttp__internal__n_after_start_req_8: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_8; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 5; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_8; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_9: s_n_llhttp__internal__n_after_start_req_9: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_9; } switch (*p) { case 'Y': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_store_method_1; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_7: s_n_llhttp__internal__n_after_start_req_7: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_7; } switch (*p) { case 'N': { p++; goto s_n_llhttp__internal__n_after_start_req_8; } case 'P': { p++; goto s_n_llhttp__internal__n_after_start_req_9; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_5: s_n_llhttp__internal__n_after_start_req_5: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_5; } switch (*p) { case 'H': { p++; goto s_n_llhttp__internal__n_after_start_req_6; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_7; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_12: s_n_llhttp__internal__n_after_start_req_12: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_12; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 0; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_12; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_13: s_n_llhttp__internal__n_after_start_req_13: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_13; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 5); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 35; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_13; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_11: s_n_llhttp__internal__n_after_start_req_11: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_11; } switch (*p) { case 'L': { p++; goto s_n_llhttp__internal__n_after_start_req_12; } case 'S': { p++; goto s_n_llhttp__internal__n_after_start_req_13; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_10: s_n_llhttp__internal__n_after_start_req_10: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_10; } switch (*p) { case 'E': { p++; goto s_n_llhttp__internal__n_after_start_req_11; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_14: s_n_llhttp__internal__n_after_start_req_14: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_14; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 45; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_14; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_17: s_n_llhttp__internal__n_after_start_req_17: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_17; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 9); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 41; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_17; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_16: s_n_llhttp__internal__n_after_start_req_16: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_16; } switch (*p) { case '_': { p++; goto s_n_llhttp__internal__n_after_start_req_17; } default: { match = 1; goto s_n_llhttp__internal__n_invoke_store_method_1; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_15: s_n_llhttp__internal__n_after_start_req_15: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_15; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_after_start_req_16; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_15; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_18: s_n_llhttp__internal__n_after_start_req_18: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_18; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_18; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_20: s_n_llhttp__internal__n_after_start_req_20: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_20; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 31; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_20; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_21: s_n_llhttp__internal__n_after_start_req_21: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_21; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 9; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_21; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_19: s_n_llhttp__internal__n_after_start_req_19: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_19; } switch (*p) { case 'I': { p++; goto s_n_llhttp__internal__n_after_start_req_20; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_21; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_23: s_n_llhttp__internal__n_after_start_req_23: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_23; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 24; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_23; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_24: s_n_llhttp__internal__n_after_start_req_24: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_24; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 23; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_24; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_26: s_n_llhttp__internal__n_after_start_req_26: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_26; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 7); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 21; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_26; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_28: s_n_llhttp__internal__n_after_start_req_28: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_28; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 30; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_28; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_29: s_n_llhttp__internal__n_after_start_req_29: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_29; } switch (*p) { case 'L': { p++; match = 10; goto s_n_llhttp__internal__n_invoke_store_method_1; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_27: s_n_llhttp__internal__n_after_start_req_27: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_27; } switch (*p) { case 'A': { p++; goto s_n_llhttp__internal__n_after_start_req_28; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_29; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_25: s_n_llhttp__internal__n_after_start_req_25: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_25; } switch (*p) { case 'A': { p++; goto s_n_llhttp__internal__n_after_start_req_26; } case 'C': { p++; goto s_n_llhttp__internal__n_after_start_req_27; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_30: s_n_llhttp__internal__n_after_start_req_30: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_30; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 11; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_30; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_22: s_n_llhttp__internal__n_after_start_req_22: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_22; } switch (*p) { case '-': { p++; goto s_n_llhttp__internal__n_after_start_req_23; } case 'E': { p++; goto s_n_llhttp__internal__n_after_start_req_24; } case 'K': { p++; goto s_n_llhttp__internal__n_after_start_req_25; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_30; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_31: s_n_llhttp__internal__n_after_start_req_31: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_31; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 5); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 25; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_31; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_32: s_n_llhttp__internal__n_after_start_req_32: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_32; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 6; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_32; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_35: s_n_llhttp__internal__n_after_start_req_35: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_35; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 28; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_35; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_36: s_n_llhttp__internal__n_after_start_req_36: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_36; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 39; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_36; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_34: s_n_llhttp__internal__n_after_start_req_34: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_34; } switch (*p) { case 'T': { p++; goto s_n_llhttp__internal__n_after_start_req_35; } case 'U': { p++; goto s_n_llhttp__internal__n_after_start_req_36; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_37: s_n_llhttp__internal__n_after_start_req_37: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_37; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 38; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_37; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_38: s_n_llhttp__internal__n_after_start_req_38: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_38; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_38; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_42: s_n_llhttp__internal__n_after_start_req_42: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_42; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 12; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_42; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_43: s_n_llhttp__internal__n_after_start_req_43: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_43; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 13; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_43; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_41: s_n_llhttp__internal__n_after_start_req_41: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_41; } switch (*p) { case 'F': { p++; goto s_n_llhttp__internal__n_after_start_req_42; } case 'P': { p++; goto s_n_llhttp__internal__n_after_start_req_43; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_40: s_n_llhttp__internal__n_after_start_req_40: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_40; } switch (*p) { case 'P': { p++; goto s_n_llhttp__internal__n_after_start_req_41; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_39: s_n_llhttp__internal__n_after_start_req_39: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_39; } switch (*p) { case 'I': { p++; match = 34; goto s_n_llhttp__internal__n_invoke_store_method_1; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_40; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_45: s_n_llhttp__internal__n_after_start_req_45: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_45; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 29; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_45; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_44: s_n_llhttp__internal__n_after_start_req_44: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_44; } switch (*p) { case 'R': { p++; goto s_n_llhttp__internal__n_after_start_req_45; } case 'T': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_method_1; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_33: s_n_llhttp__internal__n_after_start_req_33: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_33; } switch (*p) { case 'A': { p++; goto s_n_llhttp__internal__n_after_start_req_34; } case 'L': { p++; goto s_n_llhttp__internal__n_after_start_req_37; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_38; } case 'R': { p++; goto s_n_llhttp__internal__n_after_start_req_39; } case 'U': { p++; goto s_n_llhttp__internal__n_after_start_req_44; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_46: s_n_llhttp__internal__n_after_start_req_46: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_46; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 46; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_46; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_49: s_n_llhttp__internal__n_after_start_req_49: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_49; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 17; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_49; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_50: s_n_llhttp__internal__n_after_start_req_50: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_50; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 44; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_50; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_51: s_n_llhttp__internal__n_after_start_req_51: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_51; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 5); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 43; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_51; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_52: s_n_llhttp__internal__n_after_start_req_52: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_52; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 20; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_52; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_48: s_n_llhttp__internal__n_after_start_req_48: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_48; } switch (*p) { case 'B': { p++; goto s_n_llhttp__internal__n_after_start_req_49; } case 'C': { p++; goto s_n_llhttp__internal__n_after_start_req_50; } case 'D': { p++; goto s_n_llhttp__internal__n_after_start_req_51; } case 'P': { p++; goto s_n_llhttp__internal__n_after_start_req_52; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_47: s_n_llhttp__internal__n_after_start_req_47: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_47; } switch (*p) { case 'E': { p++; goto s_n_llhttp__internal__n_after_start_req_48; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_55: s_n_llhttp__internal__n_after_start_req_55: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_55; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 14; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_55; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_57: s_n_llhttp__internal__n_after_start_req_57: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_57; } switch (*p) { case 'P': { p++; match = 37; goto s_n_llhttp__internal__n_invoke_store_method_1; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_58: s_n_llhttp__internal__n_after_start_req_58: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_58; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 9); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 42; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_58; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_56: s_n_llhttp__internal__n_after_start_req_56: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_56; } switch (*p) { case 'U': { p++; goto s_n_llhttp__internal__n_after_start_req_57; } case '_': { p++; goto s_n_llhttp__internal__n_after_start_req_58; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_54: s_n_llhttp__internal__n_after_start_req_54: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_54; } switch (*p) { case 'A': { p++; goto s_n_llhttp__internal__n_after_start_req_55; } case 'T': { p++; goto s_n_llhttp__internal__n_after_start_req_56; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_59: s_n_llhttp__internal__n_after_start_req_59: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_59; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 4); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 33; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_59; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_60: s_n_llhttp__internal__n_after_start_req_60: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_60; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob51, 7); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 26; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_60; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_53: s_n_llhttp__internal__n_after_start_req_53: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_53; } switch (*p) { case 'E': { p++; goto s_n_llhttp__internal__n_after_start_req_54; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_59; } case 'U': { p++; goto s_n_llhttp__internal__n_after_start_req_60; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_62: s_n_llhttp__internal__n_after_start_req_62: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_62; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob52, 6); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 40; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_62; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_63: s_n_llhttp__internal__n_after_start_req_63: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_63; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob53, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 7; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_63; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_61: s_n_llhttp__internal__n_after_start_req_61: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_61; } switch (*p) { case 'E': { p++; goto s_n_llhttp__internal__n_after_start_req_62; } case 'R': { p++; goto s_n_llhttp__internal__n_after_start_req_63; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_66: s_n_llhttp__internal__n_after_start_req_66: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_66; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob54, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 18; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_66; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_68: s_n_llhttp__internal__n_after_start_req_68: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_68; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob55, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 32; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_68; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_69: s_n_llhttp__internal__n_after_start_req_69: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_69; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob56, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 15; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_69; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_67: s_n_llhttp__internal__n_after_start_req_67: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_67; } switch (*p) { case 'I': { p++; goto s_n_llhttp__internal__n_after_start_req_68; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_69; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_70: s_n_llhttp__internal__n_after_start_req_70: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_after_start_req_70; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob57, 8); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 27; goto s_n_llhttp__internal__n_invoke_store_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_after_start_req_70; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_65: s_n_llhttp__internal__n_after_start_req_65: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_65; } switch (*p) { case 'B': { p++; goto s_n_llhttp__internal__n_after_start_req_66; } case 'L': { p++; goto s_n_llhttp__internal__n_after_start_req_67; } case 'S': { p++; goto s_n_llhttp__internal__n_after_start_req_70; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req_64: s_n_llhttp__internal__n_after_start_req_64: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req_64; } switch (*p) { case 'N': { p++; goto s_n_llhttp__internal__n_after_start_req_65; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_after_start_req: s_n_llhttp__internal__n_after_start_req: { if (p == endp) { return s_n_llhttp__internal__n_after_start_req; } switch (*p) { case 'A': { p++; goto s_n_llhttp__internal__n_after_start_req_1; } case 'B': { p++; goto s_n_llhttp__internal__n_after_start_req_4; } case 'C': { p++; goto s_n_llhttp__internal__n_after_start_req_5; } case 'D': { p++; goto s_n_llhttp__internal__n_after_start_req_10; } case 'F': { p++; goto s_n_llhttp__internal__n_after_start_req_14; } case 'G': { p++; goto s_n_llhttp__internal__n_after_start_req_15; } case 'H': { p++; goto s_n_llhttp__internal__n_after_start_req_18; } case 'L': { p++; goto s_n_llhttp__internal__n_after_start_req_19; } case 'M': { p++; goto s_n_llhttp__internal__n_after_start_req_22; } case 'N': { p++; goto s_n_llhttp__internal__n_after_start_req_31; } case 'O': { p++; goto s_n_llhttp__internal__n_after_start_req_32; } case 'P': { p++; goto s_n_llhttp__internal__n_after_start_req_33; } case 'Q': { p++; goto s_n_llhttp__internal__n_after_start_req_46; } case 'R': { p++; goto s_n_llhttp__internal__n_after_start_req_47; } case 'S': { p++; goto s_n_llhttp__internal__n_after_start_req_53; } case 'T': { p++; goto s_n_llhttp__internal__n_after_start_req_61; } case 'U': { p++; goto s_n_llhttp__internal__n_after_start_req_64; } default: { goto s_n_llhttp__internal__n_error_112; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_method_1: s_n_llhttp__internal__n_span_start_llhttp__on_method_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_method_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_method; goto s_n_llhttp__internal__n_after_start_req; UNREACHABLE; } case s_n_llhttp__internal__n_res_line_almost_done: s_n_llhttp__internal__n_res_line_almost_done: { if (p == endp) { return s_n_llhttp__internal__n_res_line_almost_done; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; } case 13: { p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; } default: { goto s_n_llhttp__internal__n_invoke_test_lenient_flags_29; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_test_lenient_flags_30: s_n_llhttp__internal__n_invoke_test_lenient_flags_30: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; default: goto s_n_llhttp__internal__n_error_98; } UNREACHABLE; } case s_n_llhttp__internal__n_res_status: s_n_llhttp__internal__n_res_status: { if (p == endp) { return s_n_llhttp__internal__n_res_status; } switch (*p) { case 10: { goto s_n_llhttp__internal__n_span_end_llhttp__on_status; } case 13: { goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1; } default: { p++; goto s_n_llhttp__internal__n_res_status; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_status: s_n_llhttp__internal__n_span_start_llhttp__on_status: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_status; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_status; goto s_n_llhttp__internal__n_res_status; UNREACHABLE; } case s_n_llhttp__internal__n_res_status_code_otherwise: s_n_llhttp__internal__n_res_status_code_otherwise: { if (p == endp) { return s_n_llhttp__internal__n_res_status_code_otherwise; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_28; } case 13: { p++; goto s_n_llhttp__internal__n_res_line_almost_done; } case ' ': { p++; goto s_n_llhttp__internal__n_span_start_llhttp__on_status; } default: { goto s_n_llhttp__internal__n_error_99; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_status_code_digit_3: s_n_llhttp__internal__n_res_status_code_digit_3: { if (p == endp) { return s_n_llhttp__internal__n_res_status_code_digit_3; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; } default: { goto s_n_llhttp__internal__n_error_101; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_status_code_digit_2: s_n_llhttp__internal__n_res_status_code_digit_2: { if (p == endp) { return s_n_llhttp__internal__n_res_status_code_digit_2; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; } default: { goto s_n_llhttp__internal__n_error_103; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_status_code_digit_1: s_n_llhttp__internal__n_res_status_code_digit_1: { if (p == endp) { return s_n_llhttp__internal__n_res_status_code_digit_1; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_mul_add_status_code; } default: { goto s_n_llhttp__internal__n_error_105; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_after_version: s_n_llhttp__internal__n_res_after_version: { if (p == endp) { return s_n_llhttp__internal__n_res_after_version; } switch (*p) { case ' ': { p++; goto s_n_llhttp__internal__n_invoke_update_status_code; } default: { goto s_n_llhttp__internal__n_error_106; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: { switch (llhttp__on_version_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_res_after_version; case 21: goto s_n_llhttp__internal__n_pause_28; default: goto s_n_llhttp__internal__n_error_94; } UNREACHABLE; } case s_n_llhttp__internal__n_error_93: s_n_llhttp__internal__n_error_93: { state->error = 0x9; state->reason = "Invalid HTTP version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_error_107: s_n_llhttp__internal__n_error_107: { state->error = 0x9; state->reason = "Invalid minor version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_res_http_minor: s_n_llhttp__internal__n_res_http_minor: { if (p == endp) { return s_n_llhttp__internal__n_res_http_minor; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_store_http_minor_1; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_7; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_108: s_n_llhttp__internal__n_error_108: { state->error = 0x9; state->reason = "Expected dot"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_res_http_dot: s_n_llhttp__internal__n_res_http_dot: { if (p == endp) { return s_n_llhttp__internal__n_res_http_dot; } switch (*p) { case '.': { p++; goto s_n_llhttp__internal__n_res_http_minor; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_8; } } UNREACHABLE; } case s_n_llhttp__internal__n_error_109: s_n_llhttp__internal__n_error_109: { state->error = 0x9; state->reason = "Invalid major version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_res_http_major: s_n_llhttp__internal__n_res_http_major: { if (p == endp) { return s_n_llhttp__internal__n_res_http_major; } switch (*p) { case '0': { p++; match = 0; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '1': { p++; match = 1; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '2': { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '3': { p++; match = 3; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '4': { p++; match = 4; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '5': { p++; match = 5; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '6': { p++; match = 6; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '7': { p++; match = 7; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '8': { p++; match = 8; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } case '9': { p++; match = 9; goto s_n_llhttp__internal__n_invoke_store_http_major_1; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_version_9; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_version_1: s_n_llhttp__internal__n_span_start_llhttp__on_version_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_version_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_version; goto s_n_llhttp__internal__n_res_http_major; UNREACHABLE; } case s_n_llhttp__internal__n_res_after_protocol: s_n_llhttp__internal__n_res_after_protocol: { if (p == endp) { return s_n_llhttp__internal__n_res_after_protocol; } switch (*p) { case '/': { p++; goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; } default: { goto s_n_llhttp__internal__n_error_114; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3: s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3: { switch (llhttp__on_protocol_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_res_after_protocol; case 21: goto s_n_llhttp__internal__n_pause_30; default: goto s_n_llhttp__internal__n_error_113; } UNREACHABLE; } case s_n_llhttp__internal__n_error_115: s_n_llhttp__internal__n_error_115: { state->error = 0x8; state->reason = "Expected HTTP/, RTSP/ or ICE/"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } case s_n_llhttp__internal__n_res_after_start_1: s_n_llhttp__internal__n_res_after_start_1: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_res_after_start_1; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob58, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; } case kMatchPause: { return s_n_llhttp__internal__n_res_after_start_1; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_after_start_2: s_n_llhttp__internal__n_res_after_start_2: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_res_after_start_2; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob59, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; } case kMatchPause: { return s_n_llhttp__internal__n_res_after_start_2; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_after_start_3: s_n_llhttp__internal__n_res_after_start_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_res_after_start_3; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob60, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; } case kMatchPause: { return s_n_llhttp__internal__n_res_after_start_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_res_after_start: s_n_llhttp__internal__n_res_after_start: { if (p == endp) { return s_n_llhttp__internal__n_res_after_start; } switch (*p) { case 'H': { p++; goto s_n_llhttp__internal__n_res_after_start_1; } case 'I': { p++; goto s_n_llhttp__internal__n_res_after_start_2; } case 'R': { p++; goto s_n_llhttp__internal__n_res_after_start_3; } default: { goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1: s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_protocol; goto s_n_llhttp__internal__n_res_after_start; UNREACHABLE; } case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: { switch (llhttp__on_method_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_req_first_space_before_url; case 21: goto s_n_llhttp__internal__n_pause_26; default: goto s_n_llhttp__internal__n_error_1; } UNREACHABLE; } case s_n_llhttp__internal__n_req_or_res_method_2: s_n_llhttp__internal__n_req_or_res_method_2: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_or_res_method_2; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob61, 2); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; match = 2; goto s_n_llhttp__internal__n_invoke_store_method; } case kMatchPause: { return s_n_llhttp__internal__n_req_or_res_method_2; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_110; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_update_type_1: s_n_llhttp__internal__n_invoke_update_type_1: { switch (llhttp__internal__c_update_type_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; } UNREACHABLE; } case s_n_llhttp__internal__n_req_or_res_method_3: s_n_llhttp__internal__n_req_or_res_method_3: { llparse_match_t match_seq; if (p == endp) { return s_n_llhttp__internal__n_req_or_res_method_3; } match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob62, 3); p = match_seq.current; switch (match_seq.status) { case kMatchComplete: { p++; goto s_n_llhttp__internal__n_span_end_llhttp__on_method_1; } case kMatchPause: { return s_n_llhttp__internal__n_req_or_res_method_3; } case kMatchMismatch: { goto s_n_llhttp__internal__n_error_110; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_or_res_method_1: s_n_llhttp__internal__n_req_or_res_method_1: { if (p == endp) { return s_n_llhttp__internal__n_req_or_res_method_1; } switch (*p) { case 'E': { p++; goto s_n_llhttp__internal__n_req_or_res_method_2; } case 'T': { p++; goto s_n_llhttp__internal__n_req_or_res_method_3; } default: { goto s_n_llhttp__internal__n_error_110; } } UNREACHABLE; } case s_n_llhttp__internal__n_req_or_res_method: s_n_llhttp__internal__n_req_or_res_method: { if (p == endp) { return s_n_llhttp__internal__n_req_or_res_method; } switch (*p) { case 'H': { p++; goto s_n_llhttp__internal__n_req_or_res_method_1; } default: { goto s_n_llhttp__internal__n_error_110; } } UNREACHABLE; } case s_n_llhttp__internal__n_span_start_llhttp__on_method: s_n_llhttp__internal__n_span_start_llhttp__on_method: { if (p == endp) { return s_n_llhttp__internal__n_span_start_llhttp__on_method; } state->_span_pos0 = (void*) p; state->_span_cb0 = llhttp__on_method; goto s_n_llhttp__internal__n_req_or_res_method; UNREACHABLE; } case s_n_llhttp__internal__n_start_req_or_res: s_n_llhttp__internal__n_start_req_or_res: { if (p == endp) { return s_n_llhttp__internal__n_start_req_or_res; } switch (*p) { case 'H': { goto s_n_llhttp__internal__n_span_start_llhttp__on_method; } default: { goto s_n_llhttp__internal__n_invoke_update_type_2; } } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_load_type: s_n_llhttp__internal__n_invoke_load_type: { switch (llhttp__internal__c_load_type(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; case 2: goto s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1; default: goto s_n_llhttp__internal__n_start_req_or_res; } UNREACHABLE; } case s_n_llhttp__internal__n_invoke_update_finish: s_n_llhttp__internal__n_invoke_update_finish: { switch (llhttp__internal__c_update_finish(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin; } UNREACHABLE; } case s_n_llhttp__internal__n_start: s_n_llhttp__internal__n_start: { if (p == endp) { return s_n_llhttp__internal__n_start; } switch (*p) { case 10: { p++; goto s_n_llhttp__internal__n_start; } case 13: { p++; goto s_n_llhttp__internal__n_start; } default: { goto s_n_llhttp__internal__n_invoke_load_initial_message_completed; } } UNREACHABLE; } default: UNREACHABLE; } s_n_llhttp__internal__n_error_2: { state->error = 0x7; state->reason = "Invalid characters in url"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_finish_2: { switch (llhttp__internal__c_update_finish_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_start; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_initial_message_completed: { switch (llhttp__internal__c_update_initial_message_completed(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_finish_2; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_content_length: { switch (llhttp__internal__c_update_content_length(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; } UNREACHABLE; } s_n_llhttp__internal__n_error_8: { state->error = 0x5; state->reason = "Data after `Connection: close`"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_3: { switch (llhttp__internal__c_test_lenient_flags_3(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_closed; default: goto s_n_llhttp__internal__n_error_8; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_2: { switch (llhttp__internal__c_test_lenient_flags_2(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; default: goto s_n_llhttp__internal__n_closed; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_finish_1: { switch (llhttp__internal__c_update_finish_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_2; } UNREACHABLE; } s_n_llhttp__internal__n_pause_13: { state->error = 0x15; state->reason = "on_message_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_38: { state->error = 0x12; state->reason = "`on_message_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_15: { state->error = 0x15; state->reason = "on_chunk_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_40: { state->error = 0x14; state->reason = "`on_chunk_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: { switch (llhttp__on_chunk_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; case 21: goto s_n_llhttp__internal__n_pause_15; default: goto s_n_llhttp__internal__n_error_40; } UNREACHABLE; } s_n_llhttp__internal__n_pause_2: { state->error = 0x15; state->reason = "on_message_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_9: { state->error = 0x12; state->reason = "`on_message_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: { switch (llhttp__on_message_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_pause_1; case 21: goto s_n_llhttp__internal__n_pause_2; default: goto s_n_llhttp__internal__n_error_9; } UNREACHABLE; } s_n_llhttp__internal__n_error_36: { state->error = 0xc; state->reason = "Chunk size overflow"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_10: { state->error = 0xc; state->reason = "Invalid character in chunk size"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_4: { switch (llhttp__internal__c_test_lenient_flags_4(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_chunk_size_otherwise; default: goto s_n_llhttp__internal__n_error_10; } UNREACHABLE; } s_n_llhttp__internal__n_pause_3: { state->error = 0x15; state->reason = "on_chunk_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length_1; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_14: { state->error = 0x14; state->reason = "`on_chunk_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: { switch (llhttp__on_chunk_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_update_content_length_1; case 21: goto s_n_llhttp__internal__n_pause_3; default: goto s_n_llhttp__internal__n_error_14; } UNREACHABLE; } s_n_llhttp__internal__n_error_13: { state->error = 0x19; state->reason = "Missing expected CR after chunk data"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_6: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; default: goto s_n_llhttp__internal__n_error_13; } UNREACHABLE; } s_n_llhttp__internal__n_error_15: { state->error = 0x2; state->reason = "Expected LF after chunk data"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_7: { switch (llhttp__internal__c_test_lenient_flags_7(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; default: goto s_n_llhttp__internal__n_error_15; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_body: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_body(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done; return s_error; } goto s_n_llhttp__internal__n_chunk_data_almost_done; UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags: { switch (llhttp__internal__c_or_flags(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_field_start; } UNREACHABLE; } s_n_llhttp__internal__n_pause_4: { state->error = 0x15; state->reason = "on_chunk_header pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_12: { state->error = 0x13; state->reason = "`on_chunk_header` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: { switch (llhttp__on_chunk_header(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_is_equal_content_length; case 21: goto s_n_llhttp__internal__n_pause_4; default: goto s_n_llhttp__internal__n_error_12; } UNREACHABLE; } s_n_llhttp__internal__n_error_16: { state->error = 0x2; state->reason = "Expected LF after chunk size"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_8: { switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; default: goto s_n_llhttp__internal__n_error_16; } UNREACHABLE; } s_n_llhttp__internal__n_error_11: { state->error = 0x19; state->reason = "Missing expected CR after chunk size"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_5: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_chunk_size_almost_done; default: goto s_n_llhttp__internal__n_error_11; } UNREACHABLE; } s_n_llhttp__internal__n_error_17: { state->error = 0x2; state->reason = "Invalid character in chunk extensions"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_18: { state->error = 0x2; state->reason = "Invalid character in chunk extensions"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_20: { state->error = 0x19; state->reason = "Missing expected CR after chunk extension name"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_5: { state->error = 0x15; state->reason = "on_chunk_extension_name pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_9; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_19: { state->error = 0x22; state->reason = "`on_chunk_extension_name` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_name(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; UNREACHABLE; } s_n_llhttp__internal__n_pause_6: { state->error = 0x15; state->reason = "on_chunk_extension_name pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_21: { state->error = 0x22; state->reason = "`on_chunk_extension_name` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_name(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; UNREACHABLE; } s_n_llhttp__internal__n_pause_7: { state->error = 0x15; state->reason = "on_chunk_extension_name pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_22: { state->error = 0x22; state->reason = "`on_chunk_extension_name` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_name(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; UNREACHABLE; } s_n_llhttp__internal__n_error_25: { state->error = 0x19; state->reason = "Missing expected CR after chunk extension value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_8: { state->error = 0x15; state->reason = "on_chunk_extension_value pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_10; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_24: { state->error = 0x23; state->reason = "`on_chunk_extension_value` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; UNREACHABLE; } s_n_llhttp__internal__n_pause_9: { state->error = 0x15; state->reason = "on_chunk_extension_value pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_26: { state->error = 0x23; state->reason = "`on_chunk_extension_value` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; UNREACHABLE; } s_n_llhttp__internal__n_error_28: { state->error = 0x19; state->reason = "Missing expected CR after chunk extension value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_11: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_chunk_size_almost_done; default: goto s_n_llhttp__internal__n_error_28; } UNREACHABLE; } s_n_llhttp__internal__n_error_29: { state->error = 0x2; state->reason = "Invalid character in chunk extensions quote value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_10: { state->error = 0x15; state->reason = "on_chunk_extension_value pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_quoted_value_done; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_27: { state->error = 0x23; state->reason = "`on_chunk_extension_value` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_30; return s_error; } p++; goto s_n_llhttp__internal__n_error_30; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_31; return s_error; } p++; goto s_n_llhttp__internal__n_error_31; UNREACHABLE; } s_n_llhttp__internal__n_pause_11: { state->error = 0x15; state->reason = "on_chunk_extension_value pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_32: { state->error = 0x23; state->reason = "`on_chunk_extension_value` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_33; return s_error; } p++; goto s_n_llhttp__internal__n_error_33; UNREACHABLE; } s_n_llhttp__internal__n_pause_12: { state->error = 0x15; state->reason = "on_chunk_extension_name pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_value; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_23: { state->error = 0x22; state->reason = "`on_chunk_extension_name` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3: { switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_chunk_extension_value; case 21: goto s_n_llhttp__internal__n_pause_12; default: goto s_n_llhttp__internal__n_error_23; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_name(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; return s_error; } p++; goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_chunk_extension_name(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_34; return s_error; } p++; goto s_n_llhttp__internal__n_error_34; UNREACHABLE; } s_n_llhttp__internal__n_error_35: { state->error = 0xc; state->reason = "Invalid character in chunk size"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_mul_add_content_length: { switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) { case 1: goto s_n_llhttp__internal__n_error_36; default: goto s_n_llhttp__internal__n_chunk_size; } UNREACHABLE; } s_n_llhttp__internal__n_error_37: { state->error = 0xc; state->reason = "Invalid character in chunk size"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_body_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_body(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_finish_3: { switch (llhttp__internal__c_update_finish_3(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2; } UNREACHABLE; } s_n_llhttp__internal__n_error_39: { state->error = 0xf; state->reason = "Request has invalid `Transfer-Encoding`"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause: { state->error = 0x15; state->reason = "on_message_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_7: { state->error = 0x12; state->reason = "`on_message_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: { switch (llhttp__on_message_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; case 21: goto s_n_llhttp__internal__n_pause; default: goto s_n_llhttp__internal__n_error_7; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_1: { switch (llhttp__internal__c_or_flags_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_2: { switch (llhttp__internal__c_or_flags_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_upgrade: { switch (llhttp__internal__c_update_upgrade(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_or_flags_2; } UNREACHABLE; } s_n_llhttp__internal__n_pause_14: { state->error = 0x15; state->reason = "Paused by on_headers_complete"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_6: { state->error = 0x11; state->reason = "User callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: { switch (llhttp__on_headers_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; case 1: goto s_n_llhttp__internal__n_invoke_or_flags_1; case 2: goto s_n_llhttp__internal__n_invoke_update_upgrade; case 21: goto s_n_llhttp__internal__n_pause_14; default: goto s_n_llhttp__internal__n_error_6; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: { switch (llhttp__before_headers_complete(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags: { switch (llhttp__internal__c_test_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1; default: goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_1: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_flags; default: goto s_n_llhttp__internal__n_error_5; } UNREACHABLE; } s_n_llhttp__internal__n_pause_17: { state->error = 0x15; state->reason = "on_chunk_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_42: { state->error = 0x14; state->reason = "`on_chunk_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2: { switch (llhttp__on_chunk_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; case 21: goto s_n_llhttp__internal__n_pause_17; default: goto s_n_llhttp__internal__n_error_42; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_3: { switch (llhttp__internal__c_or_flags_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_4: { switch (llhttp__internal__c_or_flags_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_upgrade_1: { switch (llhttp__internal__c_update_upgrade(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_or_flags_4; } UNREACHABLE; } s_n_llhttp__internal__n_pause_16: { state->error = 0x15; state->reason = "Paused by on_headers_complete"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_41: { state->error = 0x11; state->reason = "User callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1: { switch (llhttp__on_headers_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; case 1: goto s_n_llhttp__internal__n_invoke_or_flags_3; case 2: goto s_n_llhttp__internal__n_invoke_update_upgrade_1; case 21: goto s_n_llhttp__internal__n_pause_16; default: goto s_n_llhttp__internal__n_error_41; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1: { switch (llhttp__before_headers_complete(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags_1: { switch (llhttp__internal__c_test_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2; default: goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1; } UNREACHABLE; } s_n_llhttp__internal__n_error_43: { state->error = 0x2; state->reason = "Expected LF after headers"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_12: { switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_flags_1; default: goto s_n_llhttp__internal__n_error_43; } UNREACHABLE; } s_n_llhttp__internal__n_error_44: { state->error = 0xa; state->reason = "Invalid header token"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_field: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_field(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_5; return s_error; } p++; goto s_n_llhttp__internal__n_error_5; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_13: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_field_colon_discard_ws; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field; } UNREACHABLE; } s_n_llhttp__internal__n_error_60: { state->error = 0xb; state->reason = "Content-Length can't be present with Transfer-Encoding"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_47: { state->error = 0xa; state->reason = "Invalid header value char"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_15: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_discard_ws; default: goto s_n_llhttp__internal__n_error_47; } UNREACHABLE; } s_n_llhttp__internal__n_error_49: { state->error = 0xb; state->reason = "Empty Content-Length"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_18: { state->error = 0x15; state->reason = "on_header_value_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_48: { state->error = 0x1d; state->reason = "`on_header_value_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state: { switch (llhttp__internal__c_update_header_state(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_5: { switch (llhttp__internal__c_or_flags_5(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_6: { switch (llhttp__internal__c_or_flags_6(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_7: { switch (llhttp__internal__c_or_flags_7(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_8: { switch (llhttp__internal__c_or_flags_8(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_2: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 5: goto s_n_llhttp__internal__n_invoke_or_flags_5; case 6: goto s_n_llhttp__internal__n_invoke_or_flags_6; case 7: goto s_n_llhttp__internal__n_invoke_or_flags_7; case 8: goto s_n_llhttp__internal__n_invoke_or_flags_8; default: goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_1: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 2: goto s_n_llhttp__internal__n_error_49; default: goto s_n_llhttp__internal__n_invoke_load_header_state_2; } UNREACHABLE; } s_n_llhttp__internal__n_error_46: { state->error = 0xa; state->reason = "Invalid header value char"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_14: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_discard_lws; default: goto s_n_llhttp__internal__n_error_46; } UNREACHABLE; } s_n_llhttp__internal__n_error_50: { state->error = 0x2; state->reason = "Expected LF after CR"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_16: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_discard_lws; default: goto s_n_llhttp__internal__n_error_50; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_1: { switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_4: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 8: goto s_n_llhttp__internal__n_invoke_update_header_state_1; default: goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; } UNREACHABLE; } s_n_llhttp__internal__n_error_52: { state->error = 0xa; state->reason = "Unexpected whitespace after header value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_18: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_load_header_state_4; default: goto s_n_llhttp__internal__n_error_52; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_2: { switch (llhttp__internal__c_update_header_state(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_9: { switch (llhttp__internal__c_or_flags_5(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_2; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_10: { switch (llhttp__internal__c_or_flags_6(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_2; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_11: { switch (llhttp__internal__c_or_flags_7(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_2; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_12: { switch (llhttp__internal__c_or_flags_8(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_5: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 5: goto s_n_llhttp__internal__n_invoke_or_flags_9; case 6: goto s_n_llhttp__internal__n_invoke_or_flags_10; case 7: goto s_n_llhttp__internal__n_invoke_or_flags_11; case 8: goto s_n_llhttp__internal__n_invoke_or_flags_12; default: goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; } UNREACHABLE; } s_n_llhttp__internal__n_error_53: { state->error = 0x3; state->reason = "Missing expected LF after header value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_51: { state->error = 0x19; state->reason = "Missing expected CR after header value"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_17; return s_error; } goto s_n_llhttp__internal__n_invoke_test_lenient_flags_17; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; return s_error; } p++; goto s_n_llhttp__internal__n_header_value_almost_done; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; return s_error; } goto s_n_llhttp__internal__n_header_value_almost_done; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; return s_error; } p++; goto s_n_llhttp__internal__n_header_value_almost_done; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_54; return s_error; } goto s_n_llhttp__internal__n_error_54; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_19: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_lenient; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_4: { switch (llhttp__internal__c_update_header_state(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_13: { switch (llhttp__internal__c_or_flags_5(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_4; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_14: { switch (llhttp__internal__c_or_flags_6(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_4; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_15: { switch (llhttp__internal__c_or_flags_7(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_4; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_16: { switch (llhttp__internal__c_or_flags_8(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_6: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 5: goto s_n_llhttp__internal__n_invoke_or_flags_13; case 6: goto s_n_llhttp__internal__n_invoke_or_flags_14; case 7: goto s_n_llhttp__internal__n_invoke_or_flags_15; case 8: goto s_n_llhttp__internal__n_invoke_or_flags_16; default: goto s_n_llhttp__internal__n_header_value_connection; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_5: { switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection_token; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_3: { switch (llhttp__internal__c_update_header_state_3(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection_ws; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_6: { switch (llhttp__internal__c_update_header_state_6(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection_ws; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_7: { switch (llhttp__internal__c_update_header_state_7(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_connection_ws; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_56; return s_error; } goto s_n_llhttp__internal__n_error_56; UNREACHABLE; } s_n_llhttp__internal__n_invoke_mul_add_content_length_1: { switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) { case 1: goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6; default: goto s_n_llhttp__internal__n_header_value_content_length; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_17: { switch (llhttp__internal__c_or_flags_17(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_otherwise; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_57; return s_error; } goto s_n_llhttp__internal__n_error_57; UNREACHABLE; } s_n_llhttp__internal__n_error_55: { state->error = 0x4; state->reason = "Duplicate Content-Length"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags_2: { switch (llhttp__internal__c_test_flags_2(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_header_value_content_length; default: goto s_n_llhttp__internal__n_error_55; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_59; return s_error; } p++; goto s_n_llhttp__internal__n_error_59; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_8: { switch (llhttp__internal__c_update_header_state_8(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_otherwise; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_value(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_58; return s_error; } p++; goto s_n_llhttp__internal__n_error_58; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_20: { switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8; default: goto s_n_llhttp__internal__n_header_value_te_chunked; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_type_1: { switch (llhttp__internal__c_load_type(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_20; default: goto s_n_llhttp__internal__n_header_value_te_chunked; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_9: { switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_and_flags: { switch (llhttp__internal__c_and_flags(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_value_te_chunked; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_19: { switch (llhttp__internal__c_or_flags_18(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_and_flags; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_21: { switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9; default: goto s_n_llhttp__internal__n_invoke_or_flags_19; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_type_2: { switch (llhttp__internal__c_load_type(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_21; default: goto s_n_llhttp__internal__n_invoke_or_flags_19; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_18: { switch (llhttp__internal__c_or_flags_18(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_and_flags; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags_3: { switch (llhttp__internal__c_test_flags_3(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_load_type_2; default: goto s_n_llhttp__internal__n_invoke_or_flags_18; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_or_flags_20: { switch (llhttp__internal__c_or_flags_20(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_header_state_9; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_header_state_3: { switch (llhttp__internal__c_load_header_state(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_value_connection; case 2: goto s_n_llhttp__internal__n_invoke_test_flags_2; case 3: goto s_n_llhttp__internal__n_invoke_test_flags_3; case 4: goto s_n_llhttp__internal__n_invoke_or_flags_20; default: goto s_n_llhttp__internal__n_header_value; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_22: { switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_error_60; default: goto s_n_llhttp__internal__n_header_value_discard_ws; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags_4: { switch (llhttp__internal__c_test_flags_4(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_22; default: goto s_n_llhttp__internal__n_header_value_discard_ws; } UNREACHABLE; } s_n_llhttp__internal__n_error_61: { state->error = 0xf; state->reason = "Transfer-Encoding can't be present with Content-Length"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_23: { switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_error_61; default: goto s_n_llhttp__internal__n_header_value_discard_ws; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_flags_5: { switch (llhttp__internal__c_test_flags_2(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_23; default: goto s_n_llhttp__internal__n_header_value_discard_ws; } UNREACHABLE; } s_n_llhttp__internal__n_pause_19: { state->error = 0x15; state->reason = "on_header_field_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_header_state; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_45: { state->error = 0x1c; state->reason = "`on_header_field_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_field(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_header_field(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; UNREACHABLE; } s_n_llhttp__internal__n_error_62: { state->error = 0xa; state->reason = "Invalid header token"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_10: { switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_field_general; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_header_state: { switch (llhttp__internal__c_store_header_state(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_header_field_colon; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_header_state_11: { switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { default: goto s_n_llhttp__internal__n_header_field_general; } UNREACHABLE; } s_n_llhttp__internal__n_error_4: { state->error = 0x1e; state->reason = "Unexpected space after start line"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags: { switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_header_field_start; default: goto s_n_llhttp__internal__n_error_4; } UNREACHABLE; } s_n_llhttp__internal__n_pause_20: { state->error = 0x15; state->reason = "on_url_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_3: { state->error = 0x1a; state->reason = "`on_url_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_url_complete: { switch (llhttp__on_url_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_headers_start; case 21: goto s_n_llhttp__internal__n_pause_20; default: goto s_n_llhttp__internal__n_error_3; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_http_minor: { switch (llhttp__internal__c_update_http_minor(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_http_major: { switch (llhttp__internal__c_update_http_major(state, p, endp)) { default: goto s_n_llhttp__internal__n_invoke_update_http_minor; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_error_63: { state->error = 0x7; state->reason = "Expected CRLF"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_lf_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_error_72: { state->error = 0x17; state->reason = "Pause on PRI/Upgrade"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_73: { state->error = 0x9; state->reason = "Expected HTTP/2 Connection Preface"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_70: { state->error = 0x2; state->reason = "Expected CRLF after version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_26: { switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_headers_start; default: goto s_n_llhttp__internal__n_error_70; } UNREACHABLE; } s_n_llhttp__internal__n_error_69: { state->error = 0x9; state->reason = "Expected CRLF after version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_25: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_req_http_complete_crlf; default: goto s_n_llhttp__internal__n_error_69; } UNREACHABLE; } s_n_llhttp__internal__n_error_71: { state->error = 0x9; state->reason = "Expected CRLF after version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_21: { state->error = 0x15; state->reason = "on_version_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_1; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_68: { state->error = 0x21; state->reason = "`on_version_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_67; return s_error; } goto s_n_llhttp__internal__n_error_67; UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 9: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor_1: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; case 1: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor_2: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_major: { switch (llhttp__internal__c_load_http_major(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_http_minor; case 1: goto s_n_llhttp__internal__n_invoke_load_http_minor_1; case 2: goto s_n_llhttp__internal__n_invoke_load_http_minor_2; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_24: { switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; default: goto s_n_llhttp__internal__n_invoke_load_http_major; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_http_minor: { switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_24; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_74; return s_error; } goto s_n_llhttp__internal__n_error_74; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_75; return s_error; } goto s_n_llhttp__internal__n_error_75; UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_http_major: { switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_req_http_dot; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_76; return s_error; } goto s_n_llhttp__internal__n_error_76; UNREACHABLE; } s_n_llhttp__internal__n_error_77: { state->error = 0x8; state->reason = "Expected HTTP/, RTSP/ or ICE/"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_66: { state->error = 0x8; state->reason = "Invalid method for HTTP/x.x request"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_22: { state->error = 0x15; state->reason = "on_protocol_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_65: { state->error = 0x26; state->reason = "`on_protocol_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_82; return s_error; } goto s_n_llhttp__internal__n_error_82; UNREACHABLE; } s_n_llhttp__internal__n_error_79: { state->error = 0x8; state->reason = "Expected SOURCE method for ICE/x.x request"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_23: { state->error = 0x15; state->reason = "on_protocol_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_2; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_78: { state->error = 0x26; state->reason = "`on_protocol_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1; UNREACHABLE; } s_n_llhttp__internal__n_error_81: { state->error = 0x8; state->reason = "Invalid method for RTSP/x.x request"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_24: { state->error = 0x15; state->reason = "on_protocol_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_3; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_80: { state->error = 0x26; state->reason = "`on_protocol_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2; UNREACHABLE; } s_n_llhttp__internal__n_pause_25: { state->error = 0x15; state->reason = "on_url_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_http_start; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_64: { state->error = 0x1a; state->reason = "`on_url_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1: { switch (llhttp__on_url_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_req_http_start; case 21: goto s_n_llhttp__internal__n_pause_25; default: goto s_n_llhttp__internal__n_error_64; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_5: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_6: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_7: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_lf_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_8: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http; UNREACHABLE; } s_n_llhttp__internal__n_error_83: { state->error = 0x7; state->reason = "Invalid char in url fragment start"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_9: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_10: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_lf_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_11: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http; UNREACHABLE; } s_n_llhttp__internal__n_error_84: { state->error = 0x7; state->reason = "Invalid char in url query"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_85: { state->error = 0x7; state->reason = "Invalid char in url path"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_lf_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_12: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_13: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; return s_error; } goto s_n_llhttp__internal__n_url_skip_lf_to_http09; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_url_14: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_url(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; return s_error; } goto s_n_llhttp__internal__n_url_skip_to_http; UNREACHABLE; } s_n_llhttp__internal__n_error_86: { state->error = 0x7; state->reason = "Double @ in url"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_87: { state->error = 0x7; state->reason = "Unexpected char in url server"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_88: { state->error = 0x7; state->reason = "Unexpected char in url server"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_89: { state->error = 0x7; state->reason = "Unexpected char in url schema"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_90: { state->error = 0x7; state->reason = "Unexpected char in url schema"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_91: { state->error = 0x7; state->reason = "Unexpected start char in url"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_is_equal_method: { switch (llhttp__internal__c_is_equal_method(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_url_entry_normal; default: goto s_n_llhttp__internal__n_url_entry_connect; } UNREACHABLE; } s_n_llhttp__internal__n_error_92: { state->error = 0x6; state->reason = "Expected space after method"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_29: { state->error = 0x15; state->reason = "on_method_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_111: { state->error = 0x20; state->reason = "`on_method_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_method_2: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_method(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_method_1: { switch (llhttp__internal__c_store_method(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_span_end_llhttp__on_method_2; } UNREACHABLE; } s_n_llhttp__internal__n_error_112: { state->error = 0x6; state->reason = "Invalid method encountered"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_104: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_102: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_100: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_27: { state->error = 0x15; state->reason = "on_status_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_96: { state->error = 0x1b; state->reason = "`on_status_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_status_complete: { switch (llhttp__on_status_complete(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_headers_start; case 21: goto s_n_llhttp__internal__n_pause_27; default: goto s_n_llhttp__internal__n_error_96; } UNREACHABLE; } s_n_llhttp__internal__n_error_95: { state->error = 0xd; state->reason = "Invalid response status"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_28: { switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; default: goto s_n_llhttp__internal__n_error_95; } UNREACHABLE; } s_n_llhttp__internal__n_error_97: { state->error = 0x2; state->reason = "Expected LF after CR"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_29: { switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; default: goto s_n_llhttp__internal__n_error_97; } UNREACHABLE; } s_n_llhttp__internal__n_error_98: { state->error = 0x19; state->reason = "Missing expected CR after response line"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_status: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_status(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_30; return s_error; } p++; goto s_n_llhttp__internal__n_invoke_test_lenient_flags_30; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_status_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_status(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) (p + 1); state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; return s_error; } p++; goto s_n_llhttp__internal__n_res_line_almost_done; UNREACHABLE; } s_n_llhttp__internal__n_error_99: { state->error = 0xd; state->reason = "Invalid response status"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_mul_add_status_code_2: { switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { case 1: goto s_n_llhttp__internal__n_error_100; default: goto s_n_llhttp__internal__n_res_status_code_otherwise; } UNREACHABLE; } s_n_llhttp__internal__n_error_101: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_mul_add_status_code_1: { switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { case 1: goto s_n_llhttp__internal__n_error_102; default: goto s_n_llhttp__internal__n_res_status_code_digit_3; } UNREACHABLE; } s_n_llhttp__internal__n_error_103: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_mul_add_status_code: { switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { case 1: goto s_n_llhttp__internal__n_error_104; default: goto s_n_llhttp__internal__n_res_status_code_digit_2; } UNREACHABLE; } s_n_llhttp__internal__n_error_105: { state->error = 0xd; state->reason = "Invalid status code"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_status_code: { switch (llhttp__internal__c_update_status_code(state, p, endp)) { default: goto s_n_llhttp__internal__n_res_status_code_digit_1; } UNREACHABLE; } s_n_llhttp__internal__n_error_106: { state->error = 0x9; state->reason = "Expected space after version"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_28: { state->error = 0x15; state->reason = "on_version_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_version; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_94: { state->error = 0x21; state->reason = "`on_version_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_6: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_5: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_93; return s_error; } goto s_n_llhttp__internal__n_error_93; UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor_3: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 9: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor_4: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; case 1: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_minor_5: { switch (llhttp__internal__c_load_http_minor(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_http_major_1: { switch (llhttp__internal__c_load_http_major(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_http_minor_3; case 1: goto s_n_llhttp__internal__n_invoke_load_http_minor_4; case 2: goto s_n_llhttp__internal__n_invoke_load_http_minor_5; default: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_test_lenient_flags_27: { switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; default: goto s_n_llhttp__internal__n_invoke_load_http_major_1; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_http_minor_1: { switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_invoke_test_lenient_flags_27; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_7: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_107; return s_error; } goto s_n_llhttp__internal__n_error_107; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_8: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_108; return s_error; } goto s_n_llhttp__internal__n_error_108; UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_http_major_1: { switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_res_http_dot; } UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_version_9: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_version(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_109; return s_error; } goto s_n_llhttp__internal__n_error_109; UNREACHABLE; } s_n_llhttp__internal__n_error_114: { state->error = 0x8; state->reason = "Expected HTTP/, RTSP/ or ICE/"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_pause_30: { state->error = 0x15; state->reason = "on_protocol_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_protocol; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_113: { state->error = 0x26; state->reason = "`on_protocol_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_protocol(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_115; return s_error; } goto s_n_llhttp__internal__n_error_115; UNREACHABLE; } s_n_llhttp__internal__n_pause_26: { state->error = 0x15; state->reason = "on_method_complete pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_1: { state->error = 0x20; state->reason = "`on_method_complete` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_method: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_method(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; return s_error; } goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_type: { switch (llhttp__internal__c_update_type(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_end_llhttp__on_method; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_store_method: { switch (llhttp__internal__c_store_method(state, p, endp, match)) { default: goto s_n_llhttp__internal__n_invoke_update_type; } UNREACHABLE; } s_n_llhttp__internal__n_error_110: { state->error = 0x8; state->reason = "Invalid word encountered"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_span_end_llhttp__on_method_1: { const unsigned char* start; int err; start = state->_span_pos0; state->_span_pos0 = NULL; err = llhttp__on_method(state, start, p); if (err != 0) { state->error = err; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_type_1; return s_error; } goto s_n_llhttp__internal__n_invoke_update_type_1; UNREACHABLE; } s_n_llhttp__internal__n_invoke_update_type_2: { switch (llhttp__internal__c_update_type(state, p, endp)) { default: goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; } UNREACHABLE; } s_n_llhttp__internal__n_pause_31: { state->error = 0x15; state->reason = "on_message_begin pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error: { state->error = 0x10; state->reason = "`on_message_begin` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: { switch (llhttp__on_message_begin(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_load_type; case 21: goto s_n_llhttp__internal__n_pause_31; default: goto s_n_llhttp__internal__n_error; } UNREACHABLE; } s_n_llhttp__internal__n_pause_32: { state->error = 0x15; state->reason = "on_reset pause"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_finish; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_error_116: { state->error = 0x1f; state->reason = "`on_reset` callback error"; state->error_pos = (const char*) p; state->_current = (void*) (intptr_t) s_error; return s_error; UNREACHABLE; } s_n_llhttp__internal__n_invoke_llhttp__on_reset: { switch (llhttp__on_reset(state, p, endp)) { case 0: goto s_n_llhttp__internal__n_invoke_update_finish; case 21: goto s_n_llhttp__internal__n_pause_32; default: goto s_n_llhttp__internal__n_error_116; } UNREACHABLE; } s_n_llhttp__internal__n_invoke_load_initial_message_completed: { switch (llhttp__internal__c_load_initial_message_completed(state, p, endp)) { case 1: goto s_n_llhttp__internal__n_invoke_llhttp__on_reset; default: goto s_n_llhttp__internal__n_invoke_update_finish; } UNREACHABLE; } } int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) { llparse_state_t next; /* check lingering errors */ if (state->error != 0) { return state->error; } /* restart spans */ if (state->_span_pos0 != NULL) { state->_span_pos0 = (void*) p; } next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); if (next == s_error) { return state->error; } state->_current = (void*) (intptr_t) next; /* execute spans */ if (state->_span_pos0 != NULL) { int error; error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); if (error != 0) { state->error = error; state->error_pos = endp; return error; } } return 0; } ================================================ FILE: thirdparty/llhttp/llhttp.h ================================================ #ifndef INCLUDE_LLHTTP_H_ #define INCLUDE_LLHTTP_H_ #define LLHTTP_VERSION_MAJOR 9 #define LLHTTP_VERSION_MINOR 3 #define LLHTTP_VERSION_PATCH 0 #ifndef INCLUDE_LLHTTP_ITSELF_H_ #define INCLUDE_LLHTTP_ITSELF_H_ #ifdef __cplusplus extern "C" { #endif #include typedef struct llhttp__internal_s llhttp__internal_t; struct llhttp__internal_s { int32_t _index; void* _span_pos0; void* _span_cb0; int32_t error; const char* reason; const char* error_pos; void* data; void* _current; uint64_t content_length; uint8_t type; uint8_t method; uint8_t http_major; uint8_t http_minor; uint8_t header_state; uint16_t lenient_flags; uint8_t upgrade; uint8_t finish; uint16_t flags; uint16_t status_code; uint8_t initial_message_completed; void* settings; }; int llhttp__internal_init(llhttp__internal_t* s); int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* INCLUDE_LLHTTP_ITSELF_H_ */ #ifndef LLLLHTTP_C_HEADERS_ #define LLLLHTTP_C_HEADERS_ #ifdef __cplusplus extern "C" { #endif enum llhttp_errno { HPE_OK = 0, HPE_INTERNAL = 1, HPE_STRICT = 2, HPE_CR_EXPECTED = 25, HPE_LF_EXPECTED = 3, HPE_UNEXPECTED_CONTENT_LENGTH = 4, HPE_UNEXPECTED_SPACE = 30, HPE_CLOSED_CONNECTION = 5, HPE_INVALID_METHOD = 6, HPE_INVALID_URL = 7, HPE_INVALID_CONSTANT = 8, HPE_INVALID_VERSION = 9, HPE_INVALID_HEADER_TOKEN = 10, HPE_INVALID_CONTENT_LENGTH = 11, HPE_INVALID_CHUNK_SIZE = 12, HPE_INVALID_STATUS = 13, HPE_INVALID_EOF_STATE = 14, HPE_INVALID_TRANSFER_ENCODING = 15, HPE_CB_MESSAGE_BEGIN = 16, HPE_CB_HEADERS_COMPLETE = 17, HPE_CB_MESSAGE_COMPLETE = 18, HPE_CB_CHUNK_HEADER = 19, HPE_CB_CHUNK_COMPLETE = 20, HPE_PAUSED = 21, HPE_PAUSED_UPGRADE = 22, HPE_PAUSED_H2_UPGRADE = 23, HPE_USER = 24, HPE_CB_URL_COMPLETE = 26, HPE_CB_STATUS_COMPLETE = 27, HPE_CB_METHOD_COMPLETE = 32, HPE_CB_VERSION_COMPLETE = 33, HPE_CB_HEADER_FIELD_COMPLETE = 28, HPE_CB_HEADER_VALUE_COMPLETE = 29, HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, HPE_CB_RESET = 31, HPE_CB_PROTOCOL_COMPLETE = 38 }; typedef enum llhttp_errno llhttp_errno_t; enum llhttp_flags { F_CONNECTION_KEEP_ALIVE = 0x1, F_CONNECTION_CLOSE = 0x2, F_CONNECTION_UPGRADE = 0x4, F_CHUNKED = 0x8, F_UPGRADE = 0x10, F_CONTENT_LENGTH = 0x20, F_SKIPBODY = 0x40, F_TRAILING = 0x80, F_TRANSFER_ENCODING = 0x200 }; typedef enum llhttp_flags llhttp_flags_t; enum llhttp_lenient_flags { LENIENT_HEADERS = 0x1, LENIENT_CHUNKED_LENGTH = 0x2, LENIENT_KEEP_ALIVE = 0x4, LENIENT_TRANSFER_ENCODING = 0x8, LENIENT_VERSION = 0x10, LENIENT_DATA_AFTER_CLOSE = 0x20, LENIENT_OPTIONAL_LF_AFTER_CR = 0x40, LENIENT_OPTIONAL_CRLF_AFTER_CHUNK = 0x80, LENIENT_OPTIONAL_CR_BEFORE_LF = 0x100, LENIENT_SPACES_AFTER_CHUNK_SIZE = 0x200 }; typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; enum llhttp_type { HTTP_BOTH = 0, HTTP_REQUEST = 1, HTTP_RESPONSE = 2 }; typedef enum llhttp_type llhttp_type_t; enum llhttp_finish { HTTP_FINISH_SAFE = 0, HTTP_FINISH_SAFE_WITH_CB = 1, HTTP_FINISH_UNSAFE = 2 }; typedef enum llhttp_finish llhttp_finish_t; enum llhttp_method { HTTP_DELETE = 0, HTTP_GET = 1, HTTP_HEAD = 2, HTTP_POST = 3, HTTP_PUT = 4, HTTP_CONNECT = 5, HTTP_OPTIONS = 6, HTTP_TRACE = 7, HTTP_COPY = 8, HTTP_LOCK = 9, HTTP_MKCOL = 10, HTTP_MOVE = 11, HTTP_PROPFIND = 12, HTTP_PROPPATCH = 13, HTTP_SEARCH = 14, HTTP_UNLOCK = 15, HTTP_BIND = 16, HTTP_REBIND = 17, HTTP_UNBIND = 18, HTTP_ACL = 19, HTTP_REPORT = 20, HTTP_MKACTIVITY = 21, HTTP_CHECKOUT = 22, HTTP_MERGE = 23, HTTP_MSEARCH = 24, HTTP_NOTIFY = 25, HTTP_SUBSCRIBE = 26, HTTP_UNSUBSCRIBE = 27, HTTP_PATCH = 28, HTTP_PURGE = 29, HTTP_MKCALENDAR = 30, HTTP_LINK = 31, HTTP_UNLINK = 32, HTTP_SOURCE = 33, HTTP_PRI = 34, HTTP_DESCRIBE = 35, HTTP_ANNOUNCE = 36, HTTP_SETUP = 37, HTTP_PLAY = 38, HTTP_PAUSE = 39, HTTP_TEARDOWN = 40, HTTP_GET_PARAMETER = 41, HTTP_SET_PARAMETER = 42, HTTP_REDIRECT = 43, HTTP_RECORD = 44, HTTP_FLUSH = 45, HTTP_QUERY = 46 }; typedef enum llhttp_method llhttp_method_t; enum llhttp_status { HTTP_STATUS_CONTINUE = 100, HTTP_STATUS_SWITCHING_PROTOCOLS = 101, HTTP_STATUS_PROCESSING = 102, HTTP_STATUS_EARLY_HINTS = 103, HTTP_STATUS_RESPONSE_IS_STALE = 110, HTTP_STATUS_REVALIDATION_FAILED = 111, HTTP_STATUS_DISCONNECTED_OPERATION = 112, HTTP_STATUS_HEURISTIC_EXPIRATION = 113, HTTP_STATUS_MISCELLANEOUS_WARNING = 199, HTTP_STATUS_OK = 200, HTTP_STATUS_CREATED = 201, HTTP_STATUS_ACCEPTED = 202, HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, HTTP_STATUS_NO_CONTENT = 204, HTTP_STATUS_RESET_CONTENT = 205, HTTP_STATUS_PARTIAL_CONTENT = 206, HTTP_STATUS_MULTI_STATUS = 207, HTTP_STATUS_ALREADY_REPORTED = 208, HTTP_STATUS_TRANSFORMATION_APPLIED = 214, HTTP_STATUS_IM_USED = 226, HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, HTTP_STATUS_MULTIPLE_CHOICES = 300, HTTP_STATUS_MOVED_PERMANENTLY = 301, HTTP_STATUS_FOUND = 302, HTTP_STATUS_SEE_OTHER = 303, HTTP_STATUS_NOT_MODIFIED = 304, HTTP_STATUS_USE_PROXY = 305, HTTP_STATUS_SWITCH_PROXY = 306, HTTP_STATUS_TEMPORARY_REDIRECT = 307, HTTP_STATUS_PERMANENT_REDIRECT = 308, HTTP_STATUS_BAD_REQUEST = 400, HTTP_STATUS_UNAUTHORIZED = 401, HTTP_STATUS_PAYMENT_REQUIRED = 402, HTTP_STATUS_FORBIDDEN = 403, HTTP_STATUS_NOT_FOUND = 404, HTTP_STATUS_METHOD_NOT_ALLOWED = 405, HTTP_STATUS_NOT_ACCEPTABLE = 406, HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, HTTP_STATUS_REQUEST_TIMEOUT = 408, HTTP_STATUS_CONFLICT = 409, HTTP_STATUS_GONE = 410, HTTP_STATUS_LENGTH_REQUIRED = 411, HTTP_STATUS_PRECONDITION_FAILED = 412, HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, HTTP_STATUS_URI_TOO_LONG = 414, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, HTTP_STATUS_EXPECTATION_FAILED = 417, HTTP_STATUS_IM_A_TEAPOT = 418, HTTP_STATUS_PAGE_EXPIRED = 419, HTTP_STATUS_ENHANCE_YOUR_CALM = 420, HTTP_STATUS_MISDIRECTED_REQUEST = 421, HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, HTTP_STATUS_LOCKED = 423, HTTP_STATUS_FAILED_DEPENDENCY = 424, HTTP_STATUS_TOO_EARLY = 425, HTTP_STATUS_UPGRADE_REQUIRED = 426, HTTP_STATUS_PRECONDITION_REQUIRED = 428, HTTP_STATUS_TOO_MANY_REQUESTS = 429, HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, HTTP_STATUS_LOGIN_TIMEOUT = 440, HTTP_STATUS_NO_RESPONSE = 444, HTTP_STATUS_RETRY_WITH = 449, HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, HTTP_STATUS_INVALID_TOKEN = 498, HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, HTTP_STATUS_NOT_IMPLEMENTED = 501, HTTP_STATUS_BAD_GATEWAY = 502, HTTP_STATUS_SERVICE_UNAVAILABLE = 503, HTTP_STATUS_GATEWAY_TIMEOUT = 504, HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, HTTP_STATUS_INSUFFICIENT_STORAGE = 507, HTTP_STATUS_LOOP_DETECTED = 508, HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, HTTP_STATUS_NOT_EXTENDED = 510, HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, HTTP_STATUS_CONNECTION_TIMEOUT = 522, HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, HTTP_STATUS_TIMEOUT_OCCURED = 524, HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, HTTP_STATUS_RAILGUN_ERROR = 527, HTTP_STATUS_SITE_IS_OVERLOADED = 529, HTTP_STATUS_SITE_IS_FROZEN = 530, HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 }; typedef enum llhttp_status llhttp_status_t; #define HTTP_ERRNO_MAP(XX) \ XX(0, OK, OK) \ XX(1, INTERNAL, INTERNAL) \ XX(2, STRICT, STRICT) \ XX(25, CR_EXPECTED, CR_EXPECTED) \ XX(3, LF_EXPECTED, LF_EXPECTED) \ XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ XX(6, INVALID_METHOD, INVALID_METHOD) \ XX(7, INVALID_URL, INVALID_URL) \ XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ XX(9, INVALID_VERSION, INVALID_VERSION) \ XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ XX(13, INVALID_STATUS, INVALID_STATUS) \ XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ XX(21, PAUSED, PAUSED) \ XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ XX(24, USER, USER) \ XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ XX(31, CB_RESET, CB_RESET) \ XX(38, CB_PROTOCOL_COMPLETE, CB_PROTOCOL_COMPLETE) \ #define HTTP_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ XX(1, GET, GET) \ XX(2, HEAD, HEAD) \ XX(3, POST, POST) \ XX(4, PUT, PUT) \ XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ XX(11, MOVE, MOVE) \ XX(12, PROPFIND, PROPFIND) \ XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ XX(16, BIND, BIND) \ XX(17, REBIND, REBIND) \ XX(18, UNBIND, UNBIND) \ XX(19, ACL, ACL) \ XX(20, REPORT, REPORT) \ XX(21, MKACTIVITY, MKACTIVITY) \ XX(22, CHECKOUT, CHECKOUT) \ XX(23, MERGE, MERGE) \ XX(24, MSEARCH, M-SEARCH) \ XX(25, NOTIFY, NOTIFY) \ XX(26, SUBSCRIBE, SUBSCRIBE) \ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ XX(28, PATCH, PATCH) \ XX(29, PURGE, PURGE) \ XX(30, MKCALENDAR, MKCALENDAR) \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ XX(33, SOURCE, SOURCE) \ XX(46, QUERY, QUERY) \ #define RTSP_METHOD_MAP(XX) \ XX(1, GET, GET) \ XX(3, POST, POST) \ XX(6, OPTIONS, OPTIONS) \ XX(35, DESCRIBE, DESCRIBE) \ XX(36, ANNOUNCE, ANNOUNCE) \ XX(37, SETUP, SETUP) \ XX(38, PLAY, PLAY) \ XX(39, PAUSE, PAUSE) \ XX(40, TEARDOWN, TEARDOWN) \ XX(41, GET_PARAMETER, GET_PARAMETER) \ XX(42, SET_PARAMETER, SET_PARAMETER) \ XX(43, REDIRECT, REDIRECT) \ XX(44, RECORD, RECORD) \ XX(45, FLUSH, FLUSH) \ #define HTTP_ALL_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ XX(1, GET, GET) \ XX(2, HEAD, HEAD) \ XX(3, POST, POST) \ XX(4, PUT, PUT) \ XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ XX(11, MOVE, MOVE) \ XX(12, PROPFIND, PROPFIND) \ XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ XX(16, BIND, BIND) \ XX(17, REBIND, REBIND) \ XX(18, UNBIND, UNBIND) \ XX(19, ACL, ACL) \ XX(20, REPORT, REPORT) \ XX(21, MKACTIVITY, MKACTIVITY) \ XX(22, CHECKOUT, CHECKOUT) \ XX(23, MERGE, MERGE) \ XX(24, MSEARCH, M-SEARCH) \ XX(25, NOTIFY, NOTIFY) \ XX(26, SUBSCRIBE, SUBSCRIBE) \ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ XX(28, PATCH, PATCH) \ XX(29, PURGE, PURGE) \ XX(30, MKCALENDAR, MKCALENDAR) \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ XX(33, SOURCE, SOURCE) \ XX(34, PRI, PRI) \ XX(35, DESCRIBE, DESCRIBE) \ XX(36, ANNOUNCE, ANNOUNCE) \ XX(37, SETUP, SETUP) \ XX(38, PLAY, PLAY) \ XX(39, PAUSE, PAUSE) \ XX(40, TEARDOWN, TEARDOWN) \ XX(41, GET_PARAMETER, GET_PARAMETER) \ XX(42, SET_PARAMETER, SET_PARAMETER) \ XX(43, REDIRECT, REDIRECT) \ XX(44, RECORD, RECORD) \ XX(45, FLUSH, FLUSH) \ XX(46, QUERY, QUERY) \ #define HTTP_STATUS_MAP(XX) \ XX(100, CONTINUE, CONTINUE) \ XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ XX(102, PROCESSING, PROCESSING) \ XX(103, EARLY_HINTS, EARLY_HINTS) \ XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ XX(200, OK, OK) \ XX(201, CREATED, CREATED) \ XX(202, ACCEPTED, ACCEPTED) \ XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ XX(204, NO_CONTENT, NO_CONTENT) \ XX(205, RESET_CONTENT, RESET_CONTENT) \ XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ XX(207, MULTI_STATUS, MULTI_STATUS) \ XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ XX(226, IM_USED, IM_USED) \ XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ XX(302, FOUND, FOUND) \ XX(303, SEE_OTHER, SEE_OTHER) \ XX(304, NOT_MODIFIED, NOT_MODIFIED) \ XX(305, USE_PROXY, USE_PROXY) \ XX(306, SWITCH_PROXY, SWITCH_PROXY) \ XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ XX(400, BAD_REQUEST, BAD_REQUEST) \ XX(401, UNAUTHORIZED, UNAUTHORIZED) \ XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ XX(403, FORBIDDEN, FORBIDDEN) \ XX(404, NOT_FOUND, NOT_FOUND) \ XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ XX(409, CONFLICT, CONFLICT) \ XX(410, GONE, GONE) \ XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ XX(414, URI_TOO_LONG, URI_TOO_LONG) \ XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ XX(423, LOCKED, LOCKED) \ XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ XX(425, TOO_EARLY, TOO_EARLY) \ XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ XX(444, NO_RESPONSE, NO_RESPONSE) \ XX(449, RETRY_WITH, RETRY_WITH) \ XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ XX(498, INVALID_TOKEN, INVALID_TOKEN) \ XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ XX(502, BAD_GATEWAY, BAD_GATEWAY) \ XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ XX(508, LOOP_DETECTED, LOOP_DETECTED) \ XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ XX(510, NOT_EXTENDED, NOT_EXTENDED) \ XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LLLLHTTP_C_HEADERS_ */ #ifndef INCLUDE_LLHTTP_API_H_ #define INCLUDE_LLHTTP_API_H_ #ifdef __cplusplus extern "C" { #endif #include #if defined(__wasm__) #define LLHTTP_EXPORT __attribute__((visibility("default"))) #elif defined(_WIN32) #define LLHTTP_EXPORT __declspec(dllexport) #else #define LLHTTP_EXPORT #endif typedef llhttp__internal_t llhttp_t; typedef struct llhttp_settings_s llhttp_settings_t; typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); typedef int (*llhttp_cb)(llhttp_t*); struct llhttp_settings_s { /* Possible return values 0, -1, `HPE_PAUSED` */ llhttp_cb on_message_begin; /* Possible return values 0, -1, HPE_USER */ llhttp_data_cb on_protocol; llhttp_data_cb on_url; llhttp_data_cb on_status; llhttp_data_cb on_method; llhttp_data_cb on_version; llhttp_data_cb on_header_field; llhttp_data_cb on_header_value; llhttp_data_cb on_chunk_extension_name; llhttp_data_cb on_chunk_extension_value; /* Possible return values: * 0 - Proceed normally * 1 - Assume that request/response has no body, and proceed to parsing the * next message * 2 - Assume absence of body (as above) and make `llhttp_execute()` return * `HPE_PAUSED_UPGRADE` * -1 - Error * `HPE_PAUSED` */ llhttp_cb on_headers_complete; /* Possible return values 0, -1, HPE_USER */ llhttp_data_cb on_body; /* Possible return values 0, -1, `HPE_PAUSED` */ llhttp_cb on_message_complete; llhttp_cb on_protocol_complete; llhttp_cb on_url_complete; llhttp_cb on_status_complete; llhttp_cb on_method_complete; llhttp_cb on_version_complete; llhttp_cb on_header_field_complete; llhttp_cb on_header_value_complete; llhttp_cb on_chunk_extension_name_complete; llhttp_cb on_chunk_extension_value_complete; /* When on_chunk_header is called, the current chunk length is stored * in parser->content_length. * Possible return values 0, -1, `HPE_PAUSED` */ llhttp_cb on_chunk_header; llhttp_cb on_chunk_complete; llhttp_cb on_reset; }; /* Initialize the parser with specific type and user settings. * * NOTE: lifetime of `settings` has to be at least the same as the lifetime of * the `parser` here. In practice, `settings` has to be either a static * variable or be allocated with `malloc`, `new`, etc. */ LLHTTP_EXPORT void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings); LLHTTP_EXPORT llhttp_t* llhttp_alloc(llhttp_type_t type); LLHTTP_EXPORT void llhttp_free(llhttp_t* parser); LLHTTP_EXPORT uint8_t llhttp_get_type(llhttp_t* parser); LLHTTP_EXPORT uint8_t llhttp_get_http_major(llhttp_t* parser); LLHTTP_EXPORT uint8_t llhttp_get_http_minor(llhttp_t* parser); LLHTTP_EXPORT uint8_t llhttp_get_method(llhttp_t* parser); LLHTTP_EXPORT int llhttp_get_status_code(llhttp_t* parser); LLHTTP_EXPORT uint8_t llhttp_get_upgrade(llhttp_t* parser); /* Reset an already initialized parser back to the start state, preserving the * existing parser type, callback settings, user data, and lenient flags. */ LLHTTP_EXPORT void llhttp_reset(llhttp_t* parser); /* Initialize the settings object */ LLHTTP_EXPORT void llhttp_settings_init(llhttp_settings_t* settings); /* Parse full or partial request/response, invoking user callbacks along the * way. * * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing * interrupts, and such errno is returned from `llhttp_execute()`. If * `HPE_PAUSED` was used as a errno, the execution can be resumed with * `llhttp_resume()` call. * * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` * is returned after fully parsing the request/response. If the user wishes to * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. * * NOTE: if this function ever returns a non-pause type error, it will continue * to return the same error upon each successive call up until `llhttp_init()` * is called. */ LLHTTP_EXPORT llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); /* This method should be called when the other side has no further bytes to * send (e.g. shutdown of readable side of the TCP connection.) * * Requests without `Content-Length` and other messages might require treating * all incoming bytes as the part of the body, up to the last byte of the * connection. This method will invoke `on_message_complete()` callback if the * request was terminated safely. Otherwise a error code would be returned. */ LLHTTP_EXPORT llhttp_errno_t llhttp_finish(llhttp_t* parser); /* Returns `1` if the incoming message is parsed until the last byte, and has * to be completed by calling `llhttp_finish()` on EOF */ LLHTTP_EXPORT int llhttp_message_needs_eof(const llhttp_t* parser); /* Returns `1` if there might be any other messages following the last that was * successfully parsed. */ LLHTTP_EXPORT int llhttp_should_keep_alive(const llhttp_t* parser); /* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set * appropriate error reason. * * Important: do not call this from user callbacks! User callbacks must return * `HPE_PAUSED` if pausing is required. */ LLHTTP_EXPORT void llhttp_pause(llhttp_t* parser); /* Might be called to resume the execution after the pause in user's callback. * See `llhttp_execute()` above for details. * * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. */ LLHTTP_EXPORT void llhttp_resume(llhttp_t* parser); /* Might be called to resume the execution after the pause in user's callback. * See `llhttp_execute()` above for details. * * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` */ LLHTTP_EXPORT void llhttp_resume_after_upgrade(llhttp_t* parser); /* Returns the latest return error */ LLHTTP_EXPORT llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); /* Returns the verbal explanation of the latest returned error. * * Note: User callback should set error reason when returning the error. See * `llhttp_set_error_reason()` for details. */ LLHTTP_EXPORT const char* llhttp_get_error_reason(const llhttp_t* parser); /* Assign verbal description to the returned error. Must be called in user * callbacks right before returning the errno. * * Note: `HPE_USER` error code might be useful in user callbacks. */ LLHTTP_EXPORT void llhttp_set_error_reason(llhttp_t* parser, const char* reason); /* Returns the pointer to the last parsed byte before the returned error. The * pointer is relative to the `data` argument of `llhttp_execute()`. * * Note: this method might be useful for counting the number of parsed bytes. */ LLHTTP_EXPORT const char* llhttp_get_error_pos(const llhttp_t* parser); /* Returns textual name of error code */ LLHTTP_EXPORT const char* llhttp_errno_name(llhttp_errno_t err); /* Returns textual name of HTTP method */ LLHTTP_EXPORT const char* llhttp_method_name(llhttp_method_t method); /* Returns textual name of HTTP status */ LLHTTP_EXPORT const char* llhttp_status_name(llhttp_status_t status); /* Enables/disables lenient header value parsing (disabled by default). * * Lenient parsing disables header value token checks, extending llhttp's * protocol support to highly non-compliant clients/server. No * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when * lenient parsing is "on". * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of conflicting `Transfer-Encoding` and * `Content-Length` headers (disabled by default). * * Normally `llhttp` would error when `Transfer-Encoding` is present in * conjunction with `Content-Length`. This error is important to prevent HTTP * request smuggling, but may be less desirable for small number of cases * involving legacy servers. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 * requests responses. * * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) * the HTTP request/response after the request/response with `Connection: close` * and `Content-Length`. This is important to prevent cache poisoning attacks, * but might interact badly with outdated and insecure clients. With this flag * the extra request/response will be parsed normally. * * **Enabling this flag can pose a security issue since you will be exposed to * poisoning attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of `Transfer-Encoding` header. * * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value * and another value after it (either in a single header or in multiple * headers whose value are internally joined using `, `). * This is mandated by the spec to reliably determine request body size and thus * avoid request smuggling. * With this flag the extra value will be parsed normally. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of HTTP version. * * Normally `llhttp` would error when the HTTP version in the request or status line * is not `0.9`, `1.0`, `1.1` or `2.0`. * With this flag the invalid value will be parsed normally. * * **Enabling this flag can pose a security issue since you will allow unsupported * HTTP versions. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_version(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of additional data received after a message ends * and keep-alive is disabled. * * Normally `llhttp` would error when additional unexpected data is received if the message * contains the `Connection` header with `close` value. * With this flag the extra data will discarded without throwing an error. * * **Enabling this flag can pose a security issue since you will be exposed to * poisoning attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of incomplete CRLF sequences. * * Normally `llhttp` would error when a CR is not followed by LF when terminating the * request line, the status line, the headers or a chunk header. * With this flag only a CR is required to terminate such sections. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled); /* * Enables/disables lenient handling of line separators. * * Normally `llhttp` would error when a LF is not preceded by CR when terminating the * request line, the status line, the headers, a chunk header or a chunk data. * With this flag only a LF is required to terminate such sections. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of chunks not separated via CRLF. * * Normally `llhttp` would error when after a chunk data a CRLF is missing before * starting a new chunk. * With this flag the new chunk can start immediately after the previous one. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled); /* Enables/disables lenient handling of spaces after chunk size. * * Normally `llhttp` would error when after a chunk size is followed by one or more * spaces are present instead of a CRLF or `;`. * With this flag this check is disabled. * * **Enabling this flag can pose a security issue since you will be exposed to * request smuggling attacks. USE WITH CAUTION!** */ LLHTTP_EXPORT void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* INCLUDE_LLHTTP_API_H_ */ #endif /* INCLUDE_LLHTTP_H_ */ ================================================ FILE: thirdparty/multipart_parser.c ================================================ /* Based on node-formidable by Felix Geisendörfer * Igor Afonov - afonov@gmail.com - 2012 * MIT License - http://www.opensource.org/licenses/mit-license.php */ #include "multipart_parser.h" #include #include #include #ifdef DEBUG_MULTIPART #include #define multipart_log(format, ...) \ do { \ fprintf(stderr, "[MULTIPART_PARSER] line %d: " format "\n", __LINE__, __VA_ARGS__); \ } while (0) #define multipart_log_c(format) \ do { \ if (isprint(c)) { \ multipart_log("parsing '%c' " format, c); \ } else { \ multipart_log("parsing '\\x%0.2x' " format, c); \ } \ } while (0) #else #define multipart_log(format, ...) #define multipart_log_c(format, ...) #endif #define NOTIFY_CB(FOR, r) \ do { \ if (p->settings->on_##FOR) { \ if ((ret = p->settings->on_##FOR(p)) == MPPE_PAUSED) { \ return r; \ } else if (ret != MPPE_OK) { \ return MPPE_ERROR; \ } \ } \ } while (0) #define EMIT_DATA_CB(FOR, r, ptr, len) \ do { \ if (p->settings->on_##FOR) { \ if ((ret = p->settings->on_##FOR(p, ptr, len)) == MPPE_PAUSED) { \ return r; \ } else if (ret != MPPE_OK) { \ return MPPE_ERROR; \ } \ } \ } while (0) #define ERROR_OUT(reason) \ do { \ p->error_unexpected = c; \ p->error_i = i; \ p->error_reason = reason; \ return MPPE_ERROR; \ } while (0) #define ERROR_EXPECT(reason, ch) \ do { \ p->error_expected = ch; \ if (ch == LF) { \ multipart_log("expecting LF at %zu, but it's \\x%.2x", i, c); \ } else if (ch == CR) { \ multipart_log("expecting CR at %zu, but it's \\x%.2x", i, c); \ } else { \ multipart_log("expecting '%c' at %zu, but it's \\x%.2x", ch, i, c); \ } \ ERROR_OUT(reason); \ } while (0) #define LF 10 #define CR 13 enum state { s_uninitialized = 0, s_start, s_start_boundary, s_header_field_start, s_header_field, s_headers_almost_done, s_header_value_start, s_header_value, s_header_value_almost_done, s_part_data_start, s_part_data, s_part_data_almost_boundary, s_part_data_boundary, s_part_data_almost_end, s_part_data_end, s_part_data_final_hyphen, s_end }; multipart_parser *multipart_parser_init(const char *boundary, size_t boundary_length, const multipart_parser_settings *settings) { multipart_parser *p = calloc(sizeof(multipart_parser) + boundary_length + boundary_length + 9 + 4, sizeof(char)); memcpy(p->boundary, "--", 2); memcpy(p->boundary + 2, boundary, boundary_length); p->boundary[2 + boundary_length] = 0; p->boundary_length = boundary_length + 2; p->index = 0; p->state = s_start; p->error_i = 0; p->error_unexpected = 0; p->error_expected = 0; p->error_reason = MPPE_OK; p->state = s_start; p->settings = settings; return p; } void multipart_parser_free(multipart_parser *p) { free(p); } int multipart_parser_error_msg(multipart_parser *p, char *buf, size_t len) { int ret; switch (p->error_reason) { case MPPE_OK: return 0; case MPPE_PAUSED: return snprintf(buf, len, "parser paused"); case MPPE_UNKNOWN: return snprintf(buf, len, "parser unknown"); default: return snprintf(buf, len, "parser abort"); case MPPE_BOUNDARY_END_NO_CRLF: ret = snprintf(buf, len, "no CRLF at first boundary end: "); break; case MPPE_BAD_START_BOUNDARY: ret = snprintf(buf, len, "first boundary mismatching: "); break; case MPPE_INVALID_HEADER_FIELD_CHAR: ret = snprintf(buf, len, "invalid char in header field: "); break; case MPPE_INVALID_HEADER_VALUE_CHAR: ret = snprintf(buf, len, "invalid char in header value: "); break; case MPPE_BAD_PART_END: ret = snprintf(buf, len, "no next part or final hyphen: expecting CR or '-' "); break; case MPPE_END_BOUNDARY_NO_DASH: ret = snprintf(buf, len, "bad final hyphen: "); break; } if (ret < 0) { return 0; } if ((size_t) ret >= len) { return ret; } switch (p->error_expected) { case '\0': break; case CR: ret += snprintf(buf + ret, len - ret, "expecting CR "); break; case LF: ret += snprintf(buf + ret, len - ret, "expecting LF "); break; default: ret += snprintf(buf + ret, len - ret, "expecting '%c' ", p->error_expected); break; } if (ret < 0) { return 0; } if ((size_t) ret >= len) { return ret; } if (isprint(p->error_unexpected)) { ret += snprintf(buf + ret, len - ret, "at %zu, but it is '%c'", p->error_i, p->error_unexpected); } else { ret += snprintf(buf + ret, len - ret, "at %zu, but it is '\\x%.2x'", p->error_i, p->error_unexpected); } return ret; } ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t len) { size_t i = 0; size_t mark = 0; size_t mark_end = 0; char c, cl; int is_last = 0; int ret; while (i < len) { c = buf[i]; is_last = (i == (len - 1)); switch (p->state) { case s_start: multipart_log_c("s_start"); p->index = 0; p->state = s_start_boundary; /* fallthrough */ /* no break */ case s_start_boundary: multipart_log_c("s_start_boundary"); if (p->index == p->boundary_length) { // https://github.com/swoole/swoole-src/issues/5168 if (c == '-') { p->state = s_part_data_final_hyphen; } else if (c != CR) { ERROR_EXPECT(MPPE_BOUNDARY_END_NO_CRLF, CR); } p->index++; break; } else if (p->index == (size_t)(p->boundary_length + 1)) { if (c != LF) { ERROR_EXPECT(MPPE_BOUNDARY_END_NO_CRLF, LF); } p->index = 0; p->state = s_header_field_start; NOTIFY_CB(part_data_begin, i + 1); break; } if (c != p->boundary[p->index]) { ERROR_EXPECT(MPPE_BAD_START_BOUNDARY, p->boundary[p->index]); } p->index++; break; case s_header_field_start: multipart_log_c("s_header_field_start"); mark = i; p->state = s_header_field; /* fallthrough */ /* no break */ case s_header_field: multipart_log_c("s_header_field"); if (c == CR) { p->state = s_headers_almost_done; break; } if (c == '-') { if (is_last) { EMIT_DATA_CB(header_field, i + 1, buf + mark, i - mark + 1); } break; } if (c == ':') { p->state = s_header_value_start; EMIT_DATA_CB(header_field, i + 1, buf + mark, i - mark); break; } cl = c | 0x20; if (cl < 'a' || cl > 'z') { multipart_log_c("invalid character in header field"); p->error_unexpected = c; ERROR_OUT(MPPE_INVALID_HEADER_FIELD_CHAR); } if (is_last) { EMIT_DATA_CB(header_field, i + 1, buf + mark, i - mark + 1); } break; case s_headers_almost_done: multipart_log_c("s_headers_almost_done"); if (c != LF) { ERROR_EXPECT(MPPE_INVALID_HEADER_VALUE_CHAR, LF); } p->state = s_part_data_start; break; case s_header_value_start: multipart_log_c("s_header_value_start"); if (c == ' ') { break; } mark = i; p->state = s_header_value; /* fallthrough */ /* no break */ case s_header_value: multipart_log_c("s_header_value"); if (c == CR) { p->state = s_header_value_almost_done; EMIT_DATA_CB(header_value, i + 1, buf + mark, i - mark); } else if (is_last) { ERROR_EXPECT(MPPE_HEADER_VALUE_INCOMPLETE, CR); } break; case s_header_value_almost_done: multipart_log_c("s_header_value_almost_done"); if (c != LF) { ERROR_EXPECT(MPPE_INVALID_HEADER_VALUE_CHAR, LF); } p->state = s_header_field_start; break; case s_part_data_start: multipart_log_c("s_part_data_start"); mark = i; p->state = s_part_data; NOTIFY_CB(headers_complete, i); /* fallthrough */ /* no break */ case s_part_data: data_rollback: multipart_log_c("s_part_data"); mark_end = i + 1; if (c == CR) { if (mark_end - mark - 1 > 0) { EMIT_DATA_CB(part_data, i + 1, buf + mark, mark_end - mark - 1); } mark = i; p->state = s_part_data_almost_boundary; break; } if (is_last) { EMIT_DATA_CB(part_data, i + 1, buf + mark, mark_end - mark); } break; case s_part_data_almost_boundary: multipart_log_c("s_part_data_almost_boundary"); if (c != LF) { EMIT_DATA_CB(part_data, i + 1, "\r", 1); p->state = s_part_data; mark = i; goto data_rollback; } else { p->state = s_part_data_boundary; p->index = 0; break; } case s_part_data_boundary: multipart_log_c("s_part_data_boundary"); if (p->boundary[p->index] != c) { EMIT_DATA_CB(part_data, i + 1, "\r\n", 2); if (p->index > 0) { EMIT_DATA_CB(part_data, i + 1, p->boundary, p->index); } mark = i; p->state = s_part_data; goto data_rollback; } else { p->index++; if (p->index == p->boundary_length) { p->state = s_part_data_almost_end; } break; } case s_part_data_almost_end: multipart_log_c("s_part_data_almost_end"); if (c == '-') { p->state = s_part_data_final_hyphen; NOTIFY_CB(part_data_end, i + 1); break; } if (c == CR) { p->state = s_part_data_end; NOTIFY_CB(part_data_end, i + 1); break; } // should be end or another part multipart_log("expecting '-' or CR at %zu but it's \\x%0.2x", i, c); ERROR_OUT(MPPE_BAD_PART_END); case s_part_data_final_hyphen: multipart_log_c("s_part_data_final_hyphen"); if (c == '-') { p->state = s_end; NOTIFY_CB(body_end, i); break; } // should be - ERROR_EXPECT(MPPE_END_BOUNDARY_NO_DASH, '-'); case s_part_data_end: multipart_log_c("s_part_data_end"); if (c == LF) { p->state = s_header_field_start; NOTIFY_CB(part_data_begin, i + 1); break; } // should be - ERROR_EXPECT(MPPE_END_BOUNDARY_NO_DASH, '-'); case s_end: multipart_log_c("s_end"); break; default: multipart_log_c("Multipart parser unrecoverable error"); ERROR_OUT(MPPE_UNKNOWN); } ++i; } return i; } ================================================ FILE: thirdparty/multipart_parser.h ================================================ /* Based on node-formidable by Felix Geisendörfer * Igor Afonov - afonov@gmail.com - 2012 * MIT License - http://www.opensource.org/licenses/mit-license.php * @link https://github.com/libcat/libcat/blob/develop/deps/multipart_parser */ #ifndef _multipart_parser_h #define _multipart_parser_h #ifdef __cplusplus extern "C" { #endif #include #include #include typedef struct multipart_parser multipart_parser; typedef struct multipart_parser_settings multipart_parser_settings; typedef struct multipart_parser_state multipart_parser_state; typedef int (*multipart_data_cb)(multipart_parser *, const char *at, size_t length); typedef int (*multipart_notify_cb)(multipart_parser *); enum multipart_error { MPPE_OK = 0, MPPE_PAUSED, MPPE_UNKNOWN, MPPE_BOUNDARY_END_NO_CRLF, MPPE_BAD_START_BOUNDARY, MPPE_INVALID_HEADER_FIELD_CHAR, MPPE_INVALID_HEADER_VALUE_CHAR, MPPE_BAD_PART_END, MPPE_END_BOUNDARY_NO_DASH, MPPE_HEADER_VALUE_INCOMPLETE, }; #define MPPE_ERROR -1 // from RFC2046 #define BOUNDARY_MAX_LEN 70 struct multipart_parser { /* private holder for callbacks */ const multipart_parser_settings *settings; /* private internal index for matching boundary */ size_t index; /* public error unexpected char index */ size_t error_i; /* private boundary length + 2 ("--") */ unsigned char boundary_length; FILE *fp; void *data; /* private FSM state */ unsigned char state; /* public error reason */ unsigned char error_reason; /* private boundary storage: "--" + boundary */ char boundary[(2 + BOUNDARY_MAX_LEN) * 2 + 9]; /* public error expected char */ char error_expected; /* public error unexpected char */ char error_unexpected; }; struct multipart_parser_settings { /* * data callback called on header field coming * for example data is "Content-Type" with length 12 */ multipart_data_cb on_header_field; /* * data callback called on header value coming * for example data is "plain/text" with length 10 */ multipart_data_cb on_header_value; /* * data callback called on body data coming, * will be called repeatedly until data end */ multipart_data_cb on_part_data; /* * before "--" boundary */ multipart_notify_cb on_part_data_begin; /* * after all headers line "\r\n", before body */ multipart_notify_cb on_headers_complete; /* * after body, before next "--" boundary */ multipart_notify_cb on_part_data_end; /* * after last "--" boundary "--" */ multipart_notify_cb on_body_end; }; multipart_parser *multipart_parser_init(const char *boundary, size_t boundary_length, const multipart_parser_settings *settings); void multipart_parser_free(multipart_parser *p); /** * The multipart header must be complete, otherwise it will be parsed incorrectly */ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t len); int multipart_parser_error_msg(multipart_parser *p, char *buf, size_t len); #ifdef __cplusplus } /* extern "C" */ #endif #endif ================================================ FILE: thirdparty/nghttp2/COPYING ================================================ The MIT License Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: thirdparty/nghttp2/LICENSE ================================================ See COPYING ================================================ FILE: thirdparty/nghttp2/nghttp2.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013, 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_H #define NGHTTP2_H /* Define WIN32 when build target is Win32 API (borrowed from libcurl) */ #if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) # define WIN32 #endif /* Compatibility for non-Clang compilers */ #ifndef __has_declspec_attribute # define __has_declspec_attribute(x) 0 #endif #ifdef __cplusplus extern "C" { #endif #include #if defined(_MSC_VER) && (_MSC_VER < 1800) /* MSVC < 2013 does not have inttypes.h because it is not C99 compliant. See compiler macros and version number in https://sourceforge.net/p/predef/wiki/Compilers/ */ # include #else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ # include #endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ #include #include #include #include const char *nghttp2_strerror(int error_code); static inline uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) { if (len == 0) { return dest; } memcpy(dest, src, len); return dest + len; } #define DEBUGF(s, ...) #define nghttp2_min_size(A, B) ((A) < (B) ? (A) : (B)) #define nghttp2_max_size(A, B) ((A) > (B) ? (A) : (B)) #ifdef NGHTTP2_STATICLIB # define NGHTTP2_EXTERN #elif defined(WIN32) || \ (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport)) # ifdef BUILDING_NGHTTP2 # define NGHTTP2_EXTERN __declspec(dllexport) # else /* !BUILDING_NGHTTP2 */ # define NGHTTP2_EXTERN __declspec(dllimport) # endif /* !BUILDING_NGHTTP2 */ #else /* !defined(WIN32) */ # ifdef BUILDING_NGHTTP2 # define NGHTTP2_EXTERN __attribute__((visibility("default"))) # else /* !BUILDING_NGHTTP2 */ # define NGHTTP2_EXTERN # endif /* !BUILDING_NGHTTP2 */ #endif /* !defined(WIN32) */ #ifdef BUILDING_NGHTTP2 # undef NGHTTP2_NO_SSIZE_T #endif /* BUILDING_NGHTTP2 */ /** * @typedef * * :type:`nghttp2_ssize` is a signed counterpart of size_t. */ typedef ptrdiff_t nghttp2_ssize; /** * @macro * * The protocol version identification string of this library * supports. This identifier is used if HTTP/2 is used over TLS. */ #define NGHTTP2_PROTO_VERSION_ID "h2" /** * @macro * * The length of :macro:`NGHTTP2_PROTO_VERSION_ID`. */ #define NGHTTP2_PROTO_VERSION_ID_LEN 2 /** * @macro * * The serialized form of ALPN protocol identifier this library * supports. Notice that first byte is the length of following * protocol identifier. This is the same wire format of `TLS ALPN * extension `_. This is useful * to process incoming ALPN tokens in wire format. */ #define NGHTTP2_PROTO_ALPN "\x2h2" /** * @macro * * The length of :macro:`NGHTTP2_PROTO_ALPN`. */ #define NGHTTP2_PROTO_ALPN_LEN (sizeof(NGHTTP2_PROTO_ALPN) - 1) /** * @macro * * The protocol version identification string of this library * supports. This identifier is used if HTTP/2 is used over cleartext * TCP. */ #define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c" /** * @macro * * The length of :macro:`NGHTTP2_CLEARTEXT_PROTO_VERSION_ID`. */ #define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN 3 struct nghttp2_session; /** * @struct * * The primary structure to hold the resources needed for a HTTP/2 * session. The details of this structure are intentionally hidden * from the public API. */ typedef struct nghttp2_session nghttp2_session; /** * @macro * * The age of :type:`nghttp2_info` */ #define NGHTTP2_VERSION_AGE 1 /** * @struct * * This struct is what `nghttp2_version()` returns. It holds * information about the particular nghttp2 version. */ typedef struct { /** * Age of this struct. This instance of nghttp2 sets it to * :macro:`NGHTTP2_VERSION_AGE` but a future version may bump it and * add more struct fields at the bottom */ int age; /** * the :macro:`NGHTTP2_VERSION_NUM` number (since age ==1) */ int version_num; /** * points to the :macro:`NGHTTP2_VERSION` string (since age ==1) */ const char *version_str; /** * points to the :macro:`NGHTTP2_PROTO_VERSION_ID` string this * instance implements (since age ==1) */ const char *proto_str; /* -------- the above fields all exist when age == 1 */ } nghttp2_info; /** * @macro * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The default weight of stream dependency. */ #define NGHTTP2_DEFAULT_WEIGHT 16 /** * @macro * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The maximum weight of stream dependency. */ #define NGHTTP2_MAX_WEIGHT 256 /** * @macro * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The minimum weight of stream dependency. */ #define NGHTTP2_MIN_WEIGHT 1 /** * @macro * * The maximum window size */ #define NGHTTP2_MAX_WINDOW_SIZE ((int32_t)((1U << 31) - 1)) /** * @macro * * The initial window size for stream level flow control. */ #define NGHTTP2_INITIAL_WINDOW_SIZE ((1 << 16) - 1) /** * @macro * * The initial window size for connection level flow control. */ #define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1) /** * @macro * * The default header table size. */ #define NGHTTP2_DEFAULT_HEADER_TABLE_SIZE (1 << 12) /** * @macro * * The client magic string, which is the first 24 bytes byte string of * client connection preface. */ #define NGHTTP2_CLIENT_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /** * @macro * * The length of :macro:`NGHTTP2_CLIENT_MAGIC`. */ #define NGHTTP2_CLIENT_MAGIC_LEN 24 /** * @macro * * The default max number of settings per SETTINGS frame */ #define NGHTTP2_DEFAULT_MAX_SETTINGS 32 /** * @enum * * Error codes used in this library. The code range is [-999, -500], * inclusive. The following values are defined: */ typedef enum { /** * Invalid argument passed. */ NGHTTP2_ERR_INVALID_ARGUMENT = -501, /** * Out of buffer space. */ NGHTTP2_ERR_BUFFER_ERROR = -502, /** * The specified protocol version is not supported. */ NGHTTP2_ERR_UNSUPPORTED_VERSION = -503, /** * Used as a return value from :type:`nghttp2_send_callback2`, * :type:`nghttp2_recv_callback` and * :type:`nghttp2_send_data_callback` to indicate that the operation * would block. */ NGHTTP2_ERR_WOULDBLOCK = -504, /** * General protocol error */ NGHTTP2_ERR_PROTO = -505, /** * The frame is invalid. */ NGHTTP2_ERR_INVALID_FRAME = -506, /** * The peer performed a shutdown on the connection. */ NGHTTP2_ERR_EOF = -507, /** * Used as a return value from * :func:`nghttp2_data_source_read_callback2` to indicate that data * transfer is postponed. See * :func:`nghttp2_data_source_read_callback2` for details. */ NGHTTP2_ERR_DEFERRED = -508, /** * Stream ID has reached the maximum value. Therefore no stream ID * is available. */ NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE = -509, /** * The stream is already closed; or the stream ID is invalid. */ NGHTTP2_ERR_STREAM_CLOSED = -510, /** * RST_STREAM has been added to the outbound queue. The stream is * in closing state. */ NGHTTP2_ERR_STREAM_CLOSING = -511, /** * The transmission is not allowed for this stream (e.g., a frame * with END_STREAM flag set has already sent). */ NGHTTP2_ERR_STREAM_SHUT_WR = -512, /** * The stream ID is invalid. */ NGHTTP2_ERR_INVALID_STREAM_ID = -513, /** * The state of the stream is not valid (e.g., DATA cannot be sent * to the stream if response HEADERS has not been sent). */ NGHTTP2_ERR_INVALID_STREAM_STATE = -514, /** * Another DATA frame has already been deferred. */ NGHTTP2_ERR_DEFERRED_DATA_EXIST = -515, /** * Starting new stream is not allowed (e.g., GOAWAY has been sent * and/or received). */ NGHTTP2_ERR_START_STREAM_NOT_ALLOWED = -516, /** * GOAWAY has already been sent. */ NGHTTP2_ERR_GOAWAY_ALREADY_SENT = -517, /** * The received frame contains the invalid header block (e.g., There * are duplicate header names; or the header names are not encoded * in US-ASCII character set and not lower cased; or the header name * is zero-length string; or the header value contains multiple * in-sequence NUL bytes). */ NGHTTP2_ERR_INVALID_HEADER_BLOCK = -518, /** * Indicates that the context is not suitable to perform the * requested operation. */ NGHTTP2_ERR_INVALID_STATE = -519, /** * The user callback function failed due to the temporal error. */ NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE = -521, /** * The length of the frame is invalid, either too large or too small. */ NGHTTP2_ERR_FRAME_SIZE_ERROR = -522, /** * Header block inflate/deflate error. */ NGHTTP2_ERR_HEADER_COMP = -523, /** * Flow control error */ NGHTTP2_ERR_FLOW_CONTROL = -524, /** * Insufficient buffer size given to function. */ NGHTTP2_ERR_INSUFF_BUFSIZE = -525, /** * Callback was paused by the application */ NGHTTP2_ERR_PAUSE = -526, /** * There are too many in-flight SETTING frame and no more * transmission of SETTINGS is allowed. */ NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS = -527, /** * The server push is disabled. */ NGHTTP2_ERR_PUSH_DISABLED = -528, /** * DATA or HEADERS frame for a given stream has been already * submitted and has not been fully processed yet. Application * should wait for the transmission of the previously submitted * frame before submitting another. */ NGHTTP2_ERR_DATA_EXIST = -529, /** * The current session is closing due to a connection error or * `nghttp2_session_terminate_session()` is called. */ NGHTTP2_ERR_SESSION_CLOSING = -530, /** * Invalid HTTP header field was received and stream is going to be * closed. */ NGHTTP2_ERR_HTTP_HEADER = -531, /** * Violation in HTTP messaging rule. */ NGHTTP2_ERR_HTTP_MESSAGING = -532, /** * Stream was refused. */ NGHTTP2_ERR_REFUSED_STREAM = -533, /** * Unexpected internal error, but recovered. */ NGHTTP2_ERR_INTERNAL = -534, /** * Indicates that a processing was canceled. */ NGHTTP2_ERR_CANCEL = -535, /** * When a local endpoint expects to receive SETTINGS frame, it * receives an other type of frame. */ NGHTTP2_ERR_SETTINGS_EXPECTED = -536, /** * When a local endpoint receives too many settings entries * in a single SETTINGS frame. */ NGHTTP2_ERR_TOO_MANY_SETTINGS = -537, /** * The errors < :enum:`nghttp2_error.NGHTTP2_ERR_FATAL` mean that * the library is under unexpected condition and processing was * terminated (e.g., out of memory). If application receives this * error code, it must stop using that :type:`nghttp2_session` * object and only allowed operation for that object is deallocate * it using `nghttp2_session_del()`. */ NGHTTP2_ERR_FATAL = -900, /** * Out of memory. This is a fatal error. */ NGHTTP2_ERR_NOMEM = -901, /** * The user callback function failed. This is a fatal error. */ NGHTTP2_ERR_CALLBACK_FAILURE = -902, /** * Invalid client magic (see :macro:`NGHTTP2_CLIENT_MAGIC`) was * received and further processing is not possible. */ NGHTTP2_ERR_BAD_CLIENT_MAGIC = -903, /** * Possible flooding by peer was detected in this HTTP/2 session. * Flooding is measured by how many PING and SETTINGS frames with * ACK flag set are queued for transmission. These frames are * response for the peer initiated frames, and peer can cause memory * exhaustion on server side to send these frames forever and does * not read network. */ NGHTTP2_ERR_FLOODED = -904, /** * When a local endpoint receives too many CONTINUATION frames * following a HEADER frame. */ NGHTTP2_ERR_TOO_MANY_CONTINUATIONS = -905, } nghttp2_error; /** * @struct * * The object representing single contiguous buffer. */ typedef struct { /** * The pointer to the buffer. */ uint8_t *base; /** * The length of the buffer. */ size_t len; } nghttp2_vec; struct nghttp2_rcbuf; /** * @struct * * The object representing reference counted buffer. The details of * this structure are intentionally hidden from the public API. */ typedef struct nghttp2_rcbuf nghttp2_rcbuf; /** * @function * * Increments the reference count of |rcbuf| by 1. */ NGHTTP2_EXTERN void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf); /** * @function * * Decrements the reference count of |rcbuf| by 1. If the reference * count becomes zero, the object pointed by |rcbuf| will be freed. * In this case, application must not use |rcbuf| again. */ NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf); /** * @function * * Returns the underlying buffer managed by |rcbuf|. */ NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf); /** * @function * * Returns nonzero if the underlying buffer is statically allocated, * and 0 otherwise. This can be useful for language bindings that wish * to avoid creating duplicate strings for these buffers. */ NGHTTP2_EXTERN int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf); /** * @enum * * The flags for header field name/value pair. */ typedef enum { /** * No flag set. */ NGHTTP2_NV_FLAG_NONE = 0, /** * Indicates that this name/value pair must not be indexed ("Literal * Header Field never Indexed" representation must be used in HPACK * encoding). Other implementation calls this bit as "sensitive". */ NGHTTP2_NV_FLAG_NO_INDEX = 0x01, /** * This flag is set solely by application. If this flag is set, the * library does not make a copy of header field name. This could * improve performance. */ NGHTTP2_NV_FLAG_NO_COPY_NAME = 0x02, /** * This flag is set solely by application. If this flag is set, the * library does not make a copy of header field value. This could * improve performance. */ NGHTTP2_NV_FLAG_NO_COPY_VALUE = 0x04 } nghttp2_nv_flag; /** * @struct * * The name/value pair, which mainly used to represent header fields. */ typedef struct { /** * The |name| byte string. If this struct is presented from library * (e.g., :type:`nghttp2_on_frame_recv_callback`), |name| is * guaranteed to be NULL-terminated. For some callbacks * (:type:`nghttp2_before_frame_send_callback`, * :type:`nghttp2_on_frame_send_callback`, and * :type:`nghttp2_on_frame_not_send_callback`), it may not be * NULL-terminated if header field is passed from application with * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`). * When application is constructing this struct, |name| is not * required to be NULL-terminated. */ uint8_t *name; /** * The |value| byte string. If this struct is presented from * library (e.g., :type:`nghttp2_on_frame_recv_callback`), |value| * is guaranteed to be NULL-terminated. For some callbacks * (:type:`nghttp2_before_frame_send_callback`, * :type:`nghttp2_on_frame_send_callback`, and * :type:`nghttp2_on_frame_not_send_callback`), it may not be * NULL-terminated if header field is passed from application with * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE`). * When application is constructing this struct, |value| is not * required to be NULL-terminated. */ uint8_t *value; /** * The length of the |name|, excluding terminating NULL. */ size_t namelen; /** * The length of the |value|, excluding terminating NULL. */ size_t valuelen; /** * Bitwise OR of one or more of :type:`nghttp2_nv_flag`. */ uint8_t flags; } nghttp2_nv; /** * @enum * * The frame types in HTTP/2 specification. */ typedef enum { /** * The DATA frame. */ NGHTTP2_DATA = 0, /** * The HEADERS frame. */ NGHTTP2_HEADERS = 0x01, /** * The PRIORITY frame. */ NGHTTP2_PRIORITY = 0x02, /** * The RST_STREAM frame. */ NGHTTP2_RST_STREAM = 0x03, /** * The SETTINGS frame. */ NGHTTP2_SETTINGS = 0x04, /** * The PUSH_PROMISE frame. */ NGHTTP2_PUSH_PROMISE = 0x05, /** * The PING frame. */ NGHTTP2_PING = 0x06, /** * The GOAWAY frame. */ NGHTTP2_GOAWAY = 0x07, /** * The WINDOW_UPDATE frame. */ NGHTTP2_WINDOW_UPDATE = 0x08, /** * The CONTINUATION frame. This frame type won't be passed to any * callbacks because the library processes this frame type and its * preceding HEADERS/PUSH_PROMISE as a single frame. */ NGHTTP2_CONTINUATION = 0x09, /** * The ALTSVC frame, which is defined in `RFC 7383 * `_. */ NGHTTP2_ALTSVC = 0x0a, /** * The ORIGIN frame, which is defined by `RFC 8336 * `_. */ NGHTTP2_ORIGIN = 0x0c, /** * The PRIORITY_UPDATE frame, which is defined by :rfc:`9218`. */ NGHTTP2_PRIORITY_UPDATE = 0x10 } nghttp2_frame_type; /** * @enum * * The flags for HTTP/2 frames. This enum defines all flags for all * frames. */ typedef enum { /** * No flag set. */ NGHTTP2_FLAG_NONE = 0, /** * The END_STREAM flag. */ NGHTTP2_FLAG_END_STREAM = 0x01, /** * The END_HEADERS flag. */ NGHTTP2_FLAG_END_HEADERS = 0x04, /** * The ACK flag. */ NGHTTP2_FLAG_ACK = 0x01, /** * The PADDED flag. */ NGHTTP2_FLAG_PADDED = 0x08, /** * The PRIORITY flag. */ NGHTTP2_FLAG_PRIORITY = 0x20 } nghttp2_flag; /** * @enum * The SETTINGS ID. */ typedef enum { /** * SETTINGS_HEADER_TABLE_SIZE */ NGHTTP2_SETTINGS_HEADER_TABLE_SIZE = 0x01, /** * SETTINGS_ENABLE_PUSH */ NGHTTP2_SETTINGS_ENABLE_PUSH = 0x02, /** * SETTINGS_MAX_CONCURRENT_STREAMS */ NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x03, /** * SETTINGS_INITIAL_WINDOW_SIZE */ NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04, /** * SETTINGS_MAX_FRAME_SIZE */ NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05, /** * SETTINGS_MAX_HEADER_LIST_SIZE */ NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06, /** * SETTINGS_ENABLE_CONNECT_PROTOCOL * (`RFC 8441 `_) */ NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, /** * SETTINGS_NO_RFC7540_PRIORITIES (:rfc:`9218`) */ NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09 } nghttp2_settings_id; /* Note: If we add SETTINGS, update the capacity of NGHTTP2_INBOUND_NUM_IV as well */ /** * @macro * * .. warning:: * * Deprecated. The initial max concurrent streams is 0xffffffffu. * * Default maximum number of incoming concurrent streams. Use * `nghttp2_submit_settings()` with * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS` * to change the maximum number of incoming concurrent streams. * * .. note:: * * The maximum number of outgoing concurrent streams is 100 by * default. */ #define NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) /** * @enum * The status codes for the RST_STREAM and GOAWAY frames. */ typedef enum { /** * No errors. */ NGHTTP2_NO_ERROR = 0x00, /** * PROTOCOL_ERROR */ NGHTTP2_PROTOCOL_ERROR = 0x01, /** * INTERNAL_ERROR */ NGHTTP2_INTERNAL_ERROR = 0x02, /** * FLOW_CONTROL_ERROR */ NGHTTP2_FLOW_CONTROL_ERROR = 0x03, /** * SETTINGS_TIMEOUT */ NGHTTP2_SETTINGS_TIMEOUT = 0x04, /** * STREAM_CLOSED */ NGHTTP2_STREAM_CLOSED = 0x05, /** * FRAME_SIZE_ERROR */ NGHTTP2_FRAME_SIZE_ERROR = 0x06, /** * REFUSED_STREAM */ NGHTTP2_REFUSED_STREAM = 0x07, /** * CANCEL */ NGHTTP2_CANCEL = 0x08, /** * COMPRESSION_ERROR */ NGHTTP2_COMPRESSION_ERROR = 0x09, /** * CONNECT_ERROR */ NGHTTP2_CONNECT_ERROR = 0x0a, /** * ENHANCE_YOUR_CALM */ NGHTTP2_ENHANCE_YOUR_CALM = 0x0b, /** * INADEQUATE_SECURITY */ NGHTTP2_INADEQUATE_SECURITY = 0x0c, /** * HTTP_1_1_REQUIRED */ NGHTTP2_HTTP_1_1_REQUIRED = 0x0d } nghttp2_error_code; /** * @struct * The frame header. */ typedef struct { /** * The length field of this frame, excluding frame header. */ size_t length; /** * The stream identifier (aka, stream ID) */ int32_t stream_id; /** * The type of this frame. See `nghttp2_frame_type`. */ uint8_t type; /** * The flags. */ uint8_t flags; /** * Reserved bit in frame header. Currently, this is always set to 0 * and application should not expect something useful in here. */ uint8_t reserved; } nghttp2_frame_hd; /** * @union * * This union represents the some kind of data source passed to * :type:`nghttp2_data_source_read_callback2`. */ typedef union { /** * The integer field, suitable for a file descriptor. */ int fd; /** * The pointer to an arbitrary object. */ void *ptr; } nghttp2_data_source; /** * @enum * * The flags used to set in |data_flags| output parameter in * :type:`nghttp2_data_source_read_callback2`. */ typedef enum { /** * No flag set. */ NGHTTP2_DATA_FLAG_NONE = 0, /** * Indicates EOF was sensed. */ NGHTTP2_DATA_FLAG_EOF = 0x01, /** * Indicates that END_STREAM flag must not be set even if * NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send * trailer fields with `nghttp2_submit_request2()` or * `nghttp2_submit_response2()`. */ NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02, /** * Indicates that application will send complete DATA frame in * :type:`nghttp2_send_data_callback`. */ NGHTTP2_DATA_FLAG_NO_COPY = 0x04 } nghttp2_data_flag; #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_data_source_read_callback2` * instead. * * Callback function invoked when the library wants to read data from * the |source|. The read data is sent in the stream |stream_id|. * The implementation of this function must read at most |length| * bytes of data from |source| (or possibly other places) and store * them in |buf| and return number of data stored in |buf|. If EOF is * reached, set :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag * in |*data_flags|. * * Sometime it is desirable to avoid copying data into |buf| and let * application to send data directly. To achieve this, set * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to * |*data_flags| (and possibly other flags, just like when we do * copy), and return the number of bytes to send without copying data * into |buf|. The library, seeing * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke * :type:`nghttp2_send_data_callback`. The application must send * complete DATA frame in that callback. * * If this callback is set by `nghttp2_submit_request()`, * `nghttp2_submit_response()` or `nghttp2_submit_headers()` and * `nghttp2_submit_data()` with flag parameter * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to * |*data_flags|, DATA frame will have END_STREAM flag set. Usually, * this is expected behaviour and all are fine. One exception is send * trailer fields. You cannot send trailer fields after sending frame * with END_STREAM set. To avoid this problem, one can set * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the * library not to set END_STREAM in DATA frame. Then application can * use `nghttp2_submit_trailer()` to send trailer fields. * `nghttp2_submit_trailer()` can be called inside this callback. * * If the application wants to postpone DATA frames (e.g., * asynchronous I/O, or reading data blocks for long time), it is * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` * without reading any data in this invocation. The library removes * DATA frame from the outgoing queue temporarily. To move back * deferred DATA frame to outgoing queue, call * `nghttp2_session_resume_data()`. * * By default, |length| is limited to 16KiB at maximum. If peer * allows larger frames, application can enlarge transmission buffer * size. See :type:`nghttp2_data_source_read_length_callback` for * more details. * * If the application just wants to return from * `nghttp2_session_send()` or `nghttp2_session_mem_send()` without * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. * * In case of error, there are 2 choices. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will * close the stream by issuing RST_STREAM with * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. If a different * error code is desirable, use `nghttp2_submit_rst_stream()` with a * desired error code and then return * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. * Returning :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will * signal the entire session failure. */ typedef ssize_t (*nghttp2_data_source_read_callback)( nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when the library wants to read data from * the |source|. The read data is sent in the stream |stream_id|. * The implementation of this function must read at most |length| * bytes of data from |source| (or possibly other places) and store * them in |buf| and return number of data stored in |buf|. If EOF is * reached, set :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag * in |*data_flags|. * * Sometime it is desirable to avoid copying data into |buf| and let * application to send data directly. To achieve this, set * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to * |*data_flags| (and possibly other flags, just like when we do * copy), and return the number of bytes to send without copying data * into |buf|. The library, seeing * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke * :type:`nghttp2_send_data_callback`. The application must send * complete DATA frame in that callback. * * If this callback is set by `nghttp2_submit_request2()`, * `nghttp2_submit_response2()` or `nghttp2_submit_headers()` and * `nghttp2_submit_data2()` with flag parameter * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to * |*data_flags|, DATA frame will have END_STREAM flag set. Usually, * this is expected behaviour and all are fine. One exception is send * trailer fields. You cannot send trailer fields after sending frame * with END_STREAM set. To avoid this problem, one can set * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the * library not to set END_STREAM in DATA frame. Then application can * use `nghttp2_submit_trailer()` to send trailer fields. * `nghttp2_submit_trailer()` can be called inside this callback. * * If the application wants to postpone DATA frames (e.g., * asynchronous I/O, or reading data blocks for long time), it is * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` * without reading any data in this invocation. The library removes * DATA frame from the outgoing queue temporarily. To move back * deferred DATA frame to outgoing queue, call * `nghttp2_session_resume_data()`. * * By default, |length| is limited to 16KiB at maximum. If peer * allows larger frames, application can enlarge transmission buffer * size. See :type:`nghttp2_data_source_read_length_callback` for * more details. * * If the application just wants to return from * `nghttp2_session_send()` or `nghttp2_session_mem_send2()` without * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. * * In case of error, there are 2 choices. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will * close the stream by issuing RST_STREAM with * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. If a different * error code is desirable, use `nghttp2_submit_rst_stream()` with a * desired error code and then return * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. * Returning :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will * signal the entire session failure. */ typedef nghttp2_ssize (*nghttp2_data_source_read_callback2)( nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @struct * * .. warning:: * * Deprecated. Use :type:`nghttp2_data_provider2` instead. * * This struct represents the data source and the way to read a chunk * of data from it. */ typedef struct { /** * The data source. */ nghttp2_data_source source; /** * The callback function to read a chunk of data from the |source|. */ nghttp2_data_source_read_callback read_callback; } nghttp2_data_provider; #endif /* NGHTTP2_NO_SSIZE_T */ /** * @struct * * This struct represents the data source and the way to read a chunk * of data from it. */ typedef struct { /** * The data source. */ nghttp2_data_source source; /** * The callback function to read a chunk of data from the |source|. */ nghttp2_data_source_read_callback2 read_callback; } nghttp2_data_provider2; /** * @struct * * The DATA frame. The received data is delivered via * :type:`nghttp2_on_data_chunk_recv_callback`. */ typedef struct { nghttp2_frame_hd hd; /** * The length of the padding in this frame. This includes PAD_HIGH * and PAD_LOW. */ size_t padlen; } nghttp2_data; /** * @enum * * The category of HEADERS, which indicates the role of the frame. In * HTTP/2 spec, request, response, push response and other arbitrary * headers (e.g., trailer fields) are all called just HEADERS. To * give the application the role of incoming HEADERS frame, we define * several categories. */ typedef enum { /** * The HEADERS frame is opening new stream, which is analogous to * SYN_STREAM in SPDY. */ NGHTTP2_HCAT_REQUEST = 0, /** * The HEADERS frame is the first response headers, which is * analogous to SYN_REPLY in SPDY. */ NGHTTP2_HCAT_RESPONSE = 1, /** * The HEADERS frame is the first headers sent against reserved * stream. */ NGHTTP2_HCAT_PUSH_RESPONSE = 2, /** * The HEADERS frame which does not apply for the above categories, * which is analogous to HEADERS in SPDY. If non-final response * (e.g., status 1xx) is used, final response HEADERS frame will be * categorized here. */ NGHTTP2_HCAT_HEADERS = 3 } nghttp2_headers_category; /** * @struct * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The structure to specify stream dependency. */ typedef struct { /** * The stream ID of the stream to depend on. Specifying 0 makes * stream not depend any other stream. */ int32_t stream_id; /** * The weight of this dependency. */ int32_t weight; /** * nonzero means exclusive dependency */ uint8_t exclusive; } nghttp2_priority_spec; /** * @struct * * The HEADERS frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The length of the padding in this frame. This includes PAD_HIGH * and PAD_LOW. */ size_t padlen; /** * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The priority specification */ nghttp2_priority_spec pri_spec; /** * The name/value pairs. */ nghttp2_nv *nva; /** * The number of name/value pairs in |nva|. */ size_t nvlen; /** * The category of this HEADERS frame. */ nghttp2_headers_category cat; } nghttp2_headers; /** * @struct * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * The PRIORITY frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The priority specification. */ nghttp2_priority_spec pri_spec; } nghttp2_priority; /** * @struct * * The RST_STREAM frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The error code. See :type:`nghttp2_error_code`. */ uint32_t error_code; } nghttp2_rst_stream; /** * @struct * * The SETTINGS ID/Value pair. It has the following members: */ typedef struct { /** * The SETTINGS ID. See :type:`nghttp2_settings_id`. */ int32_t settings_id; /** * The value of this entry. */ uint32_t value; } nghttp2_settings_entry; /** * @struct * * The SETTINGS frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The number of SETTINGS ID/Value pairs in |iv|. */ size_t niv; /** * The pointer to the array of SETTINGS ID/Value pair. */ nghttp2_settings_entry *iv; } nghttp2_settings; /** * @struct * * The PUSH_PROMISE frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The length of the padding in this frame. This includes PAD_HIGH * and PAD_LOW. */ size_t padlen; /** * The name/value pairs. */ nghttp2_nv *nva; /** * The number of name/value pairs in |nva|. */ size_t nvlen; /** * The promised stream ID */ int32_t promised_stream_id; /** * Reserved bit. Currently this is always set to 0 and application * should not expect something useful in here. */ uint8_t reserved; } nghttp2_push_promise; /** * @struct * * The PING frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The opaque data */ uint8_t opaque_data[8]; } nghttp2_ping; /** * @struct * * The GOAWAY frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The last stream stream ID. */ int32_t last_stream_id; /** * The error code. See :type:`nghttp2_error_code`. */ uint32_t error_code; /** * The additional debug data */ uint8_t *opaque_data; /** * The length of |opaque_data| member. */ size_t opaque_data_len; /** * Reserved bit. Currently this is always set to 0 and application * should not expect something useful in here. */ uint8_t reserved; } nghttp2_goaway; /** * @struct * * The WINDOW_UPDATE frame. It has the following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The window size increment. */ int32_t window_size_increment; /** * Reserved bit. Currently this is always set to 0 and application * should not expect something useful in here. */ uint8_t reserved; } nghttp2_window_update; /** * @struct * * The extension frame. It has following members: */ typedef struct { /** * The frame header. */ nghttp2_frame_hd hd; /** * The pointer to extension payload. The exact pointer type is * determined by hd.type. * * Currently, no extension is supported. This is a place holder for * the future extensions. */ void *payload; } nghttp2_extension; /** * @union * * This union includes all frames to pass them to various function * calls as nghttp2_frame type. The CONTINUATION frame is omitted * from here because the library deals with it internally. */ typedef union { /** * The frame header, which is convenient to inspect frame header. */ nghttp2_frame_hd hd; /** * The DATA frame. */ nghttp2_data data; /** * The HEADERS frame. */ nghttp2_headers headers; /** * The PRIORITY frame. */ nghttp2_priority priority; /** * The RST_STREAM frame. */ nghttp2_rst_stream rst_stream; /** * The SETTINGS frame. */ nghttp2_settings settings; /** * The PUSH_PROMISE frame. */ nghttp2_push_promise push_promise; /** * The PING frame. */ nghttp2_ping ping; /** * The GOAWAY frame. */ nghttp2_goaway goaway; /** * The WINDOW_UPDATE frame. */ nghttp2_window_update window_update; /** * The extension frame. */ nghttp2_extension ext; } nghttp2_frame; #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_send_callback2` instead. * * Callback function invoked when |session| wants to send data to the * remote peer. The implementation of this function must send at most * |length| bytes of data stored in |data|. The |flags| is currently * not used and always 0. It must return the number of bytes sent if * it succeeds. If it cannot send any single byte without blocking, * it must return :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. For * other errors, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The * |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * This callback is required if the application uses * `nghttp2_session_send()` to send data to the remote endpoint. If * the application uses solely `nghttp2_session_mem_send()` instead, * this callback function is unnecessary. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_send_callback()`. * * .. note:: * * The |length| may be very small. If that is the case, and * application disables Nagle algorithm (``TCP_NODELAY``), then just * writing |data| to the network stack leads to very small packet, * and it is very inefficient. An application should be responsible * to buffer up small chunks of data as necessary to avoid this * situation. */ typedef ssize_t (*nghttp2_send_callback)(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when |session| wants to send data to the * remote peer. The implementation of this function must send at most * |length| bytes of data stored in |data|. The |flags| is currently * not used and always 0. It must return the number of bytes sent if * it succeeds. If it cannot send any single byte without blocking, * it must return :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. For * other errors, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The * |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * This callback is required if the application uses * `nghttp2_session_send()` to send data to the remote endpoint. If * the application uses solely `nghttp2_session_mem_send2()` instead, * this callback function is unnecessary. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_send_callback2()`. * * .. note:: * * The |length| may be very small. If that is the case, and * application disables Nagle algorithm (``TCP_NODELAY``), then just * writing |data| to the network stack leads to very small packet, * and it is very inefficient. An application should be responsible * to buffer up small chunks of data as necessary to avoid this * situation. */ typedef nghttp2_ssize (*nghttp2_send_callback2)(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data); /** * @functypedef * * Callback function invoked when * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in * :type:`nghttp2_data_source_read_callback` to send complete DATA * frame. * * The |frame| is a DATA frame to send. The |framehd| is the * serialized frame header (9 bytes). The |length| is the length of * application data to send (this does not include padding). The * |source| is the same pointer passed to * :type:`nghttp2_data_source_read_callback`. * * The application first must send frame header |framehd| of length 9 * bytes. If ``frame->data.padlen > 0``, send 1 byte of value * ``frame->data.padlen - 1``. Then send exactly |length| bytes of * application data. Finally, if ``frame->data.padlen > 1``, send * ``frame->data.padlen - 1`` bytes of zero as padding. * * The application has to send complete DATA frame in this callback. * If all data were written successfully, return 0. * * If it cannot send any data at all, just return * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`; the library will call * this callback with the same parameters later (It is recommended to * send complete DATA frame at once in this function to deal with * error; if partial frame data has already sent, it is impossible to * send another data in that state, and all we can do is tear down * connection). When data is fully processed, but application wants * to make `nghttp2_session_mem_send2()` or `nghttp2_session_send()` * return immediately without processing next frames, return * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. If application decided to * reset this stream, return * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, then * the library will send RST_STREAM with INTERNAL_ERROR as error code. * The application can also return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which will * result in connection closure. Returning any other value is treated * as :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. */ typedef int (*nghttp2_send_data_callback)(nghttp2_session *session, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_recv_callback2` instead. * * Callback function invoked when |session| wants to receive data from * the remote peer. The implementation of this function must read at * most |length| bytes of data and store it in |buf|. The |flags| is * currently not used and always 0. It must return the number of * bytes written in |buf| if it succeeds. If it cannot read any * single byte without blocking, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF * before it reads any single byte, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * Returning 0 is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. The |user_data| * pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * This callback is required if the application uses * `nghttp2_session_recv()` to receive data from the remote endpoint. * If the application uses solely `nghttp2_session_mem_recv()` * instead, this callback function is unnecessary. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_recv_callback()`. */ typedef ssize_t (*nghttp2_recv_callback)(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when |session| wants to receive data from * the remote peer. The implementation of this function must read at * most |length| bytes of data and store it in |buf|. The |flags| is * currently not used and always 0. It must return the number of * bytes written in |buf| if it succeeds. If it cannot read any * single byte without blocking, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF * before it reads any single byte, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * Returning 0 is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. The |user_data| * pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * This callback is required if the application uses * `nghttp2_session_recv()` to receive data from the remote endpoint. * If the application uses solely `nghttp2_session_mem_recv2()` * instead, this callback function is unnecessary. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_recv_callback2()`. */ typedef nghttp2_ssize (*nghttp2_recv_callback2)(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *user_data); /** * @functypedef * * Callback function invoked by `nghttp2_session_recv()` and * `nghttp2_session_mem_recv2()` when a frame is received. The * |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` * member of their data structure are always ``NULL`` and 0 * respectively. The header name/value pairs are emitted via * :type:`nghttp2_on_header_callback`. * * Only HEADERS and DATA frame can signal the end of incoming data. * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the * |frame| is the last frame from the remote peer in this stream. * * This callback won't be called for CONTINUATION frames. * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame. * * The implementation of this function must return 0 if it succeeds. * If nonzero value is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_frame_recv_callback()`. */ typedef int (*nghttp2_on_frame_recv_callback)(nghttp2_session *session, const nghttp2_frame *frame, void *user_data); /** * @functypedef * * Callback function invoked by `nghttp2_session_recv()` and * `nghttp2_session_mem_recv2()` when an invalid non-DATA frame is * received. The error is indicated by the |lib_error_code|, which is * one of the values defined in :type:`nghttp2_error`. When this * callback function is invoked, the library automatically submits * either RST_STREAM or GOAWAY frame. The |user_data| pointer is the * third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` * member of their data structure are always ``NULL`` and 0 * respectively. * * The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_invalid_frame_recv_callback()`. */ typedef int (*nghttp2_on_invalid_frame_recv_callback)( nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data); /** * @functypedef * * Callback function invoked when a chunk of data in DATA frame is * received. The |stream_id| is the stream ID this DATA frame belongs * to. The |flags| is the flags of DATA frame which this data chunk * is contained. ``(flags & NGHTTP2_FLAG_END_STREAM) != 0`` does not * necessarily mean this chunk of data is the last one in the stream. * You should use :type:`nghttp2_on_frame_recv_callback` to know all * data frames are received. The |user_data| pointer is the third * argument passed in to the call to `nghttp2_session_client_new()` or * `nghttp2_session_server_new()`. * * If the application uses `nghttp2_session_mem_recv2()`, it can * return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make * `nghttp2_session_mem_recv2()` return without processing further * input bytes. The memory by pointed by the |data| is retained until * `nghttp2_session_mem_recv2()` or `nghttp2_session_recv()` is * called. The application must retain the input bytes which was used * to produce the |data| parameter, because it may refer to the memory * region included in the input bytes. * * The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error, and * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_data_chunk_recv_callback()`. */ typedef int (*nghttp2_on_data_chunk_recv_callback)(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data); /** * @functypedef * * Callback function invoked just before the non-DATA frame |frame| is * sent. The |user_data| pointer is the third argument passed in to * the call to `nghttp2_session_client_new()` or * `nghttp2_session_server_new()`. * * The implementation of this function must return 0 if it succeeds. * It can also return :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` to * cancel the transmission of the given frame. * * If there is a fatal error while executing this callback, the * implementation should return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which makes * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * If the other value is returned, it is treated as if * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. * But the implementation should not rely on this since the library * may define new return value to extend its capability. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_before_frame_send_callback()`. */ typedef int (*nghttp2_before_frame_send_callback)(nghttp2_session *session, const nghttp2_frame *frame, void *user_data); /** * @functypedef * * Callback function invoked after the frame |frame| is sent. The * |user_data| pointer is the third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * * The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_frame_send_callback()`. */ typedef int (*nghttp2_on_frame_send_callback)(nghttp2_session *session, const nghttp2_frame *frame, void *user_data); /** * @functypedef * * Callback function invoked after the non-DATA frame |frame| is not * sent because of the error. The error is indicated by the * |lib_error_code|, which is one of the values defined in * :type:`nghttp2_error`. The |user_data| pointer is the third * argument passed in to the call to `nghttp2_session_client_new()` or * `nghttp2_session_server_new()`. * * The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * `nghttp2_session_get_stream_user_data()` can be used to get * associated data. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_frame_not_send_callback()`. */ typedef int (*nghttp2_on_frame_not_send_callback)(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data); /** * @functypedef * * Callback function invoked when the stream |stream_id| is closed. * The reason of closure is indicated by the |error_code|. The * |error_code| is usually one of :enum:`nghttp2_error_code`, but that * is not guaranteed. The stream_user_data, which was specified in * `nghttp2_submit_request2()` or `nghttp2_submit_headers()`, is still * available in this function. The |user_data| pointer is the third * argument passed in to the call to `nghttp2_session_client_new()` or * `nghttp2_session_server_new()`. * * This function is also called for a stream in reserved state. * * The implementation of this function must return 0 if it succeeds. * If nonzero is returned, it is treated as fatal error and * `nghttp2_session_recv()`, `nghttp2_session_mem_recv2()`, * `nghttp2_session_send()`, and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_stream_close_callback()`. */ typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data); /** * @functypedef * * Callback function invoked when the reception of header block in * HEADERS or PUSH_PROMISE is started. Each header name/value pair * will be emitted by :type:`nghttp2_on_header_callback`. * * The ``frame->hd.flags`` may not have * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_HEADERS` flag set, which * indicates that one or more CONTINUATION frames are involved. But * the application does not need to care about that because the header * name/value pairs are emitted transparently regardless of * CONTINUATION frames. * * The server applications probably create an object to store * information about new stream if ``frame->hd.type == * NGHTTP2_HEADERS`` and ``frame->headers.cat == * NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side, * ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST`` * containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing * trailer fields and never get PUSH_PROMISE in this callback. * * For the client applications, ``frame->hd.type`` is either * ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of * ``NGHTTP2_HEADERS``, ``frame->headers.cat == * NGHTTP2_HCAT_RESPONSE`` means that it is the first response * headers, but it may be non-final response which is indicated by 1xx * status code. In this case, there may be zero or more HEADERS frame * with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` which has * non-final response code and finally client gets exactly one HEADERS * frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` * containing final response headers (non-1xx status code). The * trailer fields also has ``frame->headers.cat == * NGHTTP2_HCAT_HEADERS`` which does not contain any status code. * * Returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will * close the stream (promised stream if frame is PUSH_PROMISE) by * issuing RST_STREAM with * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, * :type:`nghttp2_on_header_callback` and * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a * different error code is desirable, use * `nghttp2_submit_rst_stream()` with a desired error code and then * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. * Again, use ``frame->push_promise.promised_stream_id`` as stream_id * parameter in `nghttp2_submit_rst_stream()` if frame is * PUSH_PROMISE. * * The implementation of this function must return 0 if it succeeds. * It can return * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` to * reset the stream (promised stream if frame is PUSH_PROMISE). For * critical errors, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * value is returned, it is treated as if * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. If * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, * `nghttp2_session_mem_recv2()` function will immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_begin_headers_callback()`. */ typedef int (*nghttp2_on_begin_headers_callback)(nghttp2_session *session, const nghttp2_frame *frame, void *user_data); /** * @functypedef * * Callback function invoked when a header name/value pair is received * for the |frame|. The |name| of length |namelen| is header name. * The |value| of length |valuelen| is header value. The |flags| is * bitwise OR of one or more of :type:`nghttp2_nv_flag`. * * If :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_INDEX` is set in * |flags|, the receiver must not index this name/value pair when * forwarding it to the next hop. More specifically, "Literal Header * Field never Indexed" representation must be used in HPACK encoding. * * When this callback is invoked, ``frame->hd.type`` is either * :enum:`nghttp2_frame_type.NGHTTP2_HEADERS` or * :enum:`nghttp2_frame_type.NGHTTP2_PUSH_PROMISE`. After all header * name/value pairs are processed with this callback, and no error has * been detected, :type:`nghttp2_on_frame_recv_callback` will be * invoked. If there is an error in decompression, * :type:`nghttp2_on_frame_recv_callback` for the |frame| will not be * invoked. * * Both |name| and |value| are guaranteed to be NULL-terminated. The * |namelen| and |valuelen| do not include terminal NULL. If * `nghttp2_option_set_no_http_messaging()` is used with nonzero * value, NULL character may be included in |name| or |value| before * terminating NULL. * * Please note that unless `nghttp2_option_set_no_http_messaging()` is * used, nghttp2 library does perform validation against the |name| * and the |value| using `nghttp2_check_header_name()` and * `nghttp2_check_header_value()`. In addition to this, nghttp2 * performs validation based on HTTP Messaging rule, which is briefly * explained in :ref:`http-messaging` section. * * If the application uses `nghttp2_session_mem_recv2()`, it can * return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make * `nghttp2_session_mem_recv2()` return without processing further * input bytes. The memory pointed by |frame|, |name| and |value| * parameters are retained until `nghttp2_session_mem_recv2()` or * `nghttp2_session_recv()` is called. The application must retain * the input bytes which was used to produce these parameters, because * it may refer to the memory region included in the input bytes. * * Returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will * close the stream (promised stream if frame is PUSH_PROMISE) by * issuing RST_STREAM with * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, * :type:`nghttp2_on_header_callback` and * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a * different error code is desirable, use * `nghttp2_submit_rst_stream()` with a desired error code and then * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. * Again, use ``frame->push_promise.promised_stream_id`` as stream_id * parameter in `nghttp2_submit_rst_stream()` if frame is * PUSH_PROMISE. * * The implementation of this function must return 0 if it succeeds. * It may return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` or * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. For * other critical failures, it must return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * nonzero value is returned, it is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_header_callback()`. * * .. warning:: * * Application should properly limit the total buffer size to store * incoming header fields. Without it, peer may send large number * of header fields or large header fields to cause out of memory in * local endpoint. Due to how HPACK works, peer can do this * effectively without using much memory on their own. */ typedef int (*nghttp2_on_header_callback)(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data); /** * @functypedef * * Callback function invoked when a header name/value pair is received * for the |frame|. The |name| is header name. The |value| is header * value. The |flags| is bitwise OR of one or more of * :type:`nghttp2_nv_flag`. * * This callback behaves like :type:`nghttp2_on_header_callback`, * except that |name| and |value| are stored in reference counted * buffer. If application wishes to keep these references without * copying them, use `nghttp2_rcbuf_incref()` to increment their * reference count. It is the application's responsibility to call * `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so * as not to leak memory. If the |session| is created by * `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`, * the function to free memory is the one belongs to the mem * parameter. As long as this free function alives, |name| and * |value| can live after |session| was destroyed. */ typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name, nghttp2_rcbuf *value, uint8_t flags, void *user_data); /** * @functypedef * * Callback function invoked when a invalid header name/value pair is * received for the |frame|. * * The parameter and behaviour are similar to * :type:`nghttp2_on_header_callback`. The difference is that this * callback is only invoked when a invalid header name/value pair is * received which is treated as stream error if this callback is not * set. Only invalid regular header field are passed to this * callback. In other words, invalid pseudo header field is not * passed to this callback. Also header fields which includes upper * cased latter are also treated as error without passing them to this * callback. * * This callback is only considered if HTTP messaging validation is * turned on (which is on by default, see * `nghttp2_option_set_no_http_messaging()`). * * With this callback, application inspects the incoming invalid * field, and it also can reset stream from this callback by returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By * default, the error code is * :enum:`nghttp2_error_code.NGHTTP2_PROTOCOL_ERROR`. To change the * error code, call `nghttp2_submit_rst_stream()` with the error code * of choice in addition to returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. * * If 0 is returned, the header field is ignored, and the stream is * not reset. */ typedef int (*nghttp2_on_invalid_header_callback)( nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data); /** * @functypedef * * Callback function invoked when a invalid header name/value pair is * received for the |frame|. * * The parameter and behaviour are similar to * :type:`nghttp2_on_header_callback2`. The difference is that this * callback is only invoked when a invalid header name/value pair is * received which is silently ignored if this callback is not set. * Only invalid regular header field are passed to this callback. In * other words, invalid pseudo header field is not passed to this * callback. Also header fields which includes upper cased latter are * also treated as error without passing them to this callback. * * This callback is only considered if HTTP messaging validation is * turned on (which is on by default, see * `nghttp2_option_set_no_http_messaging()`). * * With this callback, application inspects the incoming invalid * field, and it also can reset stream from this callback by returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By * default, the error code is * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. To change the * error code, call `nghttp2_submit_rst_stream()` with the error code * of choice in addition to returning * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. */ typedef int (*nghttp2_on_invalid_header_callback2)( nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name, nghttp2_rcbuf *value, uint8_t flags, void *user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_select_padding_callback2` * instead. * * Callback function invoked when the library asks application how * many padding bytes are required for the transmission of the * |frame|. The application must choose the total length of payload * including padded bytes in range [frame->hd.length, max_payloadlen], * inclusive. Choosing number not in this range will be treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning * ``frame->hd.length`` means no padding is added. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions * immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_select_padding_callback()`. */ typedef ssize_t (*nghttp2_select_padding_callback)(nghttp2_session *session, const nghttp2_frame *frame, size_t max_payloadlen, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when the library asks application how * many padding bytes are required for the transmission of the * |frame|. The application must choose the total length of payload * including padded bytes in range [frame->hd.length, max_payloadlen], * inclusive. Choosing number not in this range will be treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning * ``frame->hd.length`` means no padding is added. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_select_padding_callback2()`. */ typedef nghttp2_ssize (*nghttp2_select_padding_callback2)( nghttp2_session *session, const nghttp2_frame *frame, size_t max_payloadlen, void *user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use * :type:`nghttp2_data_source_read_length_callback2` instead. * * Callback function invoked when library wants to get max length of * data to send data to the remote peer. The implementation of this * function should return a value in the following range. [1, * min(|session_remote_window_size|, |stream_remote_window_size|, * |remote_max_frame_size|)]. If a value greater than this range is * returned than the max allow value will be used. Returning a value * smaller than this range is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The * |frame_type| is provided for future extensibility and identifies * the type of frame (see :type:`nghttp2_frame_type`) for which to get * the length for. Currently supported frame types are: * :enum:`nghttp2_frame_type.NGHTTP2_DATA`. * * This callback can be used to control the length in bytes for which * :type:`nghttp2_data_source_read_callback` is allowed to send to the * remote endpoint. This callback is optional. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will signal the * entire session failure. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_data_source_read_length_callback()`. */ typedef ssize_t (*nghttp2_data_source_read_length_callback)( nghttp2_session *session, uint8_t frame_type, int32_t stream_id, int32_t session_remote_window_size, int32_t stream_remote_window_size, uint32_t remote_max_frame_size, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when library wants to get max length of * data to send data to the remote peer. The implementation of this * function should return a value in the following range. [1, * min(|session_remote_window_size|, |stream_remote_window_size|, * |remote_max_frame_size|)]. If a value greater than this range is * returned than the max allow value will be used. Returning a value * smaller than this range is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The * |frame_type| is provided for future extensibility and identifies * the type of frame (see :type:`nghttp2_frame_type`) for which to get * the length for. Currently supported frame types are: * :enum:`nghttp2_frame_type.NGHTTP2_DATA`. * * This callback can be used to control the length in bytes for which * :type:`nghttp2_data_source_read_callback` is allowed to send to the * remote endpoint. This callback is optional. Returning * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will signal the * entire session failure. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_data_source_read_length_callback2()`. */ typedef nghttp2_ssize (*nghttp2_data_source_read_length_callback2)( nghttp2_session *session, uint8_t frame_type, int32_t stream_id, int32_t session_remote_window_size, int32_t stream_remote_window_size, uint32_t remote_max_frame_size, void *user_data); /** * @functypedef * * Callback function invoked when a frame header is received. The * |hd| points to received frame header. * * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will * also be called when frame header of CONTINUATION frame is received. * * If both :type:`nghttp2_on_begin_frame_callback` and * :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or * PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback` * will be called first. * * The implementation of this function must return 0 if it succeeds. * If nonzero value is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * * To set this callback to :type:`nghttp2_session_callbacks`, use * `nghttp2_session_callbacks_set_on_begin_frame_callback()`. */ typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data); /** * @functypedef * * Callback function invoked when chunk of extension frame payload is * received. The |hd| points to frame header. The received * chunk is |data| of length |len|. * * The implementation of this function must return 0 if it succeeds. * * To abort processing this extension frame, return * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. * * If fatal error occurred, application should return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * values are returned, currently they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. */ typedef int (*nghttp2_on_extension_chunk_recv_callback)( nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, size_t len, void *user_data); /** * @functypedef * * Callback function invoked when library asks the application to * unpack extension payload from its wire format. The extension * payload has been passed to the application using * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header * is already unpacked by the library and provided as |hd|. * * To receive extension frames, the application must tell desired * extension frame type to the library using * `nghttp2_option_set_user_recv_extension_type()`. * * The implementation of this function may store the pointer to the * created object as a result of unpacking in |*payload|, and returns * 0. The pointer stored in |*payload| is opaque to the library, and * the library does not own its pointer. |*payload| is initialized as * ``NULL``. The |*payload| is available as ``frame->ext.payload`` in * :type:`nghttp2_on_frame_recv_callback`. Therefore if application * can free that memory inside :type:`nghttp2_on_frame_recv_callback` * callback. Of course, application has a liberty not to use * |*payload|, and do its own mechanism to process extension frames. * * To abort processing this extension frame, return * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. * * If fatal error occurred, application should return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * values are returned, currently they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. */ typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, void **payload, const nghttp2_frame_hd *hd, void *user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_pack_extension_callback2` * instead. * * Callback function invoked when library asks the application to pack * extension payload in its wire format. The frame header will be * packed by library. Application must pack payload only. * ``frame->ext.payload`` is the object passed to * `nghttp2_submit_extension()` as payload parameter. Application * must pack extension payload to the |buf| of its capacity |len| * bytes. The |len| is at least 16KiB. * * The implementation of this function should return the number of * bytes written into |buf| when it succeeds. * * To abort processing this extension frame, return * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and * :type:`nghttp2_on_frame_not_send_callback` will be invoked. * * If fatal error occurred, application should return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions * immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * values are returned, currently they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return * value is strictly larger than |len|, it is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. */ typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * * Callback function invoked when library asks the application to pack * extension payload in its wire format. The frame header will be * packed by library. Application must pack payload only. * ``frame->ext.payload`` is the object passed to * `nghttp2_submit_extension()` as payload parameter. Application * must pack extension payload to the |buf| of its capacity |len| * bytes. The |len| is at least 16KiB. * * The implementation of this function should return the number of * bytes written into |buf| when it succeeds. * * To abort processing this extension frame, return * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and * :type:`nghttp2_on_frame_not_send_callback` will be invoked. * * If fatal error occurred, application should return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` * functions immediately return * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other * values are returned, currently they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return * value is strictly larger than |len|, it is treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. */ typedef nghttp2_ssize (*nghttp2_pack_extension_callback2)( nghttp2_session *session, uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data); /** * @functypedef * * .. warning:: * * Deprecated. Use :type:`nghttp2_error_callback2` instead. * * Callback function invoked when library provides the error message * intended for human consumption. This callback is solely for * debugging purpose. The |msg| is typically NULL-terminated string * of length |len|. |len| does not include the sentinel NULL * character. * * The format of error message may change between nghttp2 library * versions. The application should not depend on the particular * format. * * Normally, application should return 0 from this callback. If fatal * error occurred while doing something in this callback, application * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * In this case, library will return immediately with return value * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if * nonzero value is returned from this callback, they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application * should not rely on this details. */ typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg, size_t len, void *user_data); /** * @functypedef * * Callback function invoked when library provides the error code, and * message. This callback is solely for debugging purpose. * |lib_error_code| is one of error code defined in * :enum:`nghttp2_error`. The |msg| is typically NULL-terminated * string of length |len|, and intended for human consumption. |len| * does not include the sentinel NULL character. * * The format of error message may change between nghttp2 library * versions. The application should not depend on the particular * format. * * Normally, application should return 0 from this callback. If fatal * error occurred while doing something in this callback, application * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * In this case, library will return immediately with return value * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if * nonzero value is returned from this callback, they are treated as * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application * should not rely on this details. */ typedef int (*nghttp2_error_callback2)(nghttp2_session *session, int lib_error_code, const char *msg, size_t len, void *user_data); struct nghttp2_session_callbacks; /** * @struct * * Callback functions for :type:`nghttp2_session`. The details of * this structure are intentionally hidden from the public API. */ typedef struct nghttp2_session_callbacks nghttp2_session_callbacks; /** * @function * * Initializes |*callbacks_ptr| with NULL values. * * The initialized object can be used when initializing multiple * :type:`nghttp2_session` objects. * * When the application finished using this object, it can use * `nghttp2_session_callbacks_del()` to free its memory. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr); /** * @function * * Frees any resources allocated for |callbacks|. If |callbacks| is * ``NULL``, this function does nothing. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_session_callbacks_set_send_callback2()` * with :type:`nghttp2_send_callback2` instead. * * Sets callback function invoked when a session wants to send data to * the remote peer. This callback is not necessary if the application * uses solely `nghttp2_session_mem_send()` to serialize data to * transmit. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback( nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Sets callback function invoked when a session wants to send data to * the remote peer. This callback is not necessary if the application * uses solely `nghttp2_session_mem_send2()` to serialize data to * transmit. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback2( nghttp2_session_callbacks *cbs, nghttp2_send_callback2 send_callback); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_session_callbacks_set_recv_callback2()` * with :type:`nghttp2_recv_callback2` instead. * * Sets callback function invoked when the a session wants to receive * data from the remote peer. This callback is not necessary if the * application uses solely `nghttp2_session_mem_recv()` to process * received data. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Sets callback function invoked when the a session wants to receive * data from the remote peer. This callback is not necessary if the * application uses solely `nghttp2_session_mem_recv2()` to process * received data. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback2( nghttp2_session_callbacks *cbs, nghttp2_recv_callback2 recv_callback); /** * @function * * Sets callback function invoked by `nghttp2_session_recv()` and * `nghttp2_session_mem_recv2()` when a frame is received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_frame_recv_callback on_frame_recv_callback); /** * @function * * Sets callback function invoked by `nghttp2_session_recv()` and * `nghttp2_session_mem_recv2()` when an invalid non-DATA frame is * received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback); /** * @function * * Sets callback function invoked when a chunk of data in DATA frame * is received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback); /** * @function * * Sets callback function invoked before a non-DATA frame is sent. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_before_frame_send_callback( nghttp2_session_callbacks *cbs, nghttp2_before_frame_send_callback before_frame_send_callback); /** * @function * * Sets callback function invoked after a frame is sent. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_send_callback( nghttp2_session_callbacks *cbs, nghttp2_on_frame_send_callback on_frame_send_callback); /** * @function * * Sets callback function invoked when a non-DATA frame is not sent * because of an error. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_not_send_callback( nghttp2_session_callbacks *cbs, nghttp2_on_frame_not_send_callback on_frame_not_send_callback); /** * @function * * Sets callback function invoked when the stream is closed. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_stream_close_callback( nghttp2_session_callbacks *cbs, nghttp2_on_stream_close_callback on_stream_close_callback); /** * @function * * Sets callback function invoked when the reception of header block * in HEADERS or PUSH_PROMISE is started. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback( nghttp2_session_callbacks *cbs, nghttp2_on_begin_headers_callback on_begin_headers_callback); /** * @function * * Sets callback function invoked when a header name/value pair is * received. If both * `nghttp2_session_callbacks_set_on_header_callback()` and * `nghttp2_session_callbacks_set_on_header_callback2()` are used to * set callbacks, the latter has the precedence. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback( nghttp2_session_callbacks *cbs, nghttp2_on_header_callback on_header_callback); /** * @function * * Sets callback function invoked when a header name/value pair is * received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2( nghttp2_session_callbacks *cbs, nghttp2_on_header_callback2 on_header_callback2); /** * @function * * Sets callback function invoked when a invalid header name/value * pair is received. If both * `nghttp2_session_callbacks_set_on_invalid_header_callback()` and * `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are * used to set callbacks, the latter takes the precedence. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback( nghttp2_session_callbacks *cbs, nghttp2_on_invalid_header_callback on_invalid_header_callback); /** * @function * * Sets callback function invoked when a invalid header name/value * pair is received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2( nghttp2_session_callbacks *cbs, nghttp2_on_invalid_header_callback2 on_invalid_header_callback2); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use * `nghttp2_session_callbacks_set_select_padding_callback2()` with * :type:`nghttp2_select_padding_callback2` instead. * * Sets callback function invoked when the library asks application * how many padding bytes are required for the transmission of the * given frame. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback( nghttp2_session_callbacks *cbs, nghttp2_select_padding_callback select_padding_callback); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Sets callback function invoked when the library asks application * how many padding bytes are required for the transmission of the * given frame. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback2( nghttp2_session_callbacks *cbs, nghttp2_select_padding_callback2 select_padding_callback); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use * `nghttp2_session_callbacks_set_data_source_read_length_callback2()` * with :type:`nghttp2_data_source_read_length_callback2` instead. * * Sets callback function determine the length allowed in * :type:`nghttp2_data_source_read_callback`. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_data_source_read_length_callback( nghttp2_session_callbacks *cbs, nghttp2_data_source_read_length_callback data_source_read_length_callback); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Sets callback function determine the length allowed in * :type:`nghttp2_data_source_read_callback2`. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_data_source_read_length_callback2( nghttp2_session_callbacks *cbs, nghttp2_data_source_read_length_callback2 data_source_read_length_callback); /** * @function * * Sets callback function invoked when a frame header is received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_frame_callback( nghttp2_session_callbacks *cbs, nghttp2_on_begin_frame_callback on_begin_frame_callback); /** * @function * * Sets callback function invoked when * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in * :type:`nghttp2_data_source_read_callback2` to avoid data copy. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback( nghttp2_session_callbacks *cbs, nghttp2_send_data_callback send_data_callback); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use * `nghttp2_session_callbacks_set_pack_extension_callback2()` with * :type:`nghttp2_pack_extension_callback2` instead. * * Sets callback function invoked when the library asks the * application to pack extension frame payload in wire format. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback( nghttp2_session_callbacks *cbs, nghttp2_pack_extension_callback pack_extension_callback); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Sets callback function invoked when the library asks the * application to pack extension frame payload in wire format. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback2( nghttp2_session_callbacks *cbs, nghttp2_pack_extension_callback2 pack_extension_callback); /** * @function * * Sets callback function invoked when the library asks the * application to unpack extension frame payload from wire format. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback( nghttp2_session_callbacks *cbs, nghttp2_unpack_extension_callback unpack_extension_callback); /** * @function * * Sets callback function invoked when chunk of extension frame * payload is received. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( nghttp2_session_callbacks *cbs, nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback); /** * @function * * .. warning:: * * Deprecated. Use * `nghttp2_session_callbacks_set_error_callback2()` with * :type:`nghttp2_error_callback2` instead. * * Sets callback function invoked when library tells error message to * the application. * * If both :type:`nghttp2_error_callback` and * :type:`nghttp2_error_callback2` are set, the latter takes * precedence. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback( nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback); /** * @function * * Sets callback function invoked when library tells error code, and * message to the application. * * If both :type:`nghttp2_error_callback` and * :type:`nghttp2_error_callback2` are set, the latter takes * precedence. */ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback2( nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2); /** * @functypedef * * Custom memory allocator to replace malloc(). The |mem_user_data| * is the mem_user_data member of :type:`nghttp2_mem` structure. */ typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data); /** * @functypedef * * Custom memory allocator to replace free(). The |mem_user_data| is * the mem_user_data member of :type:`nghttp2_mem` structure. */ typedef void (*nghttp2_free)(void *ptr, void *mem_user_data); /** * @functypedef * * Custom memory allocator to replace calloc(). The |mem_user_data| * is the mem_user_data member of :type:`nghttp2_mem` structure. */ typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data); /** * @functypedef * * Custom memory allocator to replace realloc(). The |mem_user_data| * is the mem_user_data member of :type:`nghttp2_mem` structure. */ typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data); /** * @struct * * Custom memory allocator functions and user defined pointer. The * |mem_user_data| member is passed to each allocator function. This * can be used, for example, to achieve per-session memory pool. * * In the following example code, ``my_malloc``, ``my_free``, * ``my_calloc`` and ``my_realloc`` are the replacement of the * standard allocators ``malloc``, ``free``, ``calloc`` and * ``realloc`` respectively:: * * void *my_malloc_cb(size_t size, void *mem_user_data) { * return my_malloc(size); * } * * void my_free_cb(void *ptr, void *mem_user_data) { my_free(ptr); } * * void *my_calloc_cb(size_t nmemb, size_t size, void *mem_user_data) { * return my_calloc(nmemb, size); * } * * void *my_realloc_cb(void *ptr, size_t size, void *mem_user_data) { * return my_realloc(ptr, size); * } * * void session_new() { * nghttp2_session *session; * nghttp2_session_callbacks *callbacks; * nghttp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, * my_realloc_cb}; * * ... * * nghttp2_session_client_new3(&session, callbacks, NULL, NULL, &mem); * * ... * } */ typedef struct { /** * An arbitrary user supplied data. This is passed to each * allocator function. */ void *mem_user_data; /** * Custom allocator function to replace malloc(). */ nghttp2_malloc malloc; /** * Custom allocator function to replace free(). */ nghttp2_free free; /** * Custom allocator function to replace calloc(). */ nghttp2_calloc calloc; /** * Custom allocator function to replace realloc(). */ nghttp2_realloc realloc; } nghttp2_mem; struct nghttp2_option; /** * @struct * * Configuration options for :type:`nghttp2_session`. The details of * this structure are intentionally hidden from the public API. */ typedef struct nghttp2_option nghttp2_option; /** * @function * * Initializes |*option_ptr| with default values. * * When the application finished using this object, it can use * `nghttp2_option_del()` to free its memory. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_option_new(nghttp2_option **option_ptr); /** * @function * * Frees any resources allocated for |option|. If |option| is * ``NULL``, this function does nothing. */ NGHTTP2_EXTERN void nghttp2_option_del(nghttp2_option *option); /** * @function * * This option prevents the library from sending WINDOW_UPDATE for a * connection automatically. If this option is set to nonzero, the * library won't send WINDOW_UPDATE for DATA until application calls * `nghttp2_session_consume()` to indicate the consumed amount of * data. Don't use `nghttp2_submit_window_update()` for this purpose. * By default, this option is set to zero. */ NGHTTP2_EXTERN void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val); /** * @function * * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of * remote endpoint as if it is received in SETTINGS frame. Without * specifying this option, the maximum number of outgoing concurrent * streams is initially limited to 100 to avoid issues when the local * endpoint submits lots of requests before receiving initial SETTINGS * frame from the remote endpoint, since sending them at once to the * remote endpoint could lead to rejection of some of the requests. * This value will be overwritten when the local endpoint receives * initial SETTINGS frame from the remote endpoint, either to the * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the * default value (unlimited) if none was advertised. */ NGHTTP2_EXTERN void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, uint32_t val); /** * @function * * By default, nghttp2 library, if configured as server, requires * first 24 bytes of client magic byte string (MAGIC). In most cases, * this will simplify the implementation of server. But sometimes * server may want to detect the application protocol based on first * few bytes on clear text communication. * * If this option is used with nonzero |val|, nghttp2 library does not * handle MAGIC. It still checks following SETTINGS frame. This * means that applications should deal with MAGIC by themselves. * * If this option is not used or used with zero value, if MAGIC does * not match :macro:`NGHTTP2_CLIENT_MAGIC`, `nghttp2_session_recv()` * and `nghttp2_session_mem_recv2()` will return error * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`, which is fatal * error. */ NGHTTP2_EXTERN void nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val); /** * @function * * By default, nghttp2 library enforces subset of HTTP Messaging rules * described in `HTTP/2 specification, section 8 * `_. See * :ref:`http-messaging` section for details. For those applications * who use nghttp2 library as non-HTTP use, give nonzero to |val| to * disable this enforcement. Please note that disabling this feature * does not change the fundamental client and server model of HTTP. * That is, even if the validation is disabled, only client can send * requests. */ NGHTTP2_EXTERN void nghttp2_option_set_no_http_messaging(nghttp2_option *option, int val); /** * @function * * RFC 7540 does not enforce any limit on the number of incoming * reserved streams (in RFC 7540 terms, streams in reserved (remote) * state). This only affects client side, since only server can push * streams. Malicious server can push arbitrary number of streams, * and make client's memory exhausted. This option can set the * maximum number of such incoming streams to avoid possible memory * exhaustion. If this option is set, and pushed streams are * automatically closed on reception, without calling user provided * callback, if they exceed the given limit. The default value is * 200. If session is configured as server side, this option has no * effect. Server can control the number of streams to push. */ NGHTTP2_EXTERN void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, uint32_t val); /** * @function * * Sets extension frame type the application is willing to handle with * user defined callbacks (see * :type:`nghttp2_on_extension_chunk_recv_callback` and * :type:`nghttp2_unpack_extension_callback`). The |type| is * extension frame type, and must be strictly greater than 0x9. * Otherwise, this function does nothing. The application can call * this function multiple times to set more than one frame type to * receive. The application does not have to call this function if it * just sends extension frames. */ NGHTTP2_EXTERN void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, uint8_t type); /** * @function * * Sets extension frame type the application is willing to receive * using builtin handler. The |type| is the extension frame type to * receive, and must be strictly greater than 0x9. Otherwise, this * function does nothing. The application can call this function * multiple times to set more than one frame type to receive. The * application does not have to call this function if it just sends * extension frames. * * If same frame type is passed to both * `nghttp2_option_set_builtin_recv_extension_type()` and * `nghttp2_option_set_user_recv_extension_type()`, the latter takes * precedence. */ NGHTTP2_EXTERN void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, uint8_t type); /** * @function * * This option prevents the library from sending PING frame with ACK * flag set automatically when PING frame without ACK flag set is * received. If this option is set to nonzero, the library won't send * PING frame with ACK flag set in the response for incoming PING * frame. The application can send PING frame with ACK flag set using * `nghttp2_submit_ping()` with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` * as flags parameter. */ NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val); /** * @function * * This option sets the maximum length of header block (a set of * header fields per one HEADERS frame) to send. The length of a * given set of header fields is calculated using * `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If * application attempts to send header fields larger than this limit, * the transmission of the frame fails with error code * :enum:`nghttp2_error.NGHTTP2_ERR_FRAME_SIZE_ERROR`. */ NGHTTP2_EXTERN void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, size_t val); /** * @function * * This option sets the maximum dynamic table size for deflating * header fields. The default value is 4KiB. In HTTP/2, receiver of * deflated header block can specify maximum dynamic table size. The * actual maximum size is the minimum of the size receiver specified * and this option value. */ NGHTTP2_EXTERN void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, size_t val); /** * @function * * This option prevents the library from retaining closed streams to * maintain the priority tree. If this option is set to nonzero, * applications can discard closed stream completely to save memory. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is submitted via `nghttp2_submit_settings()`, any * closed streams are not retained regardless of this option. */ NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val); /** * @function * * This function sets the maximum number of outgoing SETTINGS ACK and * PING ACK frames retained in :type:`nghttp2_session` object. If * more than those frames are retained, the peer is considered to be * misbehaving and session will be closed. The default value is 1000. */ NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val); /** * @function * * This function sets the maximum number of SETTINGS entries per * SETTINGS frame that will be accepted. If more than those entries * are received, the peer is considered to be misbehaving and session * will be closed. The default value is 32. */ NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val); /** * @function * * This option, if set to nonzero, allows server to fallback to * :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not * received from client, and server submitted * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * = 1 via `nghttp2_submit_settings()`. Most of the advanced * functionality for RFC 7540 priorities are still disabled. This * fallback only enables the minimal feature set of RFC 7540 * priorities to deal with priority signaling from client. * * Client session ignores this option. */ NGHTTP2_EXTERN void nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option, int val); /** * @function * * This option, if set to nonzero, turns off RFC 9113 leading and * trailing white spaces validation against HTTP field value. Some * important fields, such as HTTP/2 pseudo header fields, are * validated more strictly and this option does not apply to them. */ NGHTTP2_EXTERN void nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( nghttp2_option *option, int val); /** * @function * * This function sets the rate limit for the incoming stream reset * (RST_STREAM frame). It is server use only. It is a token-bucket * based rate limiter. |burst| specifies the number of tokens that is * initially available. The maximum number of tokens is capped to * this value. |rate| specifies the number of tokens that are * regenerated per second. An incoming RST_STREAM consumes one token. * If there is no token available, GOAWAY is sent to tear down the * connection. |burst| and |rate| default to 1000 and 33 * respectively. */ NGHTTP2_EXTERN void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, uint64_t burst, uint64_t rate); /** * @function * * This function sets the maximum number of CONTINUATION frames * following an incoming HEADER frame. If more than those frames are * received, the remote endpoint is considered to be misbehaving and * session will be closed. The default value is 8. */ NGHTTP2_EXTERN void nghttp2_option_set_max_continuations(nghttp2_option *option, size_t val); /** * @function * * Initializes |*session_ptr| for client use. The all members of * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| * does not store |callbacks|. The |user_data| is an arbitrary user * supplied data, which will be passed to the callback functions. * * The :type:`nghttp2_send_callback2` must be specified. If the * application code uses `nghttp2_session_recv()`, the * :type:`nghttp2_recv_callback` must be specified. The other members * of |callbacks| can be ``NULL``. * * If this function fails, |*session_ptr| is left untouched. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_client_new(nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data); /** * @function * * Initializes |*session_ptr| for server use. The all members of * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| * does not store |callbacks|. The |user_data| is an arbitrary user * supplied data, which will be passed to the callback functions. * * The :type:`nghttp2_send_callback2` must be specified. If the * application code uses `nghttp2_session_recv()`, the * :type:`nghttp2_recv_callback` must be specified. The other members * of |callbacks| can be ``NULL``. * * If this function fails, |*session_ptr| is left untouched. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_server_new(nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data); /** * @function * * Like `nghttp2_session_client_new()`, but with additional options * specified in the |option|. * * The |option| can be ``NULL`` and the call is equivalent to * `nghttp2_session_client_new()`. * * This function does not take ownership |option|. The application is * responsible for freeing |option| if it finishes using the object. * * The library code does not refer to |option| after this function * returns. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_client_new2(nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data, const nghttp2_option *option); /** * @function * * Like `nghttp2_session_server_new()`, but with additional options * specified in the |option|. * * The |option| can be ``NULL`` and the call is equivalent to * `nghttp2_session_server_new()`. * * This function does not take ownership |option|. The application is * responsible for freeing |option| if it finishes using the object. * * The library code does not refer to |option| after this function * returns. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_server_new2(nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data, const nghttp2_option *option); /** * @function * * Like `nghttp2_session_client_new2()`, but with additional custom * memory allocator specified in the |mem|. * * The |mem| can be ``NULL`` and the call is equivalent to * `nghttp2_session_client_new2()`. * * This function does not take ownership |mem|. The application is * responsible for freeing |mem|. * * The library code does not refer to |mem| pointer after this * function returns, so the application can safely free it. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_client_new3( nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data, const nghttp2_option *option, nghttp2_mem *mem); /** * @function * * Like `nghttp2_session_server_new2()`, but with additional custom * memory allocator specified in the |mem|. * * The |mem| can be ``NULL`` and the call is equivalent to * `nghttp2_session_server_new2()`. * * This function does not take ownership |mem|. The application is * responsible for freeing |mem|. * * The library code does not refer to |mem| pointer after this * function returns, so the application can safely free it. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_server_new3( nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, void *user_data, const nghttp2_option *option, nghttp2_mem *mem); /** * @function * * Frees any resources allocated for |session|. If |session| is * ``NULL``, this function does nothing. */ NGHTTP2_EXTERN void nghttp2_session_del(nghttp2_session *session); /** * @function * * Sends pending frames to the remote peer. * * This function retrieves the highest prioritized frame from the * outbound queue and sends it to the remote peer. It does this as * many times as possible until the user callback * :type:`nghttp2_send_callback2` returns * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`, the outbound queue * becomes empty or flow control is triggered (remote window size * becomes depleted or maximum number of concurrent streams is * reached). This function calls several callback functions which are * passed when initializing the |session|. Here is the simple time * chart which tells when each callback is invoked: * * 1. Get the next frame to send from outbound queue. * * 2. Prepare transmission of the frame. * * 3. If the control frame cannot be sent because some preconditions * are not met (e.g., request HEADERS cannot be sent after GOAWAY), * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort * the following steps. * * 4. If the frame is HEADERS, PUSH_PROMISE or DATA, * :type:`nghttp2_select_padding_callback` is invoked. * * 5. If the frame is request HEADERS, the stream is opened here. * * 6. :type:`nghttp2_before_frame_send_callback` is invoked. * * 7. If :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` is returned from * :type:`nghttp2_before_frame_send_callback`, the current frame * transmission is canceled, and * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort * the following steps. * * 8. :type:`nghttp2_send_callback2` is invoked one or more times to * send the frame. * * 9. :type:`nghttp2_on_frame_send_callback` is invoked. * * 10. If the transmission of the frame triggers closure of the * stream, the stream is closed and * :type:`nghttp2_on_stream_close_callback` is invoked. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` * The callback function failed. */ NGHTTP2_EXTERN int nghttp2_session_send(nghttp2_session *session); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_session_mem_send2()` instead. * * Returns the serialized data to send. * * This function behaves like `nghttp2_session_send()` except that it * does not use :type:`nghttp2_send_callback` to transmit data. * Instead, it assigns the pointer to the serialized data to the * |*data_ptr| and returns its length. The other callbacks are called * in the same way as they are in `nghttp2_session_send()`. * * If no data is available to send, this function returns 0. * * This function may not return all serialized data in one invocation. * To get all data, call this function repeatedly until it returns 0 * or one of negative error codes. * * The assigned |*data_ptr| is valid until the next call of * `nghttp2_session_mem_send()` or `nghttp2_session_send()`. * * The caller must send all data before sending the next chunk of * data. * * This function returns the length of the data pointed by the * |*data_ptr| if it succeeds, or one of the following negative error * codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * * .. note:: * * This function may produce very small byte string. If that is the * case, and application disables Nagle algorithm (``TCP_NODELAY``), * then writing this small chunk leads to very small packet, and it * is very inefficient. An application should be responsible to * buffer up small chunks of data as necessary to avoid this * situation. */ NGHTTP2_EXTERN ssize_t nghttp2_session_mem_send(nghttp2_session *session, const uint8_t **data_ptr); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Returns the serialized data to send. * * This function behaves like `nghttp2_session_send()` except that it * does not use :type:`nghttp2_send_callback2` to transmit data. * Instead, it assigns the pointer to the serialized data to the * |*data_ptr| and returns its length. The other callbacks are called * in the same way as they are in `nghttp2_session_send()`. * * If no data is available to send, this function returns 0. * * This function may not return all serialized data in one invocation. * To get all data, call this function repeatedly until it returns 0 * or one of negative error codes. * * The assigned |*data_ptr| is valid until the next call of * `nghttp2_session_mem_send2()` or `nghttp2_session_send()`. * * The caller must send all data before sending the next chunk of * data. * * This function returns the length of the data pointed by the * |*data_ptr| if it succeeds, or one of the following negative error * codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * * .. note:: * * This function may produce very small byte string. If that is the * case, and application disables Nagle algorithm (``TCP_NODELAY``), * then writing this small chunk leads to very small packet, and it * is very inefficient. An application should be responsible to * buffer up small chunks of data as necessary to avoid this * situation. */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_session_mem_send2(nghttp2_session *session, const uint8_t **data_ptr); /** * @function * * Receives frames from the remote peer. * * This function receives as many frames as possible until the user * callback :type:`nghttp2_recv_callback` returns * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. This function calls * several callback functions which are passed when initializing the * |session|. Here is the simple time chart which tells when each * callback is invoked: * * 1. :type:`nghttp2_recv_callback` is invoked one or more times to * receive frame header. * * 2. When frame header is received, * :type:`nghttp2_on_begin_frame_callback` is invoked. * * 3. If the frame is DATA frame: * * 1. :type:`nghttp2_recv_callback` is invoked to receive DATA * payload. For each chunk of data, * :type:`nghttp2_on_data_chunk_recv_callback` is invoked. * * 2. If one DATA frame is completely received, * :type:`nghttp2_on_frame_recv_callback` is invoked. If the * reception of the frame triggers the closure of the stream, * :type:`nghttp2_on_stream_close_callback` is invoked. * * 4. If the frame is the control frame: * * 1. :type:`nghttp2_recv_callback` is invoked one or more times to * receive whole frame. * * 2. If the received frame is valid, then following actions are * taken. If the frame is either HEADERS or PUSH_PROMISE, * :type:`nghttp2_on_begin_headers_callback` is invoked. Then * :type:`nghttp2_on_header_callback` is invoked for each header * name/value pair. For invalid header field, * :type:`nghttp2_on_invalid_header_callback` is called. After * all name/value pairs are emitted successfully, * :type:`nghttp2_on_frame_recv_callback` is invoked. For other * frames, :type:`nghttp2_on_frame_recv_callback` is invoked. * If the reception of the frame triggers the closure of the * stream, :type:`nghttp2_on_stream_close_callback` is invoked. * * 3. If the received frame is unpacked but is interpreted as * invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is * invoked. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_EOF` * The remote peer did shutdown on the connection. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` * The callback function failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` * Invalid client magic was detected. This error only returns * when |session| was configured as server and * `nghttp2_option_set_no_recv_client_magic()` is not used with * nonzero value. * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` * Flooding was detected in this HTTP/2 session, and it must be * closed. This is most likely caused by misbehaviour of peer. */ NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_session_mem_recv2()` instead. * * Processes data |in| as an input from the remote endpoint. The * |inlen| indicates the number of bytes to receive in the |in|. * * This function behaves like `nghttp2_session_recv()` except that it * does not use :type:`nghttp2_recv_callback` to receive data; the * |in| is the only data for the invocation of this function. If all * bytes are processed, this function returns. The other callbacks * are called in the same way as they are in `nghttp2_session_recv()`. * * In the current implementation, this function always tries to * processes |inlen| bytes of input data unless either an error occurs or * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from * :type:`nghttp2_on_header_callback` or * :type:`nghttp2_on_data_chunk_recv_callback`. If * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is used, the return value * includes the number of bytes which was used to produce the data or * frame for the callback. * * This function returns the number of processed bytes, or one of the * following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` * The callback function failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` * Invalid client magic was detected. This error only returns * when |session| was configured as server and * `nghttp2_option_set_no_recv_client_magic()` is not used with * nonzero value. * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` * Flooding was detected in this HTTP/2 session, and it must be * closed. This is most likely caused by misbehaviour of peer. */ NGHTTP2_EXTERN ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, size_t inlen); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Processes data |in| as an input from the remote endpoint. The * |inlen| indicates the number of bytes to receive in the |in|. * * This function behaves like `nghttp2_session_recv()` except that it * does not use :type:`nghttp2_recv_callback` to receive data; the * |in| is the only data for the invocation of this function. If all * bytes are processed, this function returns. The other callbacks * are called in the same way as they are in `nghttp2_session_recv()`. * * In the current implementation, this function always tries to * processes |inlen| bytes of input data unless either an error occurs or * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from * :type:`nghttp2_on_header_callback` or * :type:`nghttp2_on_data_chunk_recv_callback`. If * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is used, the return value * includes the number of bytes which was used to produce the data or * frame for the callback. * * This function returns the number of processed bytes, or one of the * following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` * The callback function failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` * Invalid client magic was detected. This error only returns * when |session| was configured as server and * `nghttp2_option_set_no_recv_client_magic()` is not used with * nonzero value. * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` * Flooding was detected in this HTTP/2 session, and it must be * closed. This is most likely caused by misbehaviour of peer. */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, const uint8_t *in, size_t inlen); /** * @function * * Puts back previously deferred DATA frame in the stream |stream_id| * to the outbound queue. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The stream does not exist; or no deferred data exist. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id); /** * @function * * Returns nonzero value if |session| wants to receive data from the * remote peer. * * If both `nghttp2_session_want_read()` and * `nghttp2_session_want_write()` return 0, the application should * drop the connection. */ NGHTTP2_EXTERN int nghttp2_session_want_read(nghttp2_session *session); /** * @function * * Returns nonzero value if |session| wants to send data to the remote * peer. * * If both `nghttp2_session_want_read()` and * `nghttp2_session_want_write()` return 0, the application should * drop the connection. */ NGHTTP2_EXTERN int nghttp2_session_want_write(nghttp2_session *session); /** * @function * * Returns stream_user_data for the stream |stream_id|. The * stream_user_data is provided by `nghttp2_submit_request2()`, * `nghttp2_submit_headers()` or * `nghttp2_session_set_stream_user_data()`. Unless it is set using * `nghttp2_session_set_stream_user_data()`, if the stream is * initiated by the remote endpoint, stream_user_data is always * ``NULL``. If the stream does not exist, this function returns * ``NULL``. */ NGHTTP2_EXTERN void * nghttp2_session_get_stream_user_data(nghttp2_session *session, int32_t stream_id); /** * @function * * Sets the |stream_user_data| to the stream denoted by the * |stream_id|. If a stream user data is already set to the stream, * it is replaced with the |stream_user_data|. It is valid to specify * ``NULL`` in the |stream_user_data|, which nullifies the associated * data pointer. * * It is valid to set the |stream_user_data| to the stream reserved by * PUSH_PROMISE frame. * * This function returns 0 if it succeeds, or one of following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The stream does not exist */ NGHTTP2_EXTERN int nghttp2_session_set_stream_user_data(nghttp2_session *session, int32_t stream_id, void *stream_user_data); /** * @function * * Sets |user_data| to |session|, overwriting the existing user data * specified in `nghttp2_session_client_new()`, or * `nghttp2_session_server_new()`. */ NGHTTP2_EXTERN void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data); /** * @function * * Returns the number of frames in the outbound queue. This does not * include the deferred DATA frames. */ NGHTTP2_EXTERN size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session); /** * @function * * Returns the number of DATA payload in bytes received without * WINDOW_UPDATE transmission for the stream |stream_id|. The local * (receive) window size can be adjusted by * `nghttp2_submit_window_update()`. This function takes into account * that and returns effective data length. In particular, if the * local window size is reduced by submitting negative * window_size_increment with `nghttp2_submit_window_update()`, this * function returns the number of bytes less than actually received. * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_recv_data_length( nghttp2_session *session, int32_t stream_id); /** * @function * * Returns the local (receive) window size for the stream |stream_id|. * The local window size can be adjusted by * `nghttp2_submit_window_update()`. This function takes into account * that and returns effective window size. * * This function does not take into account the amount of received * data from the remote endpoint. Use * `nghttp2_session_get_stream_local_window_size()` to know the amount * of data the remote endpoint can send without receiving stream level * WINDOW_UPDATE frame. Note that each stream is still subject to the * connection level flow control. * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size( nghttp2_session *session, int32_t stream_id); /** * @function * * Returns the amount of flow-controlled payload (e.g., DATA) that the * remote endpoint can send without receiving stream level * WINDOW_UPDATE frame. It is also subject to the connection level * flow control. So the actual amount of data to send is * min(`nghttp2_session_get_stream_local_window_size()`, * `nghttp2_session_get_local_window_size()`). * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_local_window_size( nghttp2_session *session, int32_t stream_id); /** * @function * * Returns the number of DATA payload in bytes received without * WINDOW_UPDATE transmission for a connection. The local (receive) * window size can be adjusted by `nghttp2_submit_window_update()`. * This function takes into account that and returns effective data * length. In particular, if the local window size is reduced by * submitting negative window_size_increment with * `nghttp2_submit_window_update()`, this function returns the number * of bytes less than actually received. * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_effective_recv_data_length(nghttp2_session *session); /** * @function * * Returns the local (receive) window size for a connection. The * local window size can be adjusted by * `nghttp2_submit_window_update()`. This function takes into account * that and returns effective window size. * * This function does not take into account the amount of received * data from the remote endpoint. Use * `nghttp2_session_get_local_window_size()` to know the amount of * data the remote endpoint can send without receiving * connection-level WINDOW_UPDATE frame. Note that each stream is * still subject to the stream level flow control. * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_effective_local_window_size(nghttp2_session *session); /** * @function * * Returns the amount of flow-controlled payload (e.g., DATA) that the * remote endpoint can send without receiving connection level * WINDOW_UPDATE frame. Note that each stream is still subject to the * stream level flow control (see * `nghttp2_session_get_stream_local_window_size()`). * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_local_window_size(nghttp2_session *session); /** * @function * * Returns the remote window size for a given stream |stream_id|. * * This is the amount of flow-controlled payload (e.g., DATA) that the * local endpoint can send without stream level WINDOW_UPDATE. There * is also connection level flow control, so the effective size of * payload that the local endpoint can actually send is * min(`nghttp2_session_get_stream_remote_window_size()`, * `nghttp2_session_get_remote_window_size()`). * * This function returns -1 if it fails. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_remote_window_size( nghttp2_session *session, int32_t stream_id); /** * @function * * Returns the remote window size for a connection. * * This function always succeeds. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session); /** * @function * * Returns 1 if local peer half closed the given stream |stream_id|. * Returns 0 if it did not. Returns -1 if no such stream exists. */ NGHTTP2_EXTERN int nghttp2_session_get_stream_local_close(nghttp2_session *session, int32_t stream_id); /** * @function * * Returns 1 if remote peer half closed the given stream |stream_id|. * Returns 0 if it did not. Returns -1 if no such stream exists. */ NGHTTP2_EXTERN int nghttp2_session_get_stream_remote_close(nghttp2_session *session, int32_t stream_id); /** * @function * * Returns the current dynamic table size of HPACK inflater, including * the overhead 32 bytes per entry described in RFC 7541. */ NGHTTP2_EXTERN size_t nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session); /** * @function * * Returns the current dynamic table size of HPACK deflater including * the overhead 32 bytes per entry described in RFC 7541. */ NGHTTP2_EXTERN size_t nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session); /** * @function * * Signals the session so that the connection should be terminated. * * The last stream ID is the minimum value between the stream ID of a * stream for which :type:`nghttp2_on_frame_recv_callback` was called * most recently and the last stream ID we have sent to the peer * previously. * * The |error_code| is the error code of this GOAWAY frame. The * pre-defined error code is one of :enum:`nghttp2_error_code`. * * After the transmission, both `nghttp2_session_want_read()` and * `nghttp2_session_want_write()` return 0. * * This function should be called when the connection should be * terminated after sending GOAWAY. If the remaining streams should * be processed after GOAWAY, use `nghttp2_submit_goaway()` instead. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_terminate_session(nghttp2_session *session, uint32_t error_code); /** * @function * * Signals the session so that the connection should be terminated. * * This function behaves like `nghttp2_session_terminate_session()`, * but the last stream ID can be specified by the application for fine * grained control of stream. The HTTP/2 specification does not allow * last_stream_id to be increased. So the actual value sent as * last_stream_id is the minimum value between the given * |last_stream_id| and the last_stream_id we have previously sent to * the peer. * * The |last_stream_id| is peer's stream ID or 0. So if |session| is * initialized as client, |last_stream_id| must be even or 0. If * |session| is initialized as server, |last_stream_id| must be odd or * 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |last_stream_id| is invalid. */ NGHTTP2_EXTERN int nghttp2_session_terminate_session2(nghttp2_session *session, int32_t last_stream_id, uint32_t error_code); /** * @function * * Signals to the client that the server started graceful shutdown * procedure. * * This function is only usable for server. If this function is * called with client side session, this function returns * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. * * To gracefully shutdown HTTP/2 session, server should call this * function to send GOAWAY with last_stream_id (1u << 31) - 1. And * after some delay (e.g., 1 RTT), send another GOAWAY with the stream * ID that the server has some processing using * `nghttp2_submit_goaway()`. See also * `nghttp2_session_get_last_proc_stream_id()`. * * Unlike `nghttp2_submit_goaway()`, this function just sends GOAWAY * and does nothing more. This is a mere indication to the client * that session shutdown is imminent. The application should call * `nghttp2_submit_goaway()` with appropriate last_stream_id after * this call. * * If one or more GOAWAY frame have been already sent by either * `nghttp2_submit_goaway()` or `nghttp2_session_terminate_session()`, * this function has no effect. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The |session| is initialized as client. */ NGHTTP2_EXTERN int nghttp2_submit_shutdown_notice(nghttp2_session *session); /** * @function * * Returns the value of SETTINGS |id| notified by a remote endpoint. * The |id| must be one of values defined in * :enum:`nghttp2_settings_id`. */ NGHTTP2_EXTERN uint32_t nghttp2_session_get_remote_settings( nghttp2_session *session, nghttp2_settings_id id); /** * @function * * Returns the value of SETTINGS |id| of local endpoint acknowledged * by the remote endpoint. The |id| must be one of the values defined * in :enum:`nghttp2_settings_id`. */ NGHTTP2_EXTERN uint32_t nghttp2_session_get_local_settings( nghttp2_session *session, nghttp2_settings_id id); /** * @function * * Tells the |session| that next stream ID is |next_stream_id|. The * |next_stream_id| must be equal or greater than the value returned * by `nghttp2_session_get_next_stream_id()`. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |next_stream_id| is strictly less than the value * `nghttp2_session_get_next_stream_id()` returns; or * |next_stream_id| is invalid (e.g., even integer for client, or * odd integer for server). */ NGHTTP2_EXTERN int nghttp2_session_set_next_stream_id(nghttp2_session *session, int32_t next_stream_id); /** * @function * * Returns the next outgoing stream ID. Notice that return type is * uint32_t. If we run out of stream ID for this session, this * function returns 1 << 31. */ NGHTTP2_EXTERN uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session); /** * @function * * Tells the |session| that |size| bytes for a stream denoted by * |stream_id| were consumed by application and are ready to * WINDOW_UPDATE. The consumed bytes are counted towards both * connection and stream level WINDOW_UPDATE (see * `nghttp2_session_consume_connection()` and * `nghttp2_session_consume_stream()` to update consumption * independently). This function is intended to be used without * automatic window update (see * `nghttp2_option_set_no_auto_window_update()`). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * Automatic WINDOW_UPDATE is not disabled. */ NGHTTP2_EXTERN int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, size_t size); /** * @function * * Like `nghttp2_session_consume()`, but this only tells library that * |size| bytes were consumed only for connection level. Note that * HTTP/2 maintains connection and stream level flow control windows * independently. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * Automatic WINDOW_UPDATE is not disabled. */ NGHTTP2_EXTERN int nghttp2_session_consume_connection(nghttp2_session *session, size_t size); /** * @function * * Like `nghttp2_session_consume()`, but this only tells library that * |size| bytes were consumed only for stream denoted by |stream_id|. * Note that HTTP/2 maintains connection and stream level flow control * windows independently. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * Automatic WINDOW_UPDATE is not disabled. */ NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id, size_t size); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return 0 without doing anything. * * Changes priority of existing stream denoted by |stream_id|. The * new priority specification is |pri_spec|. * * The priority is changed silently and instantly, and no PRIORITY * frame will be sent to notify the peer of this change. This * function may be useful for server to change the priority of pushed * stream. * * If |session| is initialized as server, and ``pri_spec->stream_id`` * points to the idle stream, the idle stream is created if it does * not exist. The created idle stream will depend on root stream * (stream 0) with weight 16. * * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not * found, we use default priority instead of given |pri_spec|. That * is make stream depend on root stream with weight 16. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is submitted via `nghttp2_submit_settings()`, this * function does nothing and returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * Attempted to depend on itself; or no stream exist for the given * |stream_id|; or |stream_id| is 0 */ NGHTTP2_EXTERN int nghttp2_session_change_stream_priority(nghttp2_session *session, int32_t stream_id, const nghttp2_priority_spec *pri_spec); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return 0 without doing anything. * * Creates idle stream with the given |stream_id|, and priority * |pri_spec|. * * The stream creation is done without sending PRIORITY frame, which * means that peer does not know about the existence of this idle * stream in the local endpoint. * * RFC 7540 does not disallow the use of creation of idle stream with * odd or even stream ID regardless of client or server. So this * function can create odd or even stream ID regardless of client or * server. But probably it is a bit safer to use the stream ID the * local endpoint can initiate (in other words, use odd stream ID for * client, and even stream ID for server), to avoid potential * collision from peer's instruction. Also we can use * `nghttp2_session_set_next_stream_id()` to avoid to open created * idle streams accidentally if we follow this recommendation. * * If |session| is initialized as server, and ``pri_spec->stream_id`` * points to the idle stream, the idle stream is created if it does * not exist. The created idle stream will depend on root stream * (stream 0) with weight 16. * * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not * found, we use default priority instead of given |pri_spec|. That * is make stream depend on root stream with weight 16. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is submitted via `nghttp2_submit_settings()`, this * function does nothing and returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * Attempted to depend on itself; or stream denoted by |stream_id| * already exists; or |stream_id| cannot be used to create idle * stream (in other words, local endpoint has already opened * stream ID greater than or equal to the given stream ID; or * |stream_id| is 0 */ NGHTTP2_EXTERN int nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id, const nghttp2_priority_spec *pri_spec); /** * @function * * .. warning:: * * This function is deprecated in favor of * `nghttp2_session_upgrade2()`, because this function lacks the * parameter to tell the library the request method used in the * original HTTP request. This information is required for client * to validate actual response body length against content-length * header field (see `nghttp2_option_set_no_http_messaging()`). If * HEAD is used in request, the length of response body must be 0 * regardless of value included in content-length header field. * * Performs post-process of HTTP Upgrade request. This function can * be called from both client and server, but the behavior is very * different in each other. * * If called from client side, the |settings_payload| must be the * value sent in ``HTTP2-Settings`` header field and must be decoded * by base64url decoder. The |settings_payloadlen| is the length of * |settings_payload|. The |settings_payload| is unpacked and its * setting values will be submitted using `nghttp2_submit_settings()`. * This means that the client application code does not need to submit * SETTINGS by itself. The stream with stream ID=1 is opened and the * |stream_user_data| is used for its stream_user_data. The opened * stream becomes half-closed (local) state. * * If called from server side, the |settings_payload| must be the * value received in ``HTTP2-Settings`` header field and must be * decoded by base64url decoder. The |settings_payloadlen| is the * length of |settings_payload|. It is treated as if the SETTINGS * frame with that payload is received. Thus, callback functions for * the reception of SETTINGS frame will be invoked. The stream with * stream ID=1 is opened. The |stream_user_data| is ignored. The * opened stream becomes half-closed (remote). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |settings_payload| is badly formed. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The stream ID 1 is already used or closed; or is not available. */ NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session, const uint8_t *settings_payload, size_t settings_payloadlen, void *stream_user_data); /** * @function * * Performs post-process of HTTP Upgrade request. This function can * be called from both client and server, but the behavior is very * different in each other. * * If called from client side, the |settings_payload| must be the * value sent in ``HTTP2-Settings`` header field and must be decoded * by base64url decoder. The |settings_payloadlen| is the length of * |settings_payload|. The |settings_payload| is unpacked and its * setting values will be submitted using `nghttp2_submit_settings()`. * This means that the client application code does not need to submit * SETTINGS by itself. The stream with stream ID=1 is opened and the * |stream_user_data| is used for its stream_user_data. The opened * stream becomes half-closed (local) state. * * If called from server side, the |settings_payload| must be the * value received in ``HTTP2-Settings`` header field and must be * decoded by base64url decoder. The |settings_payloadlen| is the * length of |settings_payload|. It is treated as if the SETTINGS * frame with that payload is received. Thus, callback functions for * the reception of SETTINGS frame will be invoked. The stream with * stream ID=1 is opened. The |stream_user_data| is ignored. The * opened stream becomes half-closed (remote). * * If the request method is HEAD, pass nonzero value to * |head_request|. Otherwise, pass 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |settings_payload| is badly formed. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The stream ID 1 is already used or closed; or is not available. */ NGHTTP2_EXTERN int nghttp2_session_upgrade2(nghttp2_session *session, const uint8_t *settings_payload, size_t settings_payloadlen, int head_request, void *stream_user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_pack_settings_payload2()` instead. * * Serializes the SETTINGS values |iv| in the |buf|. The size of the * |buf| is specified by |buflen|. The number of entries in the |iv| * array is given by |niv|. The required space in |buf| for the |niv| * entries is ``6*niv`` bytes and if the given buffer is too small, an * error is returned. This function is used mainly for creating a * SETTINGS payload to be sent with the ``HTTP2-Settings`` header * field in an HTTP Upgrade request. The data written in |buf| is NOT * base64url encoded and the application is responsible for encoding. * * This function returns the number of bytes written in |buf|, or one * of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |iv| contains duplicate settings ID or invalid value. * * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN ssize_t nghttp2_pack_settings_payload( uint8_t *buf, size_t buflen, const nghttp2_settings_entry *iv, size_t niv); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Serializes the SETTINGS values |iv| in the |buf|. The size of the * |buf| is specified by |buflen|. The number of entries in the |iv| * array is given by |niv|. The required space in |buf| for the |niv| * entries is ``6*niv`` bytes and if the given buffer is too small, an * error is returned. This function is used mainly for creating a * SETTINGS payload to be sent with the ``HTTP2-Settings`` header * field in an HTTP Upgrade request. The data written in |buf| is NOT * base64url encoded and the application is responsible for encoding. * * This function returns the number of bytes written in |buf|, or one * of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |iv| contains duplicate settings ID or invalid value. * * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_pack_settings_payload2( uint8_t *buf, size_t buflen, const nghttp2_settings_entry *iv, size_t niv); /** * @function * * Returns string describing the |lib_error_code|. The * |lib_error_code| must be one of the :enum:`nghttp2_error`. */ NGHTTP2_EXTERN const char *nghttp2_strerror(int lib_error_code); /** * @function * * Returns string representation of HTTP/2 error code |error_code| * (e.g., ``PROTOCOL_ERROR`` is returned if ``error_code == * NGHTTP2_PROTOCOL_ERROR``). If string representation is unknown for * given |error_code|, this function returns string ``unknown``. */ NGHTTP2_EXTERN const char *nghttp2_http2_strerror(uint32_t error_code); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * Initializes |pri_spec| with the |stream_id| of the stream to depend * on with |weight| and its exclusive flag. If |exclusive| is * nonzero, exclusive flag is set. * * The |weight| must be in [:macro:`NGHTTP2_MIN_WEIGHT`, * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. */ NGHTTP2_EXTERN void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec, int32_t stream_id, int32_t weight, int exclusive); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * Initializes |pri_spec| with the default values. The default values * are: stream_id = 0, weight = :macro:`NGHTTP2_DEFAULT_WEIGHT` and * exclusive = 0. */ NGHTTP2_EXTERN void nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * Returns nonzero if the |pri_spec| is filled with default values. */ NGHTTP2_EXTERN int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_submit_request2()` instead. * * Submits HEADERS frame and optionally one or more DATA frames. * * The |pri_spec| is a deprecated priority specification of this * request. ``NULL`` means the default priority (see * `nghttp2_priority_spec_default_init()`). To specify the priority, * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, * this function will copy its data members. * * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes * :macro:`NGHTTP2_MAX_WEIGHT`. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is received by a remote endpoint, |pri_spec| is * ignored, and treated as if ``NULL`` is specified. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * HTTP/2 specification has requirement about header fields in the * request HEADERS. See the specification for more details. * * If |data_prd| is not ``NULL``, it provides data which will be sent * in subsequent DATA frames. In this case, a method that allows * request message bodies * (https://tools.ietf.org/html/rfc7231#section-4) must be specified * with ``:method`` key in |nva| (e.g. ``POST``). This function does * not take ownership of the |data_prd|. The function copies the * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have * END_STREAM set. The |stream_user_data| is data associated to the * stream opened by this request and can be an arbitrary pointer, * which can be retrieved later by * `nghttp2_session_get_stream_user_data()`. * * This function returns assigned stream ID if it succeeds, or one of * the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * No stream ID is available because maximum stream ID was * reached. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * Trying to depend on itself (new stream ID equals * ``pri_spec->stream_id``). * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The |session| is server session. * * .. warning:: * * This function returns assigned stream ID if it succeeds. But * that stream is not created yet. The application must not submit * frame to that stream ID before * :type:`nghttp2_before_frame_send_callback` is called for this * frame. This means `nghttp2_session_get_stream_user_data()` does * not work before the callback. But * `nghttp2_session_set_stream_user_data()` handles this situation * specially, and it can set data to a stream during this period. * */ NGHTTP2_EXTERN int32_t nghttp2_submit_request( nghttp2_session *session, const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, void *stream_user_data); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Submits HEADERS frame and optionally one or more DATA frames. * * The |pri_spec| is a deprecated priority specification of this * request. ``NULL`` means the default priority (see * `nghttp2_priority_spec_default_init()`). To specify the priority, * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, * this function will copy its data members. In the future release * after the end of 2024, this function will ignore |pri_spec| and * behave as if ``NULL`` is given. * * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes * :macro:`NGHTTP2_MAX_WEIGHT`. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is received by a remote endpoint, |pri_spec| is * ignored, and treated as if ``NULL`` is specified. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * HTTP/2 specification has requirement about header fields in the * request HEADERS. See the specification for more details. * * If |data_prd| is not ``NULL``, it provides data which will be sent * in subsequent DATA frames. In this case, a method that allows * request message bodies * (https://tools.ietf.org/html/rfc7231#section-4) must be specified * with ``:method`` key in |nva| (e.g. ``POST``). This function does * not take ownership of the |data_prd|. The function copies the * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have * END_STREAM set. The |stream_user_data| is data associated to the * stream opened by this request and can be an arbitrary pointer, * which can be retrieved later by * `nghttp2_session_get_stream_user_data()`. * * This function returns assigned stream ID if it succeeds, or one of * the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * No stream ID is available because maximum stream ID was * reached. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * Trying to depend on itself (new stream ID equals * ``pri_spec->stream_id``). * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The |session| is server session. * * .. warning:: * * This function returns assigned stream ID if it succeeds. But * that stream is not created yet. The application must not submit * frame to that stream ID before * :type:`nghttp2_before_frame_send_callback` is called for this * frame. This means `nghttp2_session_get_stream_user_data()` does * not work before the callback. But * `nghttp2_session_set_stream_user_data()` handles this situation * specially, and it can set data to a stream during this period. * */ NGHTTP2_EXTERN int32_t nghttp2_submit_request2( nghttp2_session *session, const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider2 *data_prd, void *stream_user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_submit_response2()` instead. * * Submits response HEADERS frame and optionally one or more DATA * frames against the stream |stream_id|. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * HTTP/2 specification has requirement about header fields in the * response HEADERS. See the specification for more details. * * If |data_prd| is not ``NULL``, it provides data which will be sent * in subsequent DATA frames. This function does not take ownership * of the |data_prd|. The function copies the members of the * |data_prd|. If |data_prd| is ``NULL``, HEADERS will have * END_STREAM flag set. * * This method can be used as normal HTTP response and push response. * When pushing a resource using this function, the |session| must be * configured using `nghttp2_session_server_new()` or its variants and * the target stream denoted by the |stream_id| must be reserved using * `nghttp2_submit_push_promise()`. * * To send non-final response headers (e.g., HTTP status 101), don't * use this function because this function half-closes the outbound * stream. Instead, use `nghttp2_submit_headers()` for this purpose. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` * DATA or HEADERS has been already submitted and not fully * processed yet. Normally, this does not happen, but when * application wrongly calls `nghttp2_submit_response()` twice, * this may happen. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The |session| is client session. * * .. warning:: * * Calling this function twice for the same stream ID may lead to * program crash. It is generally considered to a programming error * to commit response twice. */ NGHTTP2_EXTERN int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Submits response HEADERS frame and optionally one or more DATA * frames against the stream |stream_id|. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * HTTP/2 specification has requirement about header fields in the * response HEADERS. See the specification for more details. * * If |data_prd| is not ``NULL``, it provides data which will be sent * in subsequent DATA frames. This function does not take ownership * of the |data_prd|. The function copies the members of the * |data_prd|. If |data_prd| is ``NULL``, HEADERS will have * END_STREAM flag set. * * This method can be used as normal HTTP response and push response. * When pushing a resource using this function, the |session| must be * configured using `nghttp2_session_server_new()` or its variants and * the target stream denoted by the |stream_id| must be reserved using * `nghttp2_submit_push_promise()`. * * To send non-final response headers (e.g., HTTP status 101), don't * use this function because this function half-closes the outbound * stream. Instead, use `nghttp2_submit_headers()` for this purpose. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` * DATA or HEADERS has been already submitted and not fully * processed yet. Normally, this does not happen, but when * application wrongly calls `nghttp2_submit_response2()` twice, * this may happen. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The |session| is client session. * * .. warning:: * * Calling this function twice for the same stream ID may lead to * program crash. It is generally considered to a programming error * to commit response twice. */ NGHTTP2_EXTERN int nghttp2_submit_response2(nghttp2_session *session, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider2 *data_prd); /** * @function * * Submits trailer fields HEADERS against the stream |stream_id|. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application must not include pseudo-header * fields (headers whose names starts with ":") in |nva|. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * For server, trailer fields must follow response HEADERS or response * DATA without END_STREAM flat set. The library does not enforce * this requirement, and applications should do this for themselves. * If `nghttp2_submit_trailer()` is called before any response HEADERS * submission (usually by `nghttp2_submit_response2()`), the content * of |nva| will be sent as response headers, which will result in * error. * * This function has the same effect with `nghttp2_submit_headers()`, * with flags = :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` and both * pri_spec and stream_user_data to NULL. * * To submit trailer fields after `nghttp2_submit_response2()` is * called, the application has to specify * :type:`nghttp2_data_provider2` to `nghttp2_submit_response2()`. * Inside of :type:`nghttp2_data_source_read_callback2`, when setting * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF`, also set * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM`. After * that, the application can send trailer fields using * `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used * inside :type:`nghttp2_data_source_read_callback2`. * * This function returns 0 if it succeeds and |stream_id| is -1. * Otherwise, this function returns 0 if it succeeds, or one of the * following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. */ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen); /** * @function * * Submits HEADERS frame. The |flags| is bitwise OR of the * following values: * * * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` * * If |flags| includes :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, * this frame has END_STREAM flag set. * * The library handles the CONTINUATION frame internally and it * correctly sets END_HEADERS to the last sequence of the PUSH_PROMISE * or CONTINUATION frame. * * If the |stream_id| is -1, this frame is assumed as request (i.e., * request HEADERS frame which opens new stream). In this case, the * assigned stream ID will be returned. Otherwise, specify stream ID * in |stream_id|. * * The |pri_spec| is a deprecated priority specification of this * request. ``NULL`` means the default priority (see * `nghttp2_priority_spec_default_init()`). To specify the priority, * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, * this function will copy its data members. In the future release * after the end of 2024, this function will ignore |pri_spec| and * behave as if ``NULL`` is given. * * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes :macro:`NGHTTP2_MAX_WEIGHT`. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is received by a remote endpoint, |pri_spec| is * ignored, and treated as if ``NULL`` is specified. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * The |stream_user_data| is a pointer to an arbitrary data which is * associated to the stream this frame will open. Therefore it is * only used if this frame opens streams, in other words, it changes * stream state from idle or reserved to open. * * This function is low-level in a sense that the application code can * specify flags directly. For usual HTTP request, * `nghttp2_submit_request2()` is useful. Likewise, for HTTP * response, prefer `nghttp2_submit_response2()`. * * This function returns newly assigned stream ID if it succeeds and * |stream_id| is -1. Otherwise, this function returns 0 if it * succeeds, or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * No stream ID is available because maximum stream ID was * reached. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0; or trying to depend on itself (stream ID * equals ``pri_spec->stream_id``). * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` * DATA or HEADERS has been already submitted and not fully * processed yet. This happens if stream denoted by |stream_id| * is in reserved state. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * The |stream_id| is -1, and |session| is server session. * * .. warning:: * * This function returns assigned stream ID if it succeeds and * |stream_id| is -1. But that stream is not opened yet. The * application must not submit frame to that stream ID before * :type:`nghttp2_before_frame_send_callback` is called for this * frame. * */ NGHTTP2_EXTERN int32_t nghttp2_submit_headers( nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, void *stream_user_data); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_submit_data2()` instead. * * Submits one or more DATA frames to the stream |stream_id|. The * data to be sent are provided by |data_prd|. If |flags| contains * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, the last DATA frame * has END_STREAM flag set. * * This function does not take ownership of the |data_prd|. The * function copies the members of the |data_prd|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` * DATA or HEADERS has been already submitted and not fully * processed yet. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` * The stream was already closed; or the |stream_id| is invalid. * * .. note:: * * Currently, only one DATA or HEADERS is allowed for a stream at a * time. Submitting these frames more than once before first DATA * or HEADERS is finished results in * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The * earliest callback which tells that previous frame is done is * :type:`nghttp2_on_frame_send_callback`. In side that callback, * new data can be submitted using `nghttp2_submit_data()`. Of * course, all data except for last one must not have * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` flag set in |flags|. * This sounds a bit complicated, and we recommend to use * `nghttp2_submit_request()` and `nghttp2_submit_response()` to * avoid this cascading issue. The experience shows that for HTTP * use, these two functions are enough to implement both client and * server. */ NGHTTP2_EXTERN int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_data_provider *data_prd); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Submits one or more DATA frames to the stream |stream_id|. The * data to be sent are provided by |data_prd|. If |flags| contains * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, the last DATA frame * has END_STREAM flag set. * * This function does not take ownership of the |data_prd|. The * function copies the members of the |data_prd|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` * DATA or HEADERS has been already submitted and not fully * processed yet. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` * The stream was already closed; or the |stream_id| is invalid. * * .. note:: * * Currently, only one DATA or HEADERS is allowed for a stream at a * time. Submitting these frames more than once before first DATA * or HEADERS is finished results in * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The * earliest callback which tells that previous frame is done is * :type:`nghttp2_on_frame_send_callback`. In side that callback, * new data can be submitted using `nghttp2_submit_data2()`. Of * course, all data except for last one must not have * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` flag set in |flags|. * This sounds a bit complicated, and we recommend to use * `nghttp2_submit_request2()` and `nghttp2_submit_response2()` to * avoid this cascading issue. The experience shows that for HTTP * use, these two functions are enough to implement both client and * server. */ NGHTTP2_EXTERN int nghttp2_submit_data2(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_data_provider2 *data_prd); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return 0 without doing anything. * * Submits PRIORITY frame to change the priority of stream |stream_id| * to the priority specification |pri_spec|. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |pri_spec| is a deprecated priority specification of this * request. ``NULL`` is not allowed for this function. To specify the * priority, use `nghttp2_priority_spec_init()`. This function will * copy its data members. * * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes * :macro:`NGHTTP2_MAX_WEIGHT`. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is received by a remote endpoint, this function does * nothing and returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0; or the |pri_spec| is NULL; or trying to * depend on itself. */ NGHTTP2_EXTERN int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_priority_spec *pri_spec); /** * @macro * * :macro:`NGHTTP2_EXTPRI_DEFAULT_URGENCY` is the default urgency * level for :rfc:`9218` extensible priorities. */ #define NGHTTP2_EXTPRI_DEFAULT_URGENCY 3 /** * @macro * * :macro:`NGHTTP2_EXTPRI_URGENCY_HIGH` is the highest urgency level * for :rfc:`9218` extensible priorities. */ #define NGHTTP2_EXTPRI_URGENCY_HIGH 0 /** * @macro * * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW` is the lowest urgency level for * :rfc:`9218` extensible priorities. */ #define NGHTTP2_EXTPRI_URGENCY_LOW 7 /** * @macro * * :macro:`NGHTTP2_EXTPRI_URGENCY_LEVELS` is the number of urgency * levels for :rfc:`9218` extensible priorities. */ #define NGHTTP2_EXTPRI_URGENCY_LEVELS (NGHTTP2_EXTPRI_URGENCY_LOW + 1) /** * @struct * * :type:`nghttp2_extpri` is :rfc:`9218` extensible priorities * specification for a stream. */ typedef struct nghttp2_extpri { /** * :member:`urgency` is the urgency of a stream, it must be in * [:macro:`NGHTTP2_EXTPRI_URGENCY_HIGH`, * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`], inclusive, and 0 is the * highest urgency. */ uint32_t urgency; /** * :member:`inc` indicates that a content can be processed * incrementally or not. If inc is 0, it cannot be processed * incrementally. If inc is 1, it can be processed incrementally. * Other value is not permitted. */ int inc; } nghttp2_extpri; /** * @function * * Submits RST_STREAM frame to cancel/reject the stream |stream_id| * with the error code |error_code|. * * The pre-defined error code is one of :enum:`nghttp2_error_code`. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0. */ NGHTTP2_EXTERN int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags, int32_t stream_id, uint32_t error_code); /** * @function * * Stores local settings and submits SETTINGS frame. The |iv| is the * pointer to the array of :type:`nghttp2_settings_entry`. The |niv| * indicates the number of :type:`nghttp2_settings_entry`. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * This function does not take ownership of the |iv|. This function * copies all the elements in the |iv|. * * While updating individual stream's local window size, if the window * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, * RST_STREAM is issued against such a stream. * * SETTINGS with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` is * automatically submitted by the library and application could not * send it at its will. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |iv| contains invalid value (e.g., initial window size * strictly greater than (1 << 31) - 1. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags, const nghttp2_settings_entry *iv, size_t niv); /** * @function * * Submits PUSH_PROMISE frame. * * The |flags| is currently ignored. The library handles the * CONTINUATION frame internally and it correctly sets END_HEADERS to * the last sequence of the PUSH_PROMISE or CONTINUATION frame. * * The |stream_id| must be client initiated stream ID. * * The |nva| is an array of name/value pair :type:`nghttp2_nv` with * |nvlen| elements. The application is responsible to include * required pseudo-header fields (header field whose name starts with * ":") in |nva| and must place pseudo-headers before regular header * fields. * * This function creates copies of all name/value pairs in |nva|. It * also lower-cases all names in |nva|. The order of elements in * |nva| is preserved. For header fields with * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, * header field name and value are not copied respectively. With * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application * is responsible to pass header field name in lowercase. The * application should maintain the references to them until * :type:`nghttp2_on_frame_send_callback` or * :type:`nghttp2_on_frame_not_send_callback` is called. * * The |promised_stream_user_data| is a pointer to an arbitrary data * which is associated to the promised stream this frame will open and * make it in reserved state. It is available using * `nghttp2_session_get_stream_user_data()`. The application can * access it in :type:`nghttp2_before_frame_send_callback` and * :type:`nghttp2_on_frame_send_callback` of this frame. * * The client side is not allowed to use this function. * * To submit response headers and data, use * `nghttp2_submit_response2()`. * * This function returns assigned promised stream ID if it succeeds, * or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` * This function was invoked when |session| is initialized as * client. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * No stream ID is available because maximum stream ID was * reached. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is 0; The |stream_id| does not designate stream * that peer initiated. * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` * The stream was already closed; or the |stream_id| is invalid. * * .. warning:: * * This function returns assigned promised stream ID if it succeeds. * As of 1.16.0, stream object for pushed resource is created when * this function succeeds. In that case, the application can submit * push response for the promised frame. * * In 1.15.0 or prior versions, pushed stream is not opened yet when * this function succeeds. The application must not submit frame to * that stream ID before :type:`nghttp2_before_frame_send_callback` * is called for this frame. * */ NGHTTP2_EXTERN int32_t nghttp2_submit_push_promise( nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen, void *promised_stream_user_data); /** * @function * * Submits PING frame. You don't have to send PING back when you * received PING frame. The library automatically submits PING frame * in this case. * * The |flags| is bitwise OR of 0 or more of the following value. * * * :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` * * Unless `nghttp2_option_set_no_auto_ping_ack()` is used, the |flags| * should be :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * If the |opaque_data| is non ``NULL``, then it should point to the 8 * bytes array of memory to specify opaque data to send with PING * frame. If the |opaque_data| is ``NULL``, zero-cleared 8 bytes will * be sent as opaque data. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, const uint8_t *opaque_data); /** * @function * * Submits GOAWAY frame with the last stream ID |last_stream_id| and * the error code |error_code|. * * The pre-defined error code is one of :enum:`nghttp2_error_code`. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |last_stream_id| is peer's stream ID or 0. So if |session| is * initialized as client, |last_stream_id| must be even or 0. If * |session| is initialized as server, |last_stream_id| must be odd or * 0. * * The HTTP/2 specification says last_stream_id must not be increased * from the value previously sent. So the actual value sent as * last_stream_id is the minimum value between the given * |last_stream_id| and the last_stream_id previously sent to the * peer. * * If the |opaque_data| is not ``NULL`` and |opaque_data_len| is not * zero, those data will be sent as additional debug data. The * library makes a copy of the memory region pointed by |opaque_data| * with the length |opaque_data_len|, so the caller does not need to * keep this memory after the return of this function. If the * |opaque_data_len| is 0, the |opaque_data| could be ``NULL``. * * After successful transmission of GOAWAY, following things happen. * All incoming streams having strictly more than |last_stream_id| are * closed. All incoming HEADERS which starts new stream are simply * ignored. After all active streams are handled, both * `nghttp2_session_want_read()` and `nghttp2_session_want_write()` * return 0 and the application can close session. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |opaque_data_len| is too large; the |last_stream_id| is * invalid. */ NGHTTP2_EXTERN int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, int32_t last_stream_id, uint32_t error_code, const uint8_t *opaque_data, size_t opaque_data_len); /** * @function * * Returns the last stream ID of a stream for which * :type:`nghttp2_on_frame_recv_callback` was invoked most recently. * The returned value can be used as last_stream_id parameter for * `nghttp2_submit_goaway()` and * `nghttp2_session_terminate_session2()`. * * This function always succeeds. */ NGHTTP2_EXTERN int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session); /** * @function * * Returns nonzero if new request can be sent from local endpoint. * * This function return 0 if request is not allowed for this session. * There are several reasons why request is not allowed. Some of the * reasons are: session is server; stream ID has been spent; GOAWAY * has been sent or received. * * The application can call `nghttp2_submit_request2()` without * consulting this function. In that case, * `nghttp2_submit_request2()` may return error. Or, request is * failed to sent, and :type:`nghttp2_on_stream_close_callback` is * called. */ NGHTTP2_EXTERN int nghttp2_session_check_request_allowed(nghttp2_session *session); /** * @function * * Returns nonzero if |session| is initialized as server side session. */ NGHTTP2_EXTERN int nghttp2_session_check_server_session(nghttp2_session *session); /** * @function * * Submits WINDOW_UPDATE frame. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |stream_id| is the stream ID to send this WINDOW_UPDATE. To * send connection level WINDOW_UPDATE, specify 0 to |stream_id|. * * If the |window_size_increment| is positive, the WINDOW_UPDATE with * that value as window_size_increment is queued. If the * |window_size_increment| is larger than the received bytes from the * remote endpoint, the local window size is increased by that * difference. If the sole purpose is to increase the local window * size, consider to use `nghttp2_session_set_local_window_size()`. * * If the |window_size_increment| is negative, the local window size * is decreased by -|window_size_increment|. If automatic * WINDOW_UPDATE is enabled * (`nghttp2_option_set_no_auto_window_update()`), and the library * decided that the WINDOW_UPDATE should be submitted, then * WINDOW_UPDATE is queued with the current received bytes count. If * the sole purpose is to decrease the local window size, consider to * use `nghttp2_session_set_local_window_size()`. * * If the |window_size_increment| is 0, the function does nothing and * returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_FLOW_CONTROL` * The local window size overflow or gets negative. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size_increment); /** * @function * * Set local window size (local endpoints's window size) to the given * |window_size| for the given stream denoted by |stream_id|. To * change connection level window size, specify 0 to |stream_id|. To * increase window size, this function may submit WINDOW_UPDATE frame * to transmission queue. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * This sounds similar to `nghttp2_submit_window_update()`, but there * are 2 differences. The first difference is that this function * takes the absolute value of window size to set, rather than the * delta. To change the window size, this may be easier to use since * the application just declares the intended window size, rather than * calculating delta. The second difference is that * `nghttp2_submit_window_update()` affects the received bytes count * which has not acked yet. By the specification of * `nghttp2_submit_window_update()`, to strictly increase the local * window size, we have to submit delta including all received bytes * count, which might not be desirable in some cases. On the other * hand, this function does not affect the received bytes count. It * just sets the local window size to the given value. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |stream_id| is negative. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_session_set_local_window_size(nghttp2_session *session, uint8_t flags, int32_t stream_id, int32_t window_size); /** * @function * * Submits extension frame. * * Application can pass arbitrary frame flags and stream ID in |flags| * and |stream_id| respectively. The |payload| is opaque pointer, and * it can be accessible though ``frame->ext.payload`` in * :type:`nghttp2_pack_extension_callback2`. The library will not own * passed |payload| pointer. * * The application must set :type:`nghttp2_pack_extension_callback2` * using `nghttp2_session_callbacks_set_pack_extension_callback2()`. * * The application should retain the memory pointed by |payload| until * the transmission of extension frame is done (which is indicated by * :type:`nghttp2_on_frame_send_callback`), or transmission fails * (which is indicated by :type:`nghttp2_on_frame_not_send_callback`). * If application does not touch this memory region after packing it * into a wire format, application can free it inside * :type:`nghttp2_pack_extension_callback2`. * * The standard HTTP/2 frame cannot be sent with this function, so * |type| must be strictly grater than 0x9. Otherwise, this function * will fail with error code * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * If :type:`nghttp2_pack_extension_callback2` is not set. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * If |type| specifies standard HTTP/2 frame type. The frame * types in the rage [0x0, 0x9], both inclusive, are standard * HTTP/2 frame type, and cannot be sent using this function. * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory */ NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session, uint8_t type, uint8_t flags, int32_t stream_id, void *payload); /** * @struct * * The payload of ALTSVC frame. ALTSVC frame is a non-critical * extension to HTTP/2. If this frame is received, and * `nghttp2_option_set_user_recv_extension_type()` is not set, and * `nghttp2_option_set_builtin_recv_extension_type()` is set for * :enum:`nghttp2_frame_type.NGHTTP2_ALTSVC`, * ``nghttp2_extension.payload`` will point to this struct. * * It has the following members: */ typedef struct { /** * The pointer to origin which this alternative service is * associated with. This is not necessarily NULL-terminated. */ uint8_t *origin; /** * The length of the |origin|. */ size_t origin_len; /** * The pointer to Alt-Svc field value contained in ALTSVC frame. * This is not necessarily NULL-terminated. */ uint8_t *field_value; /** * The length of the |field_value|. */ size_t field_value_len; } nghttp2_ext_altsvc; /** * @function * * Submits ALTSVC frame. * * ALTSVC frame is a non-critical extension to HTTP/2, and defined in * `RFC 7383 `_. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |origin| points to the origin this alternative service is * associated with. The |origin_len| is the length of the origin. If * |stream_id| is 0, the origin must be specified. If |stream_id| is * not zero, the origin must be empty (in other words, |origin_len| * must be 0). * * The ALTSVC frame is only usable from server side. If this function * is invoked with client side session, this function returns * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The function is called from client side session * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The sum of |origin_len| and |field_value_len| is larger than * 16382; or |origin_len| is 0 while |stream_id| is 0; or * |origin_len| is not 0 while |stream_id| is not 0. */ NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *origin, size_t origin_len, const uint8_t *field_value, size_t field_value_len); /** * @struct * * The single entry of an origin. */ typedef struct { /** * The pointer to origin. No validation is made against this field * by the library. This is not necessarily NULL-terminated. */ uint8_t *origin; /** * The length of the |origin|. */ size_t origin_len; } nghttp2_origin_entry; /** * @struct * * The payload of ORIGIN frame. ORIGIN frame is a non-critical * extension to HTTP/2 and defined by `RFC 8336 * `_. * * If this frame is received, and * `nghttp2_option_set_user_recv_extension_type()` is not set, and * `nghttp2_option_set_builtin_recv_extension_type()` is set for * :enum:`nghttp2_frame_type.NGHTTP2_ORIGIN`, * ``nghttp2_extension.payload`` will point to this struct. * * It has the following members: */ typedef struct { /** * The number of origins contained in |ov|. */ size_t nov; /** * The pointer to the array of origins contained in ORIGIN frame. */ nghttp2_origin_entry *ov; } nghttp2_ext_origin; /** * @function * * Submits ORIGIN frame. * * ORIGIN frame is a non-critical extension to HTTP/2 and defined by * `RFC 8336 `_. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |ov| points to the array of origins. The |nov| specifies the * number of origins included in |ov|. This function creates copies * of all elements in |ov|. * * The ORIGIN frame is only usable by a server. If this function is * invoked with client side session, this function returns * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The function is called from client side session. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * There are too many origins, or an origin is too large to fit * into a default frame payload. */ NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags, const nghttp2_origin_entry *ov, size_t nov); /** * @struct * * The payload of PRIORITY_UPDATE frame. PRIORITY_UPDATE frame is a * non-critical extension to HTTP/2. If this frame is received, and * `nghttp2_option_set_user_recv_extension_type()` is not set, and * `nghttp2_option_set_builtin_recv_extension_type()` is set for * :enum:`nghttp2_frame_type.NGHTTP2_PRIORITY_UPDATE`, * ``nghttp2_extension.payload`` will point to this struct. * * It has the following members: */ typedef struct { /** * The stream ID of the stream whose priority is updated. */ int32_t stream_id; /** * The pointer to Priority field value. It is not necessarily * NULL-terminated. */ uint8_t *field_value; /** * The length of the :member:`field_value`. */ size_t field_value_len; } nghttp2_ext_priority_update; /** * @function * * Submits PRIORITY_UPDATE frame. * * PRIORITY_UPDATE frame is a non-critical extension to HTTP/2, and * defined in :rfc:`9218#section-7.1`. * * The |flags| is currently ignored and should be * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. * * The |stream_id| is the ID of stream which is prioritized. The * |field_value| points to the Priority field value. The * |field_value_len| is the length of the Priority field value. * * If this function is called by server, * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` is returned. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 0 is received by a remote endpoint (or it is omitted), * this function does nothing and returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The function is called from server side session * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * The |field_value_len| is larger than 16380; or |stream_id| is * 0. */ NGHTTP2_EXTERN int nghttp2_submit_priority_update(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *field_value, size_t field_value_len); /** * @function * * Changes the priority of the existing stream denoted by |stream_id|. * The new priority is |extpri|. This function is meant to be used by * server for :rfc:`9218` extensible prioritization scheme. * * If |session| is initialized as client, this function returns * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. For client, use * `nghttp2_submit_priority_update()` instead. * * If :member:`extpri->urgency ` is out of * bound, it is set to :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`. * * If |ignore_client_signal| is nonzero, server starts to ignore * client priority signals for this stream. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is not submitted via `nghttp2_submit_settings()`, * this function does nothing and returns 0. * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The |session| is initialized as client. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * |stream_id| is zero; or a stream denoted by |stream_id| is not * found. */ NGHTTP2_EXTERN int nghttp2_session_change_extpri_stream_priority( nghttp2_session *session, int32_t stream_id, const nghttp2_extpri *extpri, int ignore_client_signal); /** * @function * * Stores the stream priority of the existing stream denoted by * |stream_id| in the object pointed by |extpri|. This function is * meant to be used by server for :rfc:`9218` extensible * prioritization scheme. * * If |session| is initialized as client, this function returns * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. * * If * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` * of value of 1 is not submitted via `nghttp2_submit_settings()`, * this function does nothing and returns 0. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The |session| is initialized as client. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * |stream_id| is zero; or a stream denoted by |stream_id| is not * found. */ NGHTTP2_EXTERN int nghttp2_session_get_extpri_stream_priority( nghttp2_session *session, nghttp2_extpri *extpri, int32_t stream_id); /** * @function * * Parses Priority header field value pointed by |value| of length * |len|, and stores the result in the object pointed by |extpri|. * Priority header field is defined in :rfc:`9218`. * * This function does not initialize the object pointed by |extpri| * before storing the result. It only assigns the values that the * parser correctly extracted to fields. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` * Failed to parse the header field value. */ NGHTTP2_EXTERN int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, const uint8_t *value, size_t len); /** * @function * * Compares ``lhs->name`` of length ``lhs->namelen`` bytes and * ``rhs->name`` of length ``rhs->namelen`` bytes. Returns negative * integer if ``lhs->name`` is found to be less than ``rhs->name``; or * returns positive integer if ``lhs->name`` is found to be greater * than ``rhs->name``; or returns 0 otherwise. */ NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs); /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_select_alpn` instead. * * A helper function for dealing with ALPN in server side. The |in| * contains peer's protocol list in preferable order. The format of * |in| is length-prefixed and not null-terminated. For example, * ``h2`` and ``http/1.1`` stored in |in| like this:: * * in[0] = 2 * in[1..2] = "h2" * in[3] = 8 * in[4..11] = "http/1.1" * inlen = 12 * * The selection algorithm is as follows: * * 1. If peer's list contains HTTP/2 protocol the library supports, * it is selected and returns 1. The following step is not taken. * * 2. If peer's list contains ``http/1.1``, this function selects * ``http/1.1`` and returns 0. The following step is not taken. * * 3. This function selects nothing and returns -1 (So called * non-overlap case). In this case, |out| and |outlen| are left * untouched. * * Selecting ``h2`` means that ``h2`` is written into |*out| and its * length (which is 2) is assigned to |*outlen|. * * For ALPN, refer to https://tools.ietf.org/html/rfc7301 * * To use this method you should do something like:: * * static int alpn_select_proto_cb(SSL* ssl, * const unsigned char **out, * unsigned char *outlen, * const unsigned char *in, * unsigned int inlen, * void *arg) * { * int rv; * rv = nghttp2_select_next_protocol((unsigned char**)out, outlen, * in, inlen); * if (rv == -1) { * return SSL_TLSEXT_ERR_NOACK; * } * if (rv == 1) { * ((MyType*)arg)->http2_selected = 1; * } * return SSL_TLSEXT_ERR_OK; * } * ... * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); * */ NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen); /** * @function * * A helper function for dealing with ALPN in server side. The |in| * contains peer's protocol list in preferable order. The format of * |in| is length-prefixed and not null-terminated. For example, * ``h2`` and ``http/1.1`` stored in |in| like this:: * * in[0] = 2 * in[1..2] = "h2" * in[3] = 8 * in[4..11] = "http/1.1" * inlen = 12 * * The selection algorithm is as follows: * * 1. If peer's list contains HTTP/2 protocol the library supports, * it is selected and returns 1. The following step is not taken. * * 2. If peer's list contains ``http/1.1``, this function selects * ``http/1.1`` and returns 0. The following step is not taken. * * 3. This function selects nothing and returns -1 (So called * non-overlap case). In this case, |out| and |outlen| are left * untouched. * * Selecting ``h2`` means that ``h2`` is written into |*out| and its * length (which is 2) is assigned to |*outlen|. * * For ALPN, refer to https://tools.ietf.org/html/rfc7301 * * To use this method you should do something like:: * * static int alpn_select_proto_cb(SSL* ssl, * const unsigned char **out, * unsigned char *outlen, * const unsigned char *in, * unsigned int inlen, * void *arg) * { * int rv; * rv = nghttp2_select_alpn(out, outlen, in, inlen); * if (rv == -1) { * return SSL_TLSEXT_ERR_NOACK; * } * if (rv == 1) { * ((MyType*)arg)->http2_selected = 1; * } * return SSL_TLSEXT_ERR_OK; * } * ... * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); * */ NGHTTP2_EXTERN int nghttp2_select_alpn(const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen); /** * @function * * Returns a pointer to a nghttp2_info struct with version information * about the run-time library in use. The |least_version| argument * can be set to a 24 bit numerical value for the least accepted * version number and if the condition is not met, this function will * return a ``NULL``. Pass in 0 to skip the version checking. */ NGHTTP2_EXTERN nghttp2_info *nghttp2_version(int least_version); /** * @function * * Returns nonzero if the :type:`nghttp2_error` library error code * |lib_error| is fatal. */ NGHTTP2_EXTERN int nghttp2_is_fatal(int lib_error_code); /** * @function * * Returns nonzero if HTTP header field name |name| of length |len| is * valid according to http://tools.ietf.org/html/rfc7230#section-3.2 * * Because this is a header field name in HTTP2, the upper cased alphabet * is treated as error. */ NGHTTP2_EXTERN int nghttp2_check_header_name(const uint8_t *name, size_t len); /** * @function * * Returns nonzero if HTTP header field value |value| of length |len| * is valid according to * http://tools.ietf.org/html/rfc7230#section-3.2 * * This function is considered obsolete, and application should * consider to use `nghttp2_check_header_value_rfc9113()` instead. */ NGHTTP2_EXTERN int nghttp2_check_header_value(const uint8_t *value, size_t len); /** * @function * * Returns nonzero if HTTP header field value |value| of length |len| * is valid according to * http://tools.ietf.org/html/rfc7230#section-3.2, plus * https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1 */ NGHTTP2_EXTERN int nghttp2_check_header_value_rfc9113(const uint8_t *value, size_t len); /** * @function * * Returns nonzero if the |value| which is supposed to be the value of * the :method header field is valid according to * https://datatracker.ietf.org/doc/html/rfc7231#section-4 and * https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 */ NGHTTP2_EXTERN int nghttp2_check_method(const uint8_t *value, size_t len); /** * @function * * Returns nonzero if the |value| which is supposed to be the value of * the :path header field is valid according to * https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3 * * |value| is valid if it merely consists of the allowed characters. * In particular, it does not check whether |value| follows the syntax * of path. The allowed characters are all characters valid by * `nghttp2_check_header_value` minus SPC and HT. */ NGHTTP2_EXTERN int nghttp2_check_path(const uint8_t *value, size_t len); /** * @function * * Returns nonzero if the |value| which is supposed to be the value of the * :authority or host header field is valid according to * https://tools.ietf.org/html/rfc3986#section-3.2 * * Note that :authority and host field values are not authority. They * do not include userinfo in RFC 3986, see * https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2, that * is, it does not include '@'. This function treats '@' as a valid * character. * * |value| is valid if it merely consists of the allowed characters. * In particular, it does not check whether |value| follows the syntax * of authority. */ NGHTTP2_EXTERN int nghttp2_check_authority(const uint8_t *value, size_t len); /* HPACK API */ struct nghttp2_hd_deflater; /** * @struct * * HPACK deflater object. */ typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; /** * @function * * Initializes |*deflater_ptr| for deflating name/values pairs. * * The |max_deflate_dynamic_table_size| is the upper bound of header * table size the deflater will use. * * If this function fails, |*deflater_ptr| is left untouched. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, size_t max_deflate_dynamic_table_size); /** * @function * * Like `nghttp2_hd_deflate_new()`, but with additional custom memory * allocator specified in the |mem|. * * The |mem| can be ``NULL`` and the call is equivalent to * `nghttp2_hd_deflate_new()`. * * This function does not take ownership |mem|. The application is * responsible for freeing |mem|. * * The library code does not refer to |mem| pointer after this * function returns, so the application can safely free it. */ NGHTTP2_EXTERN int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, size_t max_deflate_dynamic_table_size, nghttp2_mem *mem); /** * @function * * Deallocates any resources allocated for |deflater|. */ NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); /** * @function * * Changes header table size of the |deflater| to * |settings_max_dynamic_table_size| bytes. This may trigger eviction * in the dynamic table. * * The |settings_max_dynamic_table_size| should be the value received * in SETTINGS_HEADER_TABLE_SIZE. * * The deflater never uses more memory than * ``max_deflate_dynamic_table_size`` bytes specified in * `nghttp2_hd_deflate_new()`. Therefore, if * |settings_max_dynamic_table_size| > * ``max_deflate_dynamic_table_size``, resulting maximum table size * becomes ``max_deflate_dynamic_table_size``. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_hd_deflate_hd2()` instead. * * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the |buf| of length |buflen|. * * If |buf| is not large enough to store the deflated header block, * this function fails with * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller * should use `nghttp2_hd_deflate_bound()` to know the upper bound of * buffer size required to deflate given header name/value pairs. * * Once this function fails, subsequent call of this function always * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. * * After this function returns, it is safe to delete the |nva|. * * This function returns the number of bytes written to |buf| if it * succeeds, or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Deflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nva, size_t nvlen); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the |buf| of length |buflen|. * * If |buf| is not large enough to store the deflated header block, * this function fails with * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller * should use `nghttp2_hd_deflate_bound()` to know the upper bound of * buffer size required to deflate given header name/value pairs. * * Once this function fails, subsequent call of this function always * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. * * After this function returns, it is safe to delete the |nva|. * * This function returns the number of bytes written to |buf| if it * succeeds, or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Deflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_hd_deflate_hd2(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nva, size_t nvlen); #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_hd_deflate_hd_vec2()` instead. * * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the |veclen| size of buf vector |vec|. The each size of buffer * must be set in len field of :type:`nghttp2_vec`. If and only if * one chunk is filled up completely, next chunk will be used. If * |vec| is not large enough to store the deflated header block, this * function fails with * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller * should use `nghttp2_hd_deflate_bound()` to know the upper bound of * buffer size required to deflate given header name/value pairs. * * Once this function fails, subsequent call of this function always * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. * * After this function returns, it is safe to delete the |nva|. * * This function returns the number of bytes written to |vec| if it * succeeds, or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Deflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, const nghttp2_nv *nva, size_t nvlen); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the |veclen| size of buf vector |vec|. The each size of buffer * must be set in len field of :type:`nghttp2_vec`. If and only if * one chunk is filled up completely, next chunk will be used. If * |vec| is not large enough to store the deflated header block, this * function fails with * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller * should use `nghttp2_hd_deflate_bound()` to know the upper bound of * buffer size required to deflate given header name/value pairs. * * Once this function fails, subsequent call of this function always * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. * * After this function returns, it is safe to delete the |nva|. * * This function returns the number of bytes written to |vec| if it * succeeds, or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Deflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` * The provided |buflen| size is too small to hold the output. */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_hd_deflate_hd_vec2( nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, const nghttp2_nv *nva, size_t nvlen); /** * @function * * Returns an upper bound on the compressed size after deflation of * |nva| of length |nvlen|. */ NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen); /** * @function * * Returns the number of entries that header table of |deflater| * contains. This is the sum of the number of static table and * dynamic table, so the return value is at least 61. */ NGHTTP2_EXTERN size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater); /** * @function * * Returns the table entry denoted by |idx| from header table of * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of * static table. idx=62 returns first entry of dynamic table if it * exists. Specifying idx=0 is error, and this function returns NULL. * If |idx| is strictly greater than the number of entries the tables * contain, this function returns NULL. */ NGHTTP2_EXTERN const nghttp2_nv * nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx); /** * @function * * Returns the used dynamic table size, including the overhead 32 * bytes per entry described in RFC 7541. */ NGHTTP2_EXTERN size_t nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater); /** * @function * * Returns the maximum dynamic table size. */ NGHTTP2_EXTERN size_t nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater); struct nghttp2_hd_inflater; /** * @struct * * HPACK inflater object. */ typedef struct nghttp2_hd_inflater nghttp2_hd_inflater; /** * @function * * Initializes |*inflater_ptr| for inflating name/values pairs. * * If this function fails, |*inflater_ptr| is left untouched. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. */ NGHTTP2_EXTERN int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr); /** * @function * * Like `nghttp2_hd_inflate_new()`, but with additional custom memory * allocator specified in the |mem|. * * The |mem| can be ``NULL`` and the call is equivalent to * `nghttp2_hd_inflate_new()`. * * This function does not take ownership |mem|. The application is * responsible for freeing |mem|. * * The library code does not refer to |mem| pointer after this * function returns, so the application can safely free it. */ NGHTTP2_EXTERN int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr, nghttp2_mem *mem); /** * @function * * Deallocates any resources allocated for |inflater|. */ NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); /** * @function * * Changes header table size in the |inflater|. This may trigger * eviction in the dynamic table. * * The |settings_max_dynamic_table_size| should be the value * transmitted in SETTINGS_HEADER_TABLE_SIZE. * * This function must not be called while header block is being * inflated. In other words, this function must be called after * initialization of |inflater|, but before calling * `nghttp2_hd_inflate_hd3()`, or after * `nghttp2_hd_inflate_end_headers()`. Otherwise, * `NGHTTP2_ERR_INVALID_STATE` was returned. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` * The function is called while header block is being inflated. * Probably, application missed to call * `nghttp2_hd_inflate_end_headers()`. */ NGHTTP2_EXTERN int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size); /** * @enum * * The flags for header inflation. */ typedef enum { /** * No flag set. */ NGHTTP2_HD_INFLATE_NONE = 0, /** * Indicates all headers were inflated. */ NGHTTP2_HD_INFLATE_FINAL = 0x01, /** * Indicates a header was emitted. */ NGHTTP2_HD_INFLATE_EMIT = 0x02 } nghttp2_hd_inflate_flag; #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_hd_inflate_hd2()` instead. * * Inflates name/value block stored in |in| with length |inlen|. This * function performs decompression. For each successful emission of * header name/value pair, * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in * |*inflate_flags| and name/value pair is assigned to the |nv_out| * and the function returns. The caller must not free the members of * |nv_out|. * * The |nv_out| may include pointers to the memory region in the |in|. * The caller must retain the |in| while the |nv_out| is used. * * The application should call this function repeatedly until the * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and * return value is non-negative. This means the all input values are * processed successfully. Then the application must call * `nghttp2_hd_inflate_end_headers()` to prepare for the next header * block input. * * The caller can feed complete compressed header block. It also can * feed it in several chunks. The caller must set |in_final| to * nonzero if the given input is the last block of the compressed * header. * * This function returns the number of bytes processed if it succeeds, * or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Inflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` * The header field name or value is too large. * * Example follows:: * * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, * uint8_t *in, size_t inlen, int final) * { * ssize_t rv; * * for(;;) { * nghttp2_nv nv; * int inflate_flags = 0; * * rv = nghttp2_hd_inflate_hd(hd_inflater, &nv, &inflate_flags, * in, inlen, final); * * if(rv < 0) { * fprintf(stderr, "inflate failed with error code %zd", rv); * return -1; * } * * in += rv; * inlen -= rv; * * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { * fwrite(nv.name, nv.namelen, 1, stderr); * fprintf(stderr, ": "); * fwrite(nv.value, nv.valuelen, 1, stderr); * fprintf(stderr, "\n"); * } * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { * nghttp2_hd_inflate_end_headers(hd_inflater); * break; * } * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && * inlen == 0) { * break; * } * } * * return 0; * } * */ NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, uint8_t *in, size_t inlen, int in_final); #endif /* NGHTTP2_NO_SSIZE_T */ #ifndef NGHTTP2_NO_SSIZE_T /** * @function * * .. warning:: * * Deprecated. Use `nghttp2_hd_inflate_hd3()` instead. * * Inflates name/value block stored in |in| with length |inlen|. This * function performs decompression. For each successful emission of * header name/value pair, * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in * |*inflate_flags| and name/value pair is assigned to the |nv_out| * and the function returns. The caller must not free the members of * |nv_out|. * * The |nv_out| may include pointers to the memory region in the |in|. * The caller must retain the |in| while the |nv_out| is used. * * The application should call this function repeatedly until the * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and * return value is non-negative. If that happens, all given input * data (|inlen| bytes) are processed successfully. Then the * application must call `nghttp2_hd_inflate_end_headers()` to prepare * for the next header block input. * * In other words, if |in_final| is nonzero, and this function returns * |inlen|, you can assert that * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in * |*inflate_flags|. * * The caller can feed complete compressed header block. It also can * feed it in several chunks. The caller must set |in_final| to * nonzero if the given input is the last block of the compressed * header. * * This function returns the number of bytes processed if it succeeds, * or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Inflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` * The header field name or value is too large. * * Example follows:: * * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, * uint8_t *in, size_t inlen, int final) * { * ssize_t rv; * * for(;;) { * nghttp2_nv nv; * int inflate_flags = 0; * * rv = nghttp2_hd_inflate_hd2(hd_inflater, &nv, &inflate_flags, * in, inlen, final); * * if(rv < 0) { * fprintf(stderr, "inflate failed with error code %zd", rv); * return -1; * } * * in += rv; * inlen -= rv; * * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { * fwrite(nv.name, nv.namelen, 1, stderr); * fprintf(stderr, ": "); * fwrite(nv.value, nv.valuelen, 1, stderr); * fprintf(stderr, "\n"); * } * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { * nghttp2_hd_inflate_end_headers(hd_inflater); * break; * } * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && * inlen == 0) { * break; * } * } * * return 0; * } * */ NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final); #endif /* NGHTTP2_NO_SSIZE_T */ /** * @function * * Inflates name/value block stored in |in| with length |inlen|. This * function performs decompression. For each successful emission of * header name/value pair, * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in * |*inflate_flags| and name/value pair is assigned to the |nv_out| * and the function returns. The caller must not free the members of * |nv_out|. * * The |nv_out| may include pointers to the memory region in the |in|. * The caller must retain the |in| while the |nv_out| is used. * * The application should call this function repeatedly until the * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and * return value is non-negative. If that happens, all given input * data (|inlen| bytes) are processed successfully. Then the * application must call `nghttp2_hd_inflate_end_headers()` to prepare * for the next header block input. * * In other words, if |in_final| is nonzero, and this function returns * |inlen|, you can assert that * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in * |*inflate_flags|. * * The caller can feed complete compressed header block. It also can * feed it in several chunks. The caller must set |in_final| to * nonzero if the given input is the last block of the compressed * header. * * This function returns the number of bytes processed if it succeeds, * or one of the following negative error codes: * * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` * Out of memory. * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` * Inflation process has failed. * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` * The header field name or value is too large. * * Example follows:: * * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, * uint8_t *in, size_t inlen, int final) * { * nghttp2_ssize rv; * * for(;;) { * nghttp2_nv nv; * int inflate_flags = 0; * * rv = nghttp2_hd_inflate_hd3(hd_inflater, &nv, &inflate_flags, * in, inlen, final); * * if(rv < 0) { * fprintf(stderr, "inflate failed with error code %td", rv); * return -1; * } * * in += rv; * inlen -= rv; * * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { * fwrite(nv.name, nv.namelen, 1, stderr); * fprintf(stderr, ": "); * fwrite(nv.value, nv.valuelen, 1, stderr); * fprintf(stderr, "\n"); * } * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { * nghttp2_hd_inflate_end_headers(hd_inflater); * break; * } * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && * inlen == 0) { * break; * } * } * * return 0; * } * */ NGHTTP2_EXTERN nghttp2_ssize nghttp2_hd_inflate_hd3( nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final); /** * @function * * Signals the end of decompression for one header block. * * This function returns 0 if it succeeds. Currently this function * always succeeds. */ NGHTTP2_EXTERN int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater); /** * @function * * Returns the number of entries that header table of |inflater| * contains. This is the sum of the number of static table and * dynamic table, so the return value is at least 61. */ NGHTTP2_EXTERN size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater); /** * @function * * Returns the table entry denoted by |idx| from header table of * |inflater|. The |idx| is 1-based, and idx=1 returns first entry of * static table. idx=62 returns first entry of dynamic table if it * exists. Specifying idx=0 is error, and this function returns NULL. * If |idx| is strictly greater than the number of entries the tables * contain, this function returns NULL. */ NGHTTP2_EXTERN const nghttp2_nv * nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx); /** * @function * * Returns the used dynamic table size, including the overhead 32 * bytes per entry described in RFC 7541. */ NGHTTP2_EXTERN size_t nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater); /** * @function * * Returns the maximum dynamic table size. */ NGHTTP2_EXTERN size_t nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater); struct nghttp2_stream; /** * @struct * * The structure to represent HTTP/2 stream. The details of this * structure are intentionally hidden from the public API. */ typedef struct nghttp2_stream nghttp2_stream; /** * @function * * Returns pointer to :type:`nghttp2_stream` object denoted by * |stream_id|. If stream was not found, returns NULL. * * Returns imaginary root stream (see * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|. * * Unless |stream_id| == 0, the returned pointer is valid until next * call of `nghttp2_session_send()`, `nghttp2_session_mem_send2()`, * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv2()`. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_session_find_stream(nghttp2_session *session, int32_t stream_id); /** * @enum * * State of stream as described in RFC 7540. */ typedef enum { /** * idle state. */ NGHTTP2_STREAM_STATE_IDLE = 1, /** * open state. */ NGHTTP2_STREAM_STATE_OPEN, /** * reserved (local) state. */ NGHTTP2_STREAM_STATE_RESERVED_LOCAL, /** * reserved (remote) state. */ NGHTTP2_STREAM_STATE_RESERVED_REMOTE, /** * half closed (local) state. */ NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, /** * half closed (remote) state. */ NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, /** * closed state. */ NGHTTP2_STREAM_STATE_CLOSED } nghttp2_stream_proto_state; /** * @function * * Returns state of |stream|. The root stream retrieved by * `nghttp2_session_get_root_stream()` will have stream state * :enum:`nghttp2_stream_proto_state.NGHTTP2_STREAM_STATE_IDLE`. */ NGHTTP2_EXTERN nghttp2_stream_proto_state nghttp2_stream_get_state(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. * * Returns root of dependency tree, which is imaginary stream with * stream ID 0. The returned pointer is valid until |session| is * freed by `nghttp2_session_del()`. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_session_get_root_stream(nghttp2_session *session); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return NULL. * * Returns the parent stream of |stream| in dependency tree. Returns * NULL if there is no such stream. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_stream_get_parent(nghttp2_stream *stream); NGHTTP2_EXTERN int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return NULL. * * Returns the next sibling stream of |stream| in dependency tree. * Returns NULL if there is no such stream. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_stream_get_next_sibling(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return NULL. * * Returns the previous sibling stream of |stream| in dependency tree. * Returns NULL if there is no such stream. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_stream_get_previous_sibling(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return NULL. * * Returns the first child stream of |stream| in dependency tree. * Returns NULL if there is no such stream. */ NGHTTP2_EXTERN nghttp2_stream * nghttp2_stream_get_first_child(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return * :macro:`NGHTTP2_DEFAULT_WEIGHT`. * * Returns dependency weight to the parent stream of |stream|. */ NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream); /** * @function * * .. warning:: * * Deprecated. :rfc:`7540` priorities are deprecated by * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible * prioritization scheme. In the future release after the end of * 2024, this function will always return 0. * * Returns the sum of the weight for |stream|'s children. */ NGHTTP2_EXTERN int32_t nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); /** * @functypedef * * Callback function invoked when the library outputs debug logging. * The function is called with arguments suitable for ``vfprintf(3)`` * * The debug output is only enabled if the library is built with * ``DEBUGBUILD`` macro defined. */ typedef void (*nghttp2_debug_vprintf_callback)(const char *format, va_list args); /** * @function * * Sets a debug output callback called by the library when built with * ``DEBUGBUILD`` macro defined. If this option is not used, debug * log is written into standard error output. * * For builds without ``DEBUGBUILD`` macro defined, this function is * noop. * * Note that building with ``DEBUGBUILD`` may cause significant * performance penalty to libnghttp2 because of extra processing. It * should be used for debugging purpose only. * * .. Warning:: * * Building with ``DEBUGBUILD`` may cause significant performance * penalty to libnghttp2 because of extra processing. It should be * used for debugging purpose only. We write this two times because * this is important. */ NGHTTP2_EXTERN void nghttp2_set_debug_vprintf_callback( nghttp2_debug_vprintf_callback debug_vprintf_callback); #ifdef __cplusplus } #endif #endif /* NGHTTP2_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2_buf.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_buf.h" #include void nghttp2_buf_init(nghttp2_buf *buf) { buf->begin = NULL; buf->end = NULL; buf->pos = NULL; buf->last = NULL; buf->mark = NULL; } int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem) { nghttp2_buf_init(buf); return nghttp2_buf_reserve(buf, initial, mem); } void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem) { if (buf == NULL) { return; } nghttp2_mem_free(mem, buf->begin); buf->begin = NULL; } int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) { uint8_t *ptr; size_t cap; cap = nghttp2_buf_cap(buf); if (cap >= new_cap) { return 0; } new_cap = nghttp2_max_size(new_cap, cap * 2); ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap); if (ptr == NULL) { return NGHTTP2_ERR_NOMEM; } buf->pos = ptr + (buf->pos - buf->begin); buf->last = ptr + (buf->last - buf->begin); buf->mark = ptr + (buf->mark - buf->begin); buf->begin = ptr; buf->end = ptr + new_cap; return 0; } void nghttp2_buf_reset(nghttp2_buf *buf) { buf->pos = buf->last = buf->mark = buf->begin; } void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len) { buf->begin = buf->pos = buf->last = buf->mark = buf->end = begin; if (len) { buf->end += len; } } static int buf_chain_new(nghttp2_buf_chain **chain, size_t chunk_length, nghttp2_mem *mem) { int rv; *chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); if (*chain == NULL) { return NGHTTP2_ERR_NOMEM; } (*chain)->next = NULL; rv = nghttp2_buf_init2(&(*chain)->buf, chunk_length, mem); if (rv != 0) { nghttp2_mem_free(mem, *chain); return NGHTTP2_ERR_NOMEM; } return 0; } static void buf_chain_del(nghttp2_buf_chain *chain, nghttp2_mem *mem) { nghttp2_buf_free(&chain->buf, mem); nghttp2_mem_free(mem, chain); } int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, nghttp2_mem *mem) { return nghttp2_bufs_init2(bufs, chunk_length, max_chunk, 0, mem); } int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, size_t offset, nghttp2_mem *mem) { return nghttp2_bufs_init3(bufs, chunk_length, max_chunk, max_chunk, offset, mem); } int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, size_t chunk_keep, size_t offset, nghttp2_mem *mem) { int rv; nghttp2_buf_chain *chain; if (chunk_keep == 0 || max_chunk < chunk_keep || chunk_length < offset) { return NGHTTP2_ERR_INVALID_ARGUMENT; } rv = buf_chain_new(&chain, chunk_length, mem); if (rv != 0) { return rv; } bufs->mem = mem; bufs->offset = offset; bufs->head = chain; bufs->cur = bufs->head; nghttp2_buf_shift_right(&bufs->cur->buf, offset); bufs->chunk_length = chunk_length; bufs->chunk_used = 1; bufs->max_chunk = max_chunk; bufs->chunk_keep = chunk_keep; return 0; } int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length) { int rv; nghttp2_buf_chain *chain; if (chunk_length < bufs->offset) { return NGHTTP2_ERR_INVALID_ARGUMENT; } rv = buf_chain_new(&chain, chunk_length, bufs->mem); if (rv != 0) { return rv; } nghttp2_bufs_free(bufs); bufs->head = chain; bufs->cur = bufs->head; nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); bufs->chunk_length = chunk_length; bufs->chunk_used = 1; return 0; } void nghttp2_bufs_free(nghttp2_bufs *bufs) { nghttp2_buf_chain *chain, *next_chain; if (bufs == NULL) { return; } for (chain = bufs->head; chain;) { next_chain = chain->next; buf_chain_del(chain, bufs->mem); chain = next_chain; } bufs->head = NULL; } int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len, nghttp2_mem *mem) { nghttp2_buf_chain *chain; chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); if (chain == NULL) { return NGHTTP2_ERR_NOMEM; } chain->next = NULL; nghttp2_buf_wrap_init(&chain->buf, begin, len); bufs->mem = mem; bufs->offset = 0; bufs->head = chain; bufs->cur = bufs->head; bufs->chunk_length = len; bufs->chunk_used = 1; bufs->max_chunk = 1; bufs->chunk_keep = 1; return 0; } int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec, size_t veclen, nghttp2_mem *mem) { size_t i = 0; nghttp2_buf_chain *cur_chain; nghttp2_buf_chain *head_chain; nghttp2_buf_chain **dst_chain = &head_chain; if (veclen == 0) { return nghttp2_bufs_wrap_init(bufs, NULL, 0, mem); } head_chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain) * veclen); if (head_chain == NULL) { return NGHTTP2_ERR_NOMEM; } for (i = 0; i < veclen; ++i) { cur_chain = &head_chain[i]; cur_chain->next = NULL; nghttp2_buf_wrap_init(&cur_chain->buf, vec[i].base, vec[i].len); *dst_chain = cur_chain; dst_chain = &cur_chain->next; } bufs->mem = mem; bufs->offset = 0; bufs->head = head_chain; bufs->cur = bufs->head; /* We don't use chunk_length since no allocation is expected. */ bufs->chunk_length = 0; bufs->chunk_used = veclen; bufs->max_chunk = veclen; bufs->chunk_keep = veclen; return 0; } void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) { if (bufs == NULL) { return; } if (bufs->head) { nghttp2_mem_free(bufs->mem, bufs->head); } } void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs) { nghttp2_buf_chain *ci; for (ci = bufs->cur; ci; ci = ci->next) { if (nghttp2_buf_len(&ci->buf) == 0) { return; } else { bufs->cur = ci; } } } size_t nghttp2_bufs_len(nghttp2_bufs *bufs) { nghttp2_buf_chain *ci; size_t len; len = 0; for (ci = bufs->head; ci; ci = ci->next) { len += nghttp2_buf_len(&ci->buf); } return len; } static int bufs_alloc_chain(nghttp2_bufs *bufs) { int rv; nghttp2_buf_chain *chain; if (bufs->cur->next) { bufs->cur = bufs->cur->next; return 0; } if (bufs->max_chunk == bufs->chunk_used) { return NGHTTP2_ERR_BUFFER_ERROR; } rv = buf_chain_new(&chain, bufs->chunk_length, bufs->mem); if (rv != 0) { return rv; } DEBUGF("new buffer %zu bytes allocated for bufs %p, used %zu\n", bufs->chunk_length, bufs, bufs->chunk_used); ++bufs->chunk_used; bufs->cur->next = chain; bufs->cur = chain; nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); return 0; } int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) { int rv; size_t nwrite; nghttp2_buf *buf; const uint8_t *p; p = data; while (len) { buf = &bufs->cur->buf; nwrite = nghttp2_min_size(nghttp2_buf_avail(buf), len); if (nwrite == 0) { rv = bufs_alloc_chain(bufs); if (rv != 0) { return rv; } continue; } buf->last = nghttp2_cpymem(buf->last, p, nwrite); p += nwrite; len -= nwrite; } return 0; } static int bufs_ensure_addb(nghttp2_bufs *bufs) { int rv; nghttp2_buf *buf; buf = &bufs->cur->buf; if (nghttp2_buf_avail(buf) > 0) { return 0; } rv = bufs_alloc_chain(bufs); if (rv != 0) { return rv; } return 0; } int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b) { int rv; rv = bufs_ensure_addb(bufs); if (rv != 0) { return rv; } *bufs->cur->buf.last++ = b; return 0; } int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b) { int rv; rv = bufs_ensure_addb(bufs); if (rv != 0) { return rv; } *bufs->cur->buf.last = b; return 0; } int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b) { int rv; rv = bufs_ensure_addb(bufs); if (rv != 0) { return rv; } *bufs->cur->buf.last++ |= b; return 0; } int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) { int rv; rv = bufs_ensure_addb(bufs); if (rv != 0) { return rv; } *bufs->cur->buf.last |= b; return 0; } nghttp2_ssize nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { size_t len; nghttp2_buf_chain *chain; nghttp2_buf *buf; uint8_t *res; nghttp2_buf resbuf; len = 0; for (chain = bufs->head; chain; chain = chain->next) { len += nghttp2_buf_len(&chain->buf); } if (len == 0) { res = NULL; return 0; } res = nghttp2_mem_malloc(bufs->mem, len); if (res == NULL) { return NGHTTP2_ERR_NOMEM; } nghttp2_buf_wrap_init(&resbuf, res, len); for (chain = bufs->head; chain; chain = chain->next) { buf = &chain->buf; resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); } *out = res; return (nghttp2_ssize)len; } size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) { size_t len; nghttp2_buf_chain *chain; nghttp2_buf *buf; nghttp2_buf resbuf; len = nghttp2_bufs_len(bufs); nghttp2_buf_wrap_init(&resbuf, out, len); for (chain = bufs->head; chain; chain = chain->next) { buf = &chain->buf; resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); } return len; } void nghttp2_bufs_reset(nghttp2_bufs *bufs) { nghttp2_buf_chain *chain, *ci; size_t k; k = bufs->chunk_keep; for (ci = bufs->head; ci; ci = ci->next) { nghttp2_buf_reset(&ci->buf); nghttp2_buf_shift_right(&ci->buf, bufs->offset); if (--k == 0) { break; } } if (ci) { chain = ci->next; ci->next = NULL; for (ci = chain; ci;) { chain = ci->next; buf_chain_del(ci, bufs->mem); ci = chain; } bufs->chunk_used = bufs->chunk_keep; } bufs->cur = bufs->head; } int nghttp2_bufs_advance(nghttp2_bufs *bufs) { return bufs_alloc_chain(bufs); } int nghttp2_bufs_next_present(nghttp2_bufs *bufs) { nghttp2_buf_chain *chain; chain = bufs->cur->next; return chain && nghttp2_buf_len(&chain->buf); } ================================================ FILE: thirdparty/nghttp2/nghttp2_buf.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_BUF_H #define NGHTTP2_BUF_H #include "nghttp2.h" #include "nghttp2_mem.h" typedef struct { /* This points to the beginning of the buffer. The effective range of buffer is [begin, end). */ uint8_t *begin; /* This points to the memory one byte beyond the end of the buffer. */ uint8_t *end; /* The position indicator for effective start of the buffer. pos <= last must be hold. */ uint8_t *pos; /* The position indicator for effective one beyond of the end of the buffer. last <= end must be hold. */ uint8_t *last; /* Mark arbitrary position in buffer [begin, end) */ uint8_t *mark; } nghttp2_buf; #define nghttp2_buf_len(BUF) ((size_t)((BUF)->last - (BUF)->pos)) #define nghttp2_buf_avail(BUF) ((size_t)((BUF)->end - (BUF)->last)) #define nghttp2_buf_mark_avail(BUF) ((size_t)((BUF)->mark - (BUF)->last)) #define nghttp2_buf_cap(BUF) ((size_t)((BUF)->end - (BUF)->begin)) #define nghttp2_buf_pos_offset(BUF) ((size_t)((BUF)->pos - (BUF)->begin)) #define nghttp2_buf_last_offset(BUF) ((size_t)((BUF)->last - (BUF)->begin)) #define nghttp2_buf_shift_right(BUF, AMT) \ do { \ (BUF)->pos += AMT; \ (BUF)->last += AMT; \ } while (0) #define nghttp2_buf_shift_left(BUF, AMT) \ do { \ (BUF)->pos -= AMT; \ (BUF)->last -= AMT; \ } while (0) /* * Initializes the |buf|. No memory is allocated in this function. Use * nghttp2_buf_reserve() to allocate memory. */ void nghttp2_buf_init(nghttp2_buf *buf); /* * Initializes the |buf| and allocates at least |initial| bytes of * memory. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory */ int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem); /* * Frees buffer in |buf|. */ void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem); /* * Extends buffer so that nghttp2_buf_cap() returns at least * |new_cap|. If extensions took place, buffer pointers in |buf| will * change. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory */ int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem); /* * Resets pos, last, mark member of |buf| to buf->begin. */ void nghttp2_buf_reset(nghttp2_buf *buf); /* * Initializes |buf| using supplied buffer |begin| of length * |len|. Semantically, the application should not call *_reserve() or * nghttp2_free() functions for |buf|. */ void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len); struct nghttp2_buf_chain; typedef struct nghttp2_buf_chain nghttp2_buf_chain; /* Chains 2 buffers */ struct nghttp2_buf_chain { /* Points to the subsequent buffer. NULL if there is no such buffer. */ nghttp2_buf_chain *next; nghttp2_buf buf; }; typedef struct { /* Points to the first buffer */ nghttp2_buf_chain *head; /* Buffer pointer where write occurs. */ nghttp2_buf_chain *cur; /* Memory allocator */ nghttp2_mem *mem; /* The buffer capacity of each buf. This field may be 0 if nghttp2_bufs is initialized by nghttp2_bufs_wrap_init* family functions. */ size_t chunk_length; /* The maximum number of nghttp2_buf_chain */ size_t max_chunk; /* The number of nghttp2_buf_chain allocated */ size_t chunk_used; /* The number of nghttp2_buf_chain to keep on reset */ size_t chunk_keep; /* pos offset from begin in each buffers. On initialization and reset, buf->pos and buf->last are positioned at buf->begin + offset. */ size_t offset; } nghttp2_bufs; /* * This is the same as calling nghttp2_bufs_init2 with the given * arguments and offset = 0. */ int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, nghttp2_mem *mem); /* * This is the same as calling nghttp2_bufs_init3 with the given * arguments and chunk_keep = max_chunk. */ int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, size_t offset, nghttp2_mem *mem); /* * Initializes |bufs|. Each buffer size is given in the * |chunk_length|. The maximum number of buffers is given in the * |max_chunk|. On reset, first |chunk_keep| buffers are kept and * remaining buffers are deleted. Each buffer will have bufs->pos and * bufs->last shifted to left by |offset| bytes on creation and reset. * * This function allocates first buffer. bufs->head and bufs->cur * will point to the first buffer after this call. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_INVALID_ARGUMENT * chunk_keep is 0; or max_chunk < chunk_keep; or offset is too * long. */ int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, size_t chunk_keep, size_t offset, nghttp2_mem *mem); /* * Frees any related resources to the |bufs|. */ void nghttp2_bufs_free(nghttp2_bufs *bufs); /* * Initializes |bufs| using supplied buffer |begin| of length |len|. * The first buffer bufs->head uses buffer |begin|. The buffer size * is fixed and no extra chunk buffer is allocated. In other * words, max_chunk = chunk_keep = 1. To free the resource allocated * for |bufs|, use nghttp2_bufs_wrap_free(). * * Don't use the function which performs allocation, such as * nghttp2_bufs_realloc(). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. */ int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len, nghttp2_mem *mem); /* * Initializes |bufs| using supplied |veclen| size of buf vector * |vec|. The number of buffers is fixed and no extra chunk buffer is * allocated. In other words, max_chunk = chunk_keep = |in_len|. To * free the resource allocated for |bufs|, use * nghttp2_bufs_wrap_free(). * * Don't use the function which performs allocation, such as * nghttp2_bufs_realloc(). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. */ int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec, size_t veclen, nghttp2_mem *mem); /* * Frees any related resource to the |bufs|. This function does not * free supplied buffer provided in nghttp2_bufs_wrap_init(). */ void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs); /* * Reallocates internal buffer using |chunk_length|. The max_chunk, * chunk_keep and offset do not change. After successful allocation * of new buffer, previous buffers are deallocated without copying * anything into new buffers. chunk_used is reset to 1. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_INVALID_ARGUMENT * chunk_length < offset */ int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length); /* * Appends the |data| of length |len| to the |bufs|. The write starts * at bufs->cur->buf.last. A new buffers will be allocated to store * all data. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len); /* * Appends a single byte |b| to the |bufs|. The write starts at * bufs->cur->buf.last. A new buffers will be allocated to store all * data. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b); /* * Behaves like nghttp2_bufs_addb(), but this does not update * buf->last pointer. */ int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b); #define nghttp2_bufs_fast_addb(BUFS, B) \ do { \ *(BUFS)->cur->buf.last++ = B; \ } while (0) #define nghttp2_bufs_fast_addb_hold(BUFS, B) \ do { \ *(BUFS)->cur->buf.last = B; \ } while (0) /* * Performs bitwise-OR of |b| at bufs->cur->buf.last. A new buffers * will be allocated if necessary. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b); /* * Behaves like nghttp2_bufs_orb(), but does not update buf->last * pointer. */ int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b); #define nghttp2_bufs_fast_orb(BUFS, B) \ do { \ uint8_t **p = &(BUFS)->cur->buf.last; \ **p = (uint8_t)(**p | (B)); \ ++(*p); \ } while (0) #define nghttp2_bufs_fast_orb_hold(BUFS, B) \ do { \ uint8_t *p = (BUFS)->cur->buf.last; \ *p = (uint8_t)(*p | (B)); \ } while (0) /* * Copies all data stored in |bufs| to the contiguous buffer. This * function allocates the contiguous memory to store all data in * |bufs| and assigns it to |*out|. * * The contents of |bufs| is left unchanged. * * This function returns the length of copied data and assigns the * pointer to copied data to |*out| if it succeeds, or one of the * following negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory */ nghttp2_ssize nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out); /* * Copies all data stored in |bufs| to |out|. This function assumes * that the buffer space pointed by |out| has at least * nghttp2_bufs(bufs) bytes. * * The contents of |bufs| is left unchanged. * * This function returns the length of copied data. */ size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out); /* * Resets |bufs| and makes the buffers empty. */ void nghttp2_bufs_reset(nghttp2_bufs *bufs); /* * Moves bufs->cur to bufs->cur->next. If resulting bufs->cur is * NULL, this function allocates new buffers and bufs->cur points to * it. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_bufs_advance(nghttp2_bufs *bufs); /* Sets bufs->cur to bufs->head */ #define nghttp2_bufs_rewind(BUFS) \ do { \ (BUFS)->cur = (BUFS)->head; \ } while (0) /* * Move bufs->cur, from the current position, using next member, to * the last buf which has nghttp2_buf_len(buf) > 0 without seeing buf * which satisfies nghttp2_buf_len(buf) == 0. If * nghttp2_buf_len(&bufs->cur->buf) == 0 or bufs->cur->next is NULL, * bufs->cur is unchanged. */ void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs); /* * Returns nonzero if bufs->cur->next is not empty. */ int nghttp2_bufs_next_present(nghttp2_bufs *bufs); #define nghttp2_bufs_cur_avail(BUFS) nghttp2_buf_avail(&(BUFS)->cur->buf) /* * Returns the total buffer length of |bufs|. */ size_t nghttp2_bufs_len(nghttp2_bufs *bufs); #endif /* NGHTTP2_BUF_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2_hd.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_hd.h" #include #include #include /* Make scalar initialization form of nghttp2_hd_entry */ #define MAKE_STATIC_ENT(N, V, T, H) \ { \ {NULL, NULL, (uint8_t *)(N), sizeof((N)) - 1, -1}, \ {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \ {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \ T, \ H, \ } /* Generated by mkstatictbl.py */ /* 3rd parameter is nghttp2_token value for header field name. We use first enum value if same header names are repeated (e.g., :status). */ static const nghttp2_hd_static_entry static_table[] = { MAKE_STATIC_ENT(":authority", "", 0, 3153725150u), MAKE_STATIC_ENT(":method", "GET", 1, 695666056u), MAKE_STATIC_ENT(":method", "POST", 1, 695666056u), MAKE_STATIC_ENT(":path", "/", 3, 3292848686u), MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u), MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u), MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u), MAKE_STATIC_ENT(":status", "200", 7, 4000288983u), MAKE_STATIC_ENT(":status", "204", 7, 4000288983u), MAKE_STATIC_ENT(":status", "206", 7, 4000288983u), MAKE_STATIC_ENT(":status", "304", 7, 4000288983u), MAKE_STATIC_ENT(":status", "400", 7, 4000288983u), MAKE_STATIC_ENT(":status", "404", 7, 4000288983u), MAKE_STATIC_ENT(":status", "500", 7, 4000288983u), MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u), MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u), MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u), MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u), MAKE_STATIC_ENT("accept", "", 18, 136609321u), MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u), MAKE_STATIC_ENT("age", "", 20, 742476188u), MAKE_STATIC_ENT("allow", "", 21, 2930878514u), MAKE_STATIC_ENT("authorization", "", 22, 2436257726u), MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u), MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u), MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u), MAKE_STATIC_ENT("content-language", "", 26, 24973587u), MAKE_STATIC_ENT("content-length", "", 27, 1308181789u), MAKE_STATIC_ENT("content-location", "", 28, 2302364718u), MAKE_STATIC_ENT("content-range", "", 29, 3555523146u), MAKE_STATIC_ENT("content-type", "", 30, 4244048277u), MAKE_STATIC_ENT("cookie", "", 31, 2007449791u), MAKE_STATIC_ENT("date", "", 32, 3564297305u), MAKE_STATIC_ENT("etag", "", 33, 113792960u), MAKE_STATIC_ENT("expect", "", 34, 2530896728u), MAKE_STATIC_ENT("expires", "", 35, 1049544579u), MAKE_STATIC_ENT("from", "", 36, 2513272949u), MAKE_STATIC_ENT("host", "", 37, 2952701295u), MAKE_STATIC_ENT("if-match", "", 38, 3597694698u), MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u), MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u), MAKE_STATIC_ENT("if-range", "", 41, 2340978238u), MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u), MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u), MAKE_STATIC_ENT("link", "", 44, 232457833u), MAKE_STATIC_ENT("location", "", 45, 200649126u), MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u), MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u), MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u), MAKE_STATIC_ENT("range", "", 49, 4208725202u), MAKE_STATIC_ENT("referer", "", 50, 3969579366u), MAKE_STATIC_ENT("refresh", "", 51, 3572655668u), MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u), MAKE_STATIC_ENT("server", "", 53, 1085029842u), MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u), MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u), MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u), MAKE_STATIC_ENT("user-agent", "", 57, 606444526u), MAKE_STATIC_ENT("vary", "", 58, 1085005381u), MAKE_STATIC_ENT("via", "", 59, 1762798611u), MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u), }; static int memeq(const void *s1, const void *s2, size_t n) { return memcmp(s1, s2, n) == 0; } /* * This function was generated by genlibtokenlookup.py. Inspired by * h2o header lookup. https://github.com/h2o/h2o */ static int32_t lookup_token(const uint8_t *name, size_t namelen) { switch (namelen) { case 2: switch (name[1]) { case 'e': if (memeq("t", name, 1)) { return NGHTTP2_TOKEN_TE; } break; } break; case 3: switch (name[2]) { case 'a': if (memeq("vi", name, 2)) { return NGHTTP2_TOKEN_VIA; } break; case 'e': if (memeq("ag", name, 2)) { return NGHTTP2_TOKEN_AGE; } break; } break; case 4: switch (name[3]) { case 'e': if (memeq("dat", name, 3)) { return NGHTTP2_TOKEN_DATE; } break; case 'g': if (memeq("eta", name, 3)) { return NGHTTP2_TOKEN_ETAG; } break; case 'k': if (memeq("lin", name, 3)) { return NGHTTP2_TOKEN_LINK; } break; case 'm': if (memeq("fro", name, 3)) { return NGHTTP2_TOKEN_FROM; } break; case 't': if (memeq("hos", name, 3)) { return NGHTTP2_TOKEN_HOST; } break; case 'y': if (memeq("var", name, 3)) { return NGHTTP2_TOKEN_VARY; } break; } break; case 5: switch (name[4]) { case 'e': if (memeq("rang", name, 4)) { return NGHTTP2_TOKEN_RANGE; } break; case 'h': if (memeq(":pat", name, 4)) { return NGHTTP2_TOKEN__PATH; } break; case 'w': if (memeq("allo", name, 4)) { return NGHTTP2_TOKEN_ALLOW; } break; } break; case 6: switch (name[5]) { case 'e': if (memeq("cooki", name, 5)) { return NGHTTP2_TOKEN_COOKIE; } break; case 'r': if (memeq("serve", name, 5)) { return NGHTTP2_TOKEN_SERVER; } break; case 't': if (memeq("accep", name, 5)) { return NGHTTP2_TOKEN_ACCEPT; } if (memeq("expec", name, 5)) { return NGHTTP2_TOKEN_EXPECT; } break; } break; case 7: switch (name[6]) { case 'd': if (memeq(":metho", name, 6)) { return NGHTTP2_TOKEN__METHOD; } break; case 'e': if (memeq(":schem", name, 6)) { return NGHTTP2_TOKEN__SCHEME; } if (memeq("upgrad", name, 6)) { return NGHTTP2_TOKEN_UPGRADE; } break; case 'h': if (memeq("refres", name, 6)) { return NGHTTP2_TOKEN_REFRESH; } break; case 'r': if (memeq("refere", name, 6)) { return NGHTTP2_TOKEN_REFERER; } break; case 's': if (memeq(":statu", name, 6)) { return NGHTTP2_TOKEN__STATUS; } if (memeq("expire", name, 6)) { return NGHTTP2_TOKEN_EXPIRES; } break; } break; case 8: switch (name[7]) { case 'e': if (memeq("if-rang", name, 7)) { return NGHTTP2_TOKEN_IF_RANGE; } break; case 'h': if (memeq("if-matc", name, 7)) { return NGHTTP2_TOKEN_IF_MATCH; } break; case 'n': if (memeq("locatio", name, 7)) { return NGHTTP2_TOKEN_LOCATION; } break; case 'y': if (memeq("priorit", name, 7)) { return NGHTTP2_TOKEN_PRIORITY; } break; } break; case 9: switch (name[8]) { case 'l': if (memeq(":protoco", name, 8)) { return NGHTTP2_TOKEN__PROTOCOL; } break; } break; case 10: switch (name[9]) { case 'e': if (memeq("keep-aliv", name, 9)) { return NGHTTP2_TOKEN_KEEP_ALIVE; } if (memeq("set-cooki", name, 9)) { return NGHTTP2_TOKEN_SET_COOKIE; } break; case 'n': if (memeq("connectio", name, 9)) { return NGHTTP2_TOKEN_CONNECTION; } break; case 't': if (memeq("user-agen", name, 9)) { return NGHTTP2_TOKEN_USER_AGENT; } break; case 'y': if (memeq(":authorit", name, 9)) { return NGHTTP2_TOKEN__AUTHORITY; } break; } break; case 11: switch (name[10]) { case 'r': if (memeq("retry-afte", name, 10)) { return NGHTTP2_TOKEN_RETRY_AFTER; } break; } break; case 12: switch (name[11]) { case 'e': if (memeq("content-typ", name, 11)) { return NGHTTP2_TOKEN_CONTENT_TYPE; } break; case 's': if (memeq("max-forward", name, 11)) { return NGHTTP2_TOKEN_MAX_FORWARDS; } break; } break; case 13: switch (name[12]) { case 'd': if (memeq("last-modifie", name, 12)) { return NGHTTP2_TOKEN_LAST_MODIFIED; } break; case 'e': if (memeq("content-rang", name, 12)) { return NGHTTP2_TOKEN_CONTENT_RANGE; } break; case 'h': if (memeq("if-none-matc", name, 12)) { return NGHTTP2_TOKEN_IF_NONE_MATCH; } break; case 'l': if (memeq("cache-contro", name, 12)) { return NGHTTP2_TOKEN_CACHE_CONTROL; } break; case 'n': if (memeq("authorizatio", name, 12)) { return NGHTTP2_TOKEN_AUTHORIZATION; } break; case 's': if (memeq("accept-range", name, 12)) { return NGHTTP2_TOKEN_ACCEPT_RANGES; } break; } break; case 14: switch (name[13]) { case 'h': if (memeq("content-lengt", name, 13)) { return NGHTTP2_TOKEN_CONTENT_LENGTH; } break; case 't': if (memeq("accept-charse", name, 13)) { return NGHTTP2_TOKEN_ACCEPT_CHARSET; } break; } break; case 15: switch (name[14]) { case 'e': if (memeq("accept-languag", name, 14)) { return NGHTTP2_TOKEN_ACCEPT_LANGUAGE; } break; case 'g': if (memeq("accept-encodin", name, 14)) { return NGHTTP2_TOKEN_ACCEPT_ENCODING; } break; } break; case 16: switch (name[15]) { case 'e': if (memeq("content-languag", name, 15)) { return NGHTTP2_TOKEN_CONTENT_LANGUAGE; } if (memeq("www-authenticat", name, 15)) { return NGHTTP2_TOKEN_WWW_AUTHENTICATE; } break; case 'g': if (memeq("content-encodin", name, 15)) { return NGHTTP2_TOKEN_CONTENT_ENCODING; } break; case 'n': if (memeq("content-locatio", name, 15)) { return NGHTTP2_TOKEN_CONTENT_LOCATION; } if (memeq("proxy-connectio", name, 15)) { return NGHTTP2_TOKEN_PROXY_CONNECTION; } break; } break; case 17: switch (name[16]) { case 'e': if (memeq("if-modified-sinc", name, 16)) { return NGHTTP2_TOKEN_IF_MODIFIED_SINCE; } break; case 'g': if (memeq("transfer-encodin", name, 16)) { return NGHTTP2_TOKEN_TRANSFER_ENCODING; } break; } break; case 18: switch (name[17]) { case 'e': if (memeq("proxy-authenticat", name, 17)) { return NGHTTP2_TOKEN_PROXY_AUTHENTICATE; } break; } break; case 19: switch (name[18]) { case 'e': if (memeq("if-unmodified-sinc", name, 18)) { return NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE; } break; case 'n': if (memeq("content-dispositio", name, 18)) { return NGHTTP2_TOKEN_CONTENT_DISPOSITION; } if (memeq("proxy-authorizatio", name, 18)) { return NGHTTP2_TOKEN_PROXY_AUTHORIZATION; } break; } break; case 25: switch (name[24]) { case 'y': if (memeq("strict-transport-securit", name, 24)) { return NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY; } break; } break; case 27: switch (name[26]) { case 'n': if (memeq("access-control-allow-origi", name, 26)) { return NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN; } break; } break; } return -1; } void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv) { ent->nv = *nv; ent->cnv.name = nv->name->base; ent->cnv.namelen = nv->name->len; ent->cnv.value = nv->value->base; ent->cnv.valuelen = nv->value->len; ent->cnv.flags = nv->flags; ent->next = NULL; ent->hash = 0; nghttp2_rcbuf_incref(ent->nv.name); nghttp2_rcbuf_incref(ent->nv.value); } void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) { nghttp2_rcbuf_decref(ent->nv.value); nghttp2_rcbuf_decref(ent->nv.name); } static int name_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { return a->name->len == b->namelen && memeq(a->name->base, b->name, b->namelen); } static int value_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { return a->value->len == b->valuelen && memeq(a->value->base, b->value, b->valuelen); } static uint32_t name_hash(const nghttp2_nv *nv) { /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */ uint32_t h = 2166136261u; size_t i; for (i = 0; i < nv->namelen; ++i) { h ^= nv->name[i]; h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); } return h; } static void hd_map_init(nghttp2_hd_map *map) { memset(map, 0, sizeof(nghttp2_hd_map)); } static void hd_map_insert(nghttp2_hd_map *map, nghttp2_hd_entry *ent) { nghttp2_hd_entry **bucket; bucket = &map->table[ent->hash & (HD_MAP_SIZE - 1)]; if (*bucket == NULL) { *bucket = ent; return; } /* lower index is linked near the root */ ent->next = *bucket; *bucket = ent; } static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match, const nghttp2_nv *nv, int32_t token, uint32_t hash, int name_only) { nghttp2_hd_entry *p; nghttp2_hd_entry *res = NULL; *exact_match = 0; for (p = map->table[hash & (HD_MAP_SIZE - 1)]; p; p = p->next) { if (token != p->nv.token || (token == -1 && (hash != p->hash || !name_eq(&p->nv, nv)))) { continue; } if (!res) { res = p; if (name_only) { break; } } if (value_eq(&p->nv, nv)) { res = p; *exact_match = 1; break; } } return res; } static void hd_map_remove(nghttp2_hd_map *map, nghttp2_hd_entry *ent) { nghttp2_hd_entry **dst; dst = &map->table[ent->hash & (HD_MAP_SIZE - 1)]; for (; *dst; dst = &(*dst)->next) { if (*dst != ent) { continue; } *dst = ent->next; ent->next = NULL; return; } } static int hd_ringbuf_init(nghttp2_hd_ringbuf *ringbuf, size_t bufsize, nghttp2_mem *mem) { size_t size; for (size = 1; size < bufsize; size <<= 1) ; ringbuf->buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size); if (ringbuf->buffer == NULL) { return NGHTTP2_ERR_NOMEM; } ringbuf->mask = size - 1; ringbuf->first = 0; ringbuf->len = 0; return 0; } static nghttp2_hd_entry *hd_ringbuf_get(nghttp2_hd_ringbuf *ringbuf, size_t idx) { assert(idx < ringbuf->len); return ringbuf->buffer[(ringbuf->first + idx) & ringbuf->mask]; } static int hd_ringbuf_reserve(nghttp2_hd_ringbuf *ringbuf, size_t bufsize, nghttp2_mem *mem) { size_t i; size_t size; nghttp2_hd_entry **buffer; if (ringbuf->mask + 1 >= bufsize) { return 0; } for (size = 1; size < bufsize; size <<= 1) ; buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size); if (buffer == NULL) { return NGHTTP2_ERR_NOMEM; } for (i = 0; i < ringbuf->len; ++i) { buffer[i] = hd_ringbuf_get(ringbuf, i); } nghttp2_mem_free(mem, ringbuf->buffer); ringbuf->buffer = buffer; ringbuf->mask = size - 1; ringbuf->first = 0; return 0; } static void hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf, nghttp2_mem *mem) { size_t i; if (ringbuf == NULL) { return; } for (i = 0; i < ringbuf->len; ++i) { nghttp2_hd_entry *ent = hd_ringbuf_get(ringbuf, i); nghttp2_hd_entry_free(ent); nghttp2_mem_free(mem, ent); } nghttp2_mem_free(mem, ringbuf->buffer); } static int hd_ringbuf_push_front(nghttp2_hd_ringbuf *ringbuf, nghttp2_hd_entry *ent, nghttp2_mem *mem) { int rv; rv = hd_ringbuf_reserve(ringbuf, ringbuf->len + 1, mem); if (rv != 0) { return rv; } ringbuf->buffer[--ringbuf->first & ringbuf->mask] = ent; ++ringbuf->len; return 0; } static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) { assert(ringbuf->len > 0); --ringbuf->len; } static int hd_context_init(nghttp2_hd_context *context, nghttp2_mem *mem) { int rv; context->mem = mem; context->bad = 0; context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; rv = hd_ringbuf_init( &context->hd_table, context->hd_table_bufsize_max / NGHTTP2_HD_ENTRY_OVERHEAD, mem); if (rv != 0) { return rv; } context->hd_table_bufsize = 0; context->next_seq = 0; return 0; } static void hd_context_free(nghttp2_hd_context *context) { hd_ringbuf_free(&context->hd_table, context->mem); } int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) { return nghttp2_hd_deflate_init2( deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem); } int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, size_t max_deflate_dynamic_table_size, nghttp2_mem *mem) { int rv; rv = hd_context_init(&deflater->ctx, mem); if (rv != 0) { return rv; } hd_map_init(&deflater->map); if (max_deflate_dynamic_table_size < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { deflater->notify_table_size_change = 1; deflater->ctx.hd_table_bufsize_max = max_deflate_dynamic_table_size; } else { deflater->notify_table_size_change = 0; } deflater->deflate_hd_table_bufsize_max = max_deflate_dynamic_table_size; deflater->min_hd_table_bufsize_max = UINT32_MAX; return 0; } int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) { int rv; rv = hd_context_init(&inflater->ctx, mem); if (rv != 0) { goto fail; } inflater->settings_hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; inflater->min_hd_table_bufsize_max = UINT32_MAX; inflater->nv_name_keep = NULL; inflater->nv_value_keep = NULL; inflater->opcode = NGHTTP2_HD_OPCODE_NONE; inflater->state = NGHTTP2_HD_STATE_INFLATE_START; nghttp2_buf_init(&inflater->namebuf); nghttp2_buf_init(&inflater->valuebuf); inflater->namercbuf = NULL; inflater->valuercbuf = NULL; inflater->huffman_encoded = 0; inflater->index = 0; inflater->left = 0; inflater->shift = 0; inflater->index_required = 0; inflater->no_index = 0; return 0; fail: return rv; } static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) { nghttp2_rcbuf_decref(inflater->nv_value_keep); nghttp2_rcbuf_decref(inflater->nv_name_keep); inflater->nv_value_keep = NULL; inflater->nv_name_keep = NULL; } void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) { hd_context_free(&deflater->ctx); } void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) { hd_inflate_keep_free(inflater); nghttp2_rcbuf_decref(inflater->valuercbuf); nghttp2_rcbuf_decref(inflater->namercbuf); hd_context_free(&inflater->ctx); } static size_t entry_room(size_t namelen, size_t valuelen) { return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; } static void emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) { DEBUGF("inflatehd: header emission: %s: %s\n", nv->name->base, nv->value->base); /* ent->ref may be 0. This happens if the encoder emits literal block larger than header table capacity with indexing. */ *nv_out = *nv; } static size_t count_encoded_length(size_t n, size_t prefix) { size_t k = (size_t)((1 << prefix) - 1); size_t len = 0; if (n < k) { return 1; } n -= k; ++len; for (; n >= 128; n >>= 7, ++len) ; return len + 1; } static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) { size_t k = (size_t)((1 << prefix) - 1); uint8_t *begin = buf; *buf = (uint8_t)(*buf & ~k); if (n < k) { *buf = (uint8_t)(*buf | n); return 1; } *buf = (uint8_t)(*buf | k); ++buf; n -= k; for (; n >= 128; n >>= 7) { *buf++ = (uint8_t)((1 << 7) | (n & 0x7f)); } *buf++ = (uint8_t)n; return (size_t)(buf - begin); } /* * Decodes |prefix| prefixed integer stored from |in|. The |last| * represents the 1 beyond the last of the valid contiguous memory * region from |in|. The decoded integer must be less than or equal * to UINT32_MAX. * * If the |initial| is nonzero, it is used as a initial value, this * function assumes the |in| starts with intermediate data. * * An entire integer is decoded successfully, decoded, the |*fin| is * set to nonzero. * * This function stores the decoded integer in |*res| if it succeed, * including partial decoding (in this case, number of shift to make * in the next call will be stored in |*shift_ptr|) and returns number * of bytes processed, or returns -1, indicating decoding error. */ static nghttp2_ssize decode_length(uint32_t *res, size_t *shift_ptr, int *fin, uint32_t initial, size_t shift, const uint8_t *in, const uint8_t *last, size_t prefix) { uint32_t k = (uint8_t)((1 << prefix) - 1); uint32_t n = initial; const uint8_t *start = in; *shift_ptr = 0; *fin = 0; if (n == 0) { if ((*in & k) != k) { *res = (*in) & k; *fin = 1; return 1; } n = k; if (++in == last) { *res = n; return (nghttp2_ssize)(in - start); } } for (; in != last; ++in, shift += 7) { uint32_t add = *in & 0x7f; if (shift >= 32) { DEBUGF("inflate: shift exponent overflow\n"); return -1; } if ((UINT32_MAX >> shift) < add) { DEBUGF("inflate: integer overflow on shift\n"); return -1; } add <<= shift; if (UINT32_MAX - add < n) { DEBUGF("inflate: integer overflow on addition\n"); return -1; } n += add; if ((*in & (1 << 7)) == 0) { break; } } *shift_ptr = shift; if (in == last) { *res = n; return (nghttp2_ssize)(in - start); } *res = n; *fin = 1; return (nghttp2_ssize)(in + 1 - start); } static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) { int rv; uint8_t *bufp; size_t blocklen; uint8_t sb[16]; DEBUGF("deflatehd: emit table_size=%zu\n", table_size); blocklen = count_encoded_length(table_size, 5); if (sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; } bufp = sb; *bufp = 0x20u; encode_length(bufp, table_size, 5); rv = nghttp2_bufs_add(bufs, sb, blocklen); if (rv != 0) { return rv; } return 0; } static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx) { int rv; size_t blocklen; uint8_t sb[16]; uint8_t *bufp; blocklen = count_encoded_length(idx + 1, 7); DEBUGF("deflatehd: emit indexed index=%zu, %zu bytes\n", idx, blocklen); if (sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; } bufp = sb; *bufp = 0x80u; encode_length(bufp, idx + 1, 7); rv = nghttp2_bufs_add(bufs, sb, blocklen); if (rv != 0) { return rv; } return 0; } static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) { int rv; uint8_t sb[16]; uint8_t *bufp; size_t blocklen; size_t enclen; int huffman = 0; enclen = nghttp2_hd_huff_encode_count(str, len); if (enclen < len) { huffman = 1; } else { enclen = len; } blocklen = count_encoded_length(enclen, 7); DEBUGF("deflatehd: emit string str=%.*s, length=%zu, huffman=%d, " "encoded_length=%zu\n", (int)len, (const char *)str, len, huffman, enclen); if (sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; } bufp = sb; *bufp = huffman ? 1 << 7 : 0; encode_length(bufp, enclen, 7); rv = nghttp2_bufs_add(bufs, sb, blocklen); if (rv != 0) { return rv; } if (huffman) { rv = nghttp2_hd_huff_encode(bufs, str, len); } else { assert(enclen == len); rv = nghttp2_bufs_add(bufs, str, len); } return rv; } static uint8_t pack_first_byte(int indexing_mode) { switch (indexing_mode) { case NGHTTP2_HD_WITH_INDEXING: return 0x40u; case NGHTTP2_HD_WITHOUT_INDEXING: return 0; case NGHTTP2_HD_NEVER_INDEXING: return 0x10u; default: assert(0); } /* This is required to compile with android NDK r10d + --enable-werror */ return 0; } static int emit_indname_block(nghttp2_bufs *bufs, size_t idx, const nghttp2_nv *nv, int indexing_mode) { int rv; uint8_t *bufp; size_t blocklen; uint8_t sb[16]; size_t prefixlen; if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { prefixlen = 6; } else { prefixlen = 4; } DEBUGF("deflatehd: emit indname index=%zu, valuelen=%zu, indexing_mode=%d\n", idx, nv->valuelen, indexing_mode); blocklen = count_encoded_length(idx + 1, prefixlen); if (sizeof(sb) < blocklen) { return NGHTTP2_ERR_HEADER_COMP; } bufp = sb; *bufp = pack_first_byte(indexing_mode); encode_length(bufp, idx + 1, prefixlen); rv = nghttp2_bufs_add(bufs, sb, blocklen); if (rv != 0) { return rv; } rv = emit_string(bufs, nv->value, nv->valuelen); if (rv != 0) { return rv; } return 0; } static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, int indexing_mode) { int rv; DEBUGF( "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n", nv->namelen, nv->valuelen, indexing_mode); rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode)); if (rv != 0) { return rv; } rv = emit_string(bufs, nv->name, nv->namelen); if (rv != 0) { return rv; } rv = emit_string(bufs, nv->value, nv->valuelen); if (rv != 0) { return rv; } return 0; } static int add_hd_table_incremental(nghttp2_hd_context *context, nghttp2_hd_nv *nv, nghttp2_hd_map *map, uint32_t hash) { int rv; nghttp2_hd_entry *new_ent; size_t room; nghttp2_mem *mem; mem = context->mem; room = entry_room(nv->name->len, nv->value->len); while (context->hd_table_bufsize + room > context->hd_table_bufsize_max && context->hd_table.len > 0) { size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); context->hd_table_bufsize -= entry_room(ent->nv.name->len, ent->nv.value->len); DEBUGF("hpack: remove item from header table: %s: %s\n", (char *)ent->nv.name->base, (char *)ent->nv.value->base); hd_ringbuf_pop_back(&context->hd_table); if (map) { hd_map_remove(map, ent); } nghttp2_hd_entry_free(ent); nghttp2_mem_free(mem, ent); } if (room > context->hd_table_bufsize_max) { /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is immediately evicted. So we don't allocate memory for it. */ return 0; } new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry)); if (new_ent == NULL) { return NGHTTP2_ERR_NOMEM; } nghttp2_hd_entry_init(new_ent, nv); rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem); if (rv != 0) { nghttp2_hd_entry_free(new_ent); nghttp2_mem_free(mem, new_ent); return rv; } new_ent->seq = context->next_seq++; new_ent->hash = hash; if (map) { hd_map_insert(map, new_ent); } context->hd_table_bufsize += room; return 0; } typedef struct { nghttp2_ssize index; /* Nonzero if both name and value are matched. */ int name_value_match; } search_result; static search_result search_static_table(const nghttp2_nv *nv, int32_t token, int name_only) { search_result res = {token, 0}; int i; const nghttp2_hd_static_entry *ent; if (name_only) { return res; } for (i = token; i <= NGHTTP2_TOKEN_WWW_AUTHENTICATE && static_table[i].token == token; ++i) { ent = &static_table[i]; if (ent->value.len == nv->valuelen && memcmp(ent->value.base, nv->value, nv->valuelen) == 0) { res.index = i; res.name_value_match = 1; return res; } } return res; } static search_result search_hd_table(nghttp2_hd_context *context, const nghttp2_nv *nv, int32_t token, int indexing_mode, nghttp2_hd_map *map, uint32_t hash) { search_result res = {-1, 0}; const nghttp2_hd_entry *ent; int exact_match; int name_only = indexing_mode == NGHTTP2_HD_NEVER_INDEXING; exact_match = 0; ent = hd_map_find(map, &exact_match, nv, token, hash, name_only); if (!exact_match && token >= 0 && token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) { return search_static_table(nv, token, name_only); } if (ent == NULL) { return res; } res.index = (nghttp2_ssize)(context->next_seq - 1 - ent->seq + NGHTTP2_STATIC_TABLE_LENGTH); res.name_value_match = exact_match; return res; } static void hd_context_shrink_table_size(nghttp2_hd_context *context, nghttp2_hd_map *map) { nghttp2_mem *mem; mem = context->mem; while (context->hd_table_bufsize > context->hd_table_bufsize_max && context->hd_table.len > 0) { size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); context->hd_table_bufsize -= entry_room(ent->nv.name->len, ent->nv.value->len); hd_ringbuf_pop_back(&context->hd_table); if (map) { hd_map_remove(map, ent); } nghttp2_hd_entry_free(ent); nghttp2_mem_free(mem, ent); } } int nghttp2_hd_deflate_change_table_size( nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) { size_t next_bufsize = nghttp2_min_size( settings_max_dynamic_table_size, deflater->deflate_hd_table_bufsize_max); deflater->ctx.hd_table_bufsize_max = next_bufsize; deflater->min_hd_table_bufsize_max = nghttp2_min_size(deflater->min_hd_table_bufsize_max, next_bufsize); deflater->notify_table_size_change = 1; hd_context_shrink_table_size(&deflater->ctx, &deflater->map); return 0; } int nghttp2_hd_inflate_change_table_size( nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) { switch (inflater->state) { case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: case NGHTTP2_HD_STATE_INFLATE_START: break; default: return NGHTTP2_ERR_INVALID_STATE; } inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size; /* It seems that encoder is not required to send dynamic table size update if the table size is not changed after applying SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this is the intention of the editor. If new maximum table size is strictly smaller than the current negotiated maximum size, encoder must send dynamic table size update. In other cases, we cannot expect it to do so. */ if (inflater->ctx.hd_table_bufsize_max > settings_max_dynamic_table_size) { inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE; /* Remember minimum value, and validate that encoder sends the value less than or equal to this. */ inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size; inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; hd_context_shrink_table_size(&inflater->ctx, NULL); } return 0; } #define INDEX_RANGE_VALID(context, idx) \ ((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH) static size_t get_max_index(nghttp2_hd_context *context) { return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH; } nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) { assert(INDEX_RANGE_VALID(context, idx)); if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) ->nv; } else { const nghttp2_hd_static_entry *ent = &static_table[idx]; nghttp2_hd_nv nv = {(nghttp2_rcbuf *)&ent->name, (nghttp2_rcbuf *)&ent->value, ent->token, NGHTTP2_NV_FLAG_NONE}; return nv; } } static const nghttp2_nv *nghttp2_hd_table_get2(nghttp2_hd_context *context, size_t idx) { assert(INDEX_RANGE_VALID(context, idx)); if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { return &hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) ->cnv; } return &static_table[idx].cnv; } static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater, const nghttp2_nv *nv, int32_t token) { if (token == NGHTTP2_TOKEN__PATH || token == NGHTTP2_TOKEN_AGE || token == NGHTTP2_TOKEN_CONTENT_LENGTH || token == NGHTTP2_TOKEN_ETAG || token == NGHTTP2_TOKEN_IF_MODIFIED_SINCE || token == NGHTTP2_TOKEN_IF_NONE_MATCH || token == NGHTTP2_TOKEN_LOCATION || token == NGHTTP2_TOKEN_SET_COOKIE || entry_room(nv->namelen, nv->valuelen) > deflater->ctx.hd_table_bufsize_max * 3 / 4) { return NGHTTP2_HD_WITHOUT_INDEXING; } return NGHTTP2_HD_WITH_INDEXING; } static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, const nghttp2_nv *nv) { int rv; search_result res; nghttp2_ssize idx; int indexing_mode; int32_t token; nghttp2_mem *mem; uint32_t hash = 0; DEBUGF("deflatehd: deflating %.*s: %.*s\n", (int)nv->namelen, nv->name, (int)nv->valuelen, nv->value); mem = deflater->ctx.mem; token = lookup_token(nv->name, nv->namelen); if (token == -1) { hash = name_hash(nv); } else if (token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) { hash = static_table[token].hash; } /* Don't index authorization header field since it may contain low entropy secret data (e.g., id/password). Also cookie header field with less than 20 bytes value is also never indexed. This is the same criteria used in Firefox codebase. */ indexing_mode = token == NGHTTP2_TOKEN_AUTHORIZATION || (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) || (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) ? NGHTTP2_HD_NEVER_INDEXING : hd_deflate_decide_indexing(deflater, nv, token); res = search_hd_table(&deflater->ctx, nv, token, indexing_mode, &deflater->map, hash); idx = res.index; if (res.name_value_match) { DEBUGF("deflatehd: name/value match index=%td\n", idx); rv = emit_indexed_block(bufs, (size_t)idx); if (rv != 0) { return rv; } return 0; } if (res.index != -1) { DEBUGF("deflatehd: name match index=%td\n", res.index); } if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { nghttp2_hd_nv hd_nv; if (idx != -1) { hd_nv.name = nghttp2_hd_table_get(&deflater->ctx, (size_t)idx).name; nghttp2_rcbuf_incref(hd_nv.name); } else { rv = nghttp2_rcbuf_new2(&hd_nv.name, nv->name, nv->namelen, mem); if (rv != 0) { return rv; } } rv = nghttp2_rcbuf_new2(&hd_nv.value, nv->value, nv->valuelen, mem); if (rv != 0) { nghttp2_rcbuf_decref(hd_nv.name); return rv; } hd_nv.token = token; hd_nv.flags = NGHTTP2_NV_FLAG_NONE; rv = add_hd_table_incremental(&deflater->ctx, &hd_nv, &deflater->map, hash); nghttp2_rcbuf_decref(hd_nv.value); nghttp2_rcbuf_decref(hd_nv.name); if (rv != 0) { return NGHTTP2_ERR_HEADER_COMP; } } if (idx == -1) { rv = emit_newname_block(bufs, nv, indexing_mode); } else { rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode); } if (rv != 0) { return rv; } return 0; } int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, const nghttp2_nv *nv, size_t nvlen) { size_t i; int rv = 0; if (deflater->ctx.bad) { return NGHTTP2_ERR_HEADER_COMP; } if (deflater->notify_table_size_change) { size_t min_hd_table_bufsize_max; min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max; deflater->notify_table_size_change = 0; deflater->min_hd_table_bufsize_max = UINT32_MAX; if (deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) { rv = emit_table_size(bufs, min_hd_table_bufsize_max); if (rv != 0) { goto fail; } } rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max); if (rv != 0) { goto fail; } } for (i = 0; i < nvlen; ++i) { rv = deflate_nv(deflater, bufs, &nv[i]); if (rv != 0) { goto fail; } } DEBUGF("deflatehd: all input name/value pairs were deflated\n"); return 0; fail: DEBUGF("deflatehd: error return %d\n", rv); deflater->ctx.bad = 1; return rv; } ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nv, size_t nvlen) { return (ssize_t)nghttp2_hd_deflate_hd2(deflater, buf, buflen, nv, nvlen); } nghttp2_ssize nghttp2_hd_deflate_hd2(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nv, size_t nvlen) { nghttp2_bufs bufs; int rv; nghttp2_mem *mem; mem = deflater->ctx.mem; rv = nghttp2_bufs_wrap_init(&bufs, buf, buflen, mem); if (rv != 0) { return rv; } rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen); buflen = nghttp2_bufs_len(&bufs); nghttp2_bufs_wrap_free(&bufs); if (rv == NGHTTP2_ERR_BUFFER_ERROR) { return NGHTTP2_ERR_INSUFF_BUFSIZE; } if (rv != 0) { return rv; } return (nghttp2_ssize)buflen; } ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, const nghttp2_nv *nv, size_t nvlen) { return (ssize_t)nghttp2_hd_deflate_hd_vec2(deflater, vec, veclen, nv, nvlen); } nghttp2_ssize nghttp2_hd_deflate_hd_vec2(nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, const nghttp2_nv *nv, size_t nvlen) { nghttp2_bufs bufs; int rv; nghttp2_mem *mem; size_t buflen; mem = deflater->ctx.mem; rv = nghttp2_bufs_wrap_init2(&bufs, vec, veclen, mem); if (rv != 0) { return rv; } rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen); buflen = nghttp2_bufs_len(&bufs); nghttp2_bufs_wrap_free(&bufs); if (rv == NGHTTP2_ERR_BUFFER_ERROR) { return NGHTTP2_ERR_INSUFF_BUFSIZE; } if (rv != 0) { return rv; } return (nghttp2_ssize)buflen; } size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen) { size_t n = 0; size_t i; (void)deflater; /* Possible Maximum Header Table Size Change. Encoding (1u << 31) - 1 using 4 bit prefix requires 6 bytes. We may emit this at most twice. */ n += 12; /* Use Literal Header Field without indexing - New Name, since it is most space consuming format. Also we choose the less one between non-huffman and huffman, so using literal byte count is sufficient for upper bound. Encoding (1u << 31) - 1 using 7 bit prefix requires 6 bytes. We need 2 of this for |nvlen| header fields. */ n += 6 * 2 * nvlen; for (i = 0; i < nvlen; ++i) { n += nva[i].namelen + nva[i].valuelen; } return n; } int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, size_t deflate_hd_table_bufsize_max) { return nghttp2_hd_deflate_new2(deflater_ptr, deflate_hd_table_bufsize_max, NULL); } int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, size_t deflate_hd_table_bufsize_max, nghttp2_mem *mem) { int rv; nghttp2_hd_deflater *deflater; if (mem == NULL) { mem = nghttp2_mem_default(); } deflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_deflater)); if (deflater == NULL) { return NGHTTP2_ERR_NOMEM; } rv = nghttp2_hd_deflate_init2(deflater, deflate_hd_table_bufsize_max, mem); if (rv != 0) { nghttp2_mem_free(mem, deflater); return rv; } *deflater_ptr = deflater; return 0; } void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) { nghttp2_mem *mem; mem = deflater->ctx.mem; nghttp2_hd_deflate_free(deflater); nghttp2_mem_free(mem, deflater); } static void hd_inflate_set_huffman_encoded(nghttp2_hd_inflater *inflater, const uint8_t *in) { inflater->huffman_encoded = (*in & (1 << 7)) != 0; } /* * Decodes the integer from the range [in, last). The result is * assigned to |inflater->left|. If the |inflater->left| is 0, then * it performs variable integer decoding from scratch. Otherwise, it * uses the |inflater->left| as the initial value and continues to * decode assuming that [in, last) begins with intermediary sequence. * * This function returns the number of bytes read if it succeeds, or * one of the following negative error codes: * * NGHTTP2_ERR_HEADER_COMP * Integer decoding failed */ static nghttp2_ssize hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin, const uint8_t *in, const uint8_t *last, size_t prefix, size_t maxlen) { nghttp2_ssize rv; uint32_t out; *rfin = 0; rv = decode_length(&out, &inflater->shift, rfin, (uint32_t)inflater->left, inflater->shift, in, last, prefix); if (rv == -1) { DEBUGF("inflatehd: integer decoding failed\n"); return NGHTTP2_ERR_HEADER_COMP; } if (out > maxlen) { DEBUGF("inflatehd: integer exceeded the maximum value %zu\n", maxlen); return NGHTTP2_ERR_HEADER_COMP; } inflater->left = out; DEBUGF("inflatehd: decoded integer is %u\n", out); return rv; } /* * Reads |inflater->left| bytes from the range [in, last) and performs * huffman decoding against them and pushes the result into the * |buffer|. * * This function returns the number of bytes read if it succeeds, or * one of the following negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory * NGHTTP2_ERR_HEADER_COMP * Huffman decoding failed */ static nghttp2_ssize hd_inflate_read_huff(nghttp2_hd_inflater *inflater, nghttp2_buf *buf, const uint8_t *in, const uint8_t *last) { nghttp2_ssize readlen; int fin = 0; if ((size_t)(last - in) >= inflater->left) { last = in + inflater->left; fin = 1; } readlen = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, buf, in, (size_t)(last - in), fin); if (readlen < 0) { DEBUGF("inflatehd: huffman decoding failed\n"); return readlen; } if (nghttp2_hd_huff_decode_failure_state(&inflater->huff_decode_ctx)) { DEBUGF("inflatehd: huffman decoding failed\n"); return NGHTTP2_ERR_HEADER_COMP; } inflater->left -= (size_t)readlen; return readlen; } /* * Reads |inflater->left| bytes from the range [in, last) and copies * them into the |buffer|. * * This function returns the number of bytes read if it succeeds, or * one of the following negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory * NGHTTP2_ERR_HEADER_COMP * Header decompression failed */ static nghttp2_ssize hd_inflate_read(nghttp2_hd_inflater *inflater, nghttp2_buf *buf, const uint8_t *in, const uint8_t *last) { size_t len = nghttp2_min_size((size_t)(last - in), inflater->left); buf->last = nghttp2_cpymem(buf->last, in, len); inflater->left -= len; return (nghttp2_ssize)len; } /* * Finalize indexed header representation reception. The referenced * header is always emitted, and |*nv_out| is filled with that value. */ static void hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater, nghttp2_hd_nv *nv_out) { nghttp2_hd_nv nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); emit_header(nv_out, &nv); } /* * Finalize literal header representation - new name- reception. If * header is emitted, |*nv_out| is filled with that value and 0 is * returned. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory */ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater, nghttp2_hd_nv *nv_out) { nghttp2_hd_nv nv; int rv; if (inflater->no_index) { nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; } else { nv.flags = NGHTTP2_NV_FLAG_NONE; } nv.name = inflater->namercbuf; nv.value = inflater->valuercbuf; nv.token = lookup_token(inflater->namercbuf->base, inflater->namercbuf->len); if (inflater->index_required) { rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); if (rv != 0) { return rv; } } emit_header(nv_out, &nv); inflater->nv_name_keep = nv.name; inflater->nv_value_keep = nv.value; inflater->namercbuf = NULL; inflater->valuercbuf = NULL; return 0; } /* * Finalize literal header representation - indexed name- * reception. If header is emitted, |*nv_out| is filled with that * value and 0 is returned. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory */ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, nghttp2_hd_nv *nv_out) { nghttp2_hd_nv nv; int rv; nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); if (inflater->no_index) { nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; } else { nv.flags = NGHTTP2_NV_FLAG_NONE; } nghttp2_rcbuf_incref(nv.name); nv.value = inflater->valuercbuf; if (inflater->index_required) { rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); if (rv != 0) { nghttp2_rcbuf_decref(nv.name); return NGHTTP2_ERR_NOMEM; } } emit_header(nv_out, &nv); inflater->nv_name_keep = nv.name; inflater->nv_value_keep = nv.value; inflater->valuercbuf = NULL; return 0; } ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, uint8_t *in, size_t inlen, int in_final) { return nghttp2_hd_inflate_hd2(inflater, nv_out, inflate_flags, in, inlen, in_final); } ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final) { return (nghttp2_ssize)nghttp2_hd_inflate_hd3(inflater, nv_out, inflate_flags, in, inlen, in_final); } nghttp2_ssize nghttp2_hd_inflate_hd3(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final) { nghttp2_ssize rv; nghttp2_hd_nv hd_nv; rv = nghttp2_hd_inflate_hd_nv(inflater, &hd_nv, inflate_flags, in, inlen, in_final); if (rv < 0) { return rv; } if (*inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { nv_out->name = hd_nv.name->base; nv_out->namelen = hd_nv.name->len; nv_out->value = hd_nv.value->base; nv_out->valuelen = hd_nv.value->len; nv_out->flags = hd_nv.flags; } return rv; } nghttp2_ssize nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, nghttp2_hd_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final) { nghttp2_ssize rv = 0; const uint8_t *first = in; const uint8_t *last = in + inlen; int rfin = 0; int busy = 0; nghttp2_mem *mem; mem = inflater->ctx.mem; if (inflater->ctx.bad) { return NGHTTP2_ERR_HEADER_COMP; } DEBUGF("inflatehd: start state=%d\n", inflater->state); hd_inflate_keep_free(inflater); *inflate_flags = NGHTTP2_HD_INFLATE_NONE; for (; in != last || busy;) { busy = 0; switch (inflater->state) { case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: if ((*in & 0xe0u) != 0x20u) { DEBUGF("inflatehd: header table size change was expected, but saw " "0x%02x as first byte", *in); rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } /* fall through */ case NGHTTP2_HD_STATE_INFLATE_START: case NGHTTP2_HD_STATE_OPCODE: if ((*in & 0xe0u) == 0x20u) { DEBUGF("inflatehd: header table size change\n"); if (inflater->state == NGHTTP2_HD_STATE_OPCODE) { DEBUGF("inflatehd: header table size change must appear at the head " "of header block\n"); rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE; } else if (*in & 0x80u) { DEBUGF("inflatehd: indexed repr\n"); inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; inflater->state = NGHTTP2_HD_STATE_READ_INDEX; } else { if (*in == 0x40u || *in == 0 || *in == 0x10u) { DEBUGF("inflatehd: literal header repr - new name\n"); inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME; inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN; } else { DEBUGF("inflatehd: literal header repr - indexed name\n"); inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME; inflater->state = NGHTTP2_HD_STATE_READ_INDEX; } inflater->index_required = (*in & 0x40) != 0; inflater->no_index = (*in & 0xf0u) == 0x10u; DEBUGF("inflatehd: indexing required=%d, no_index=%d\n", inflater->index_required, inflater->no_index); if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { ++in; } } inflater->left = 0; inflater->shift = 0; break; case NGHTTP2_HD_STATE_READ_TABLE_SIZE: rfin = 0; rv = hd_inflate_read_len( inflater, &rfin, in, last, 5, nghttp2_min_size(inflater->min_hd_table_bufsize_max, inflater->settings_hd_table_bufsize_max)); if (rv < 0) { goto fail; } in += rv; if (!rfin) { goto almost_ok; } DEBUGF("inflatehd: table_size=%zu\n", inflater->left); inflater->min_hd_table_bufsize_max = UINT32_MAX; inflater->ctx.hd_table_bufsize_max = inflater->left; hd_context_shrink_table_size(&inflater->ctx, NULL); inflater->state = NGHTTP2_HD_STATE_INFLATE_START; break; case NGHTTP2_HD_STATE_READ_INDEX: { size_t prefixlen; if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { prefixlen = 7; } else if (inflater->index_required) { prefixlen = 6; } else { prefixlen = 4; } rfin = 0; rv = hd_inflate_read_len(inflater, &rfin, in, last, prefixlen, get_max_index(&inflater->ctx)); if (rv < 0) { goto fail; } in += rv; if (!rfin) { goto almost_ok; } if (inflater->left == 0) { rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } DEBUGF("inflatehd: index=%zu\n", inflater->left); if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { inflater->index = inflater->left; --inflater->index; hd_inflate_commit_indexed(inflater, nv_out); inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; return (nghttp2_ssize)(in - first); } else { inflater->index = inflater->left; --inflater->index; inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; } break; } case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN: hd_inflate_set_huffman_encoded(inflater, in); inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN; inflater->left = 0; inflater->shift = 0; DEBUGF("inflatehd: huffman encoded=%d\n", inflater->huffman_encoded != 0); /* Fall through */ case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN: rfin = 0; rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV); if (rv < 0) { goto fail; } in += rv; if (!rfin) { DEBUGF("inflatehd: integer not fully decoded. current=%zu\n", inflater->left); goto almost_ok; } if (inflater->huffman_encoded) { nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx); inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF; rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1, mem); } else { inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME; rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left + 1, mem); } if (rv != 0) { goto fail; } nghttp2_buf_wrap_init(&inflater->namebuf, inflater->namercbuf->base, inflater->namercbuf->len); break; case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF: rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last); if (rv < 0) { goto fail; } in += rv; DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); goto almost_ok; } *inflater->namebuf.last = '\0'; inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; break; case NGHTTP2_HD_STATE_NEWNAME_READ_NAME: rv = hd_inflate_read(inflater, &inflater->namebuf, in, last); if (rv < 0) { goto fail; } in += rv; DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); goto almost_ok; } *inflater->namebuf.last = '\0'; inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; break; case NGHTTP2_HD_STATE_CHECK_VALUELEN: hd_inflate_set_huffman_encoded(inflater, in); inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN; inflater->left = 0; inflater->shift = 0; DEBUGF("inflatehd: huffman encoded=%d\n", inflater->huffman_encoded != 0); /* Fall through */ case NGHTTP2_HD_STATE_READ_VALUELEN: rfin = 0; rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV); if (rv < 0) { goto fail; } in += rv; if (!rfin) { goto almost_ok; } DEBUGF("inflatehd: valuelen=%zu\n", inflater->left); if (inflater->huffman_encoded) { nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx); inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF; rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1, mem); } else { inflater->state = NGHTTP2_HD_STATE_READ_VALUE; rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left + 1, mem); } if (rv != 0) { goto fail; } nghttp2_buf_wrap_init(&inflater->valuebuf, inflater->valuercbuf->base, inflater->valuercbuf->len); busy = 1; break; case NGHTTP2_HD_STATE_READ_VALUEHUFF: rv = hd_inflate_read_huff(inflater, &inflater->valuebuf, in, last); if (rv < 0) { goto fail; } in += rv; DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); goto almost_ok; } *inflater->valuebuf.last = '\0'; inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { rv = hd_inflate_commit_newname(inflater, nv_out); } else { rv = hd_inflate_commit_indname(inflater, nv_out); } if (rv != 0) { goto fail; } inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; return (nghttp2_ssize)(in - first); case NGHTTP2_HD_STATE_READ_VALUE: rv = hd_inflate_read(inflater, &inflater->valuebuf, in, last); if (rv < 0) { DEBUGF("inflatehd: value read failure %td: %s\n", rv, nghttp2_strerror((int)rv)); goto fail; } in += rv; DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); goto almost_ok; } *inflater->valuebuf.last = '\0'; inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { rv = hd_inflate_commit_newname(inflater, nv_out); } else { rv = hd_inflate_commit_indname(inflater, nv_out); } if (rv != 0) { goto fail; } inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; return (nghttp2_ssize)(in - first); } } assert(in == last); DEBUGF("inflatehd: all input bytes were processed\n"); if (in_final) { DEBUGF("inflatehd: in_final set\n"); if (inflater->state != NGHTTP2_HD_STATE_OPCODE && inflater->state != NGHTTP2_HD_STATE_INFLATE_START) { DEBUGF("inflatehd: unacceptable state=%d\n", inflater->state); rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL; } return (nghttp2_ssize)(in - first); almost_ok: if (in_final) { DEBUGF("inflatehd: input ended prematurely\n"); rv = NGHTTP2_ERR_HEADER_COMP; goto fail; } return (nghttp2_ssize)(in - first); fail: DEBUGF("inflatehd: error return %td\n", rv); inflater->ctx.bad = 1; return rv; } int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) { hd_inflate_keep_free(inflater); inflater->state = NGHTTP2_HD_STATE_INFLATE_START; return 0; } int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr) { return nghttp2_hd_inflate_new2(inflater_ptr, NULL); } int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr, nghttp2_mem *mem) { int rv; nghttp2_hd_inflater *inflater; if (mem == NULL) { mem = nghttp2_mem_default(); } inflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_inflater)); if (inflater == NULL) { return NGHTTP2_ERR_NOMEM; } rv = nghttp2_hd_inflate_init(inflater, mem); if (rv != 0) { nghttp2_mem_free(mem, inflater); return rv; } *inflater_ptr = inflater; return 0; } void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) { nghttp2_mem *mem; mem = inflater->ctx.mem; nghttp2_hd_inflate_free(inflater); nghttp2_mem_free(mem, inflater); } int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t idx, nghttp2_nv *nv, int indexing_mode) { return emit_indname_block(bufs, idx, nv, indexing_mode); } int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv, int indexing_mode) { return emit_newname_block(bufs, nv, indexing_mode); } int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size) { return emit_table_size(bufs, table_size); } nghttp2_ssize nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, uint32_t initial, size_t shift, uint8_t *in, uint8_t *last, size_t prefix) { return decode_length(res, shift_ptr, fin, initial, shift, in, last, prefix); } static const nghttp2_nv *hd_get_table_entry(nghttp2_hd_context *context, size_t idx) { if (idx == 0) { return NULL; } --idx; if (!INDEX_RANGE_VALID(context, idx)) { return NULL; } return nghttp2_hd_table_get2(context, idx); } size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) { return get_max_index(&deflater->ctx); } const nghttp2_nv * nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx) { return hd_get_table_entry(&deflater->ctx, idx); } size_t nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater) { return deflater->ctx.hd_table_bufsize; } size_t nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater) { return deflater->ctx.hd_table_bufsize_max; } size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater) { return get_max_index(&inflater->ctx); } const nghttp2_nv * nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx) { return hd_get_table_entry(&inflater->ctx, idx); } size_t nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater) { return inflater->ctx.hd_table_bufsize; } size_t nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater) { return inflater->ctx.hd_table_bufsize_max; } ================================================ FILE: thirdparty/nghttp2/nghttp2_hd.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_HD_H #define NGHTTP2_HD_H #include "nghttp2.h" #include "nghttp2_hd_huffman.h" #include "nghttp2_buf.h" #include "nghttp2_rcbuf.h" #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE #define NGHTTP2_HD_ENTRY_OVERHEAD 32 /* The maximum length of one name/value pair. This is the sum of the length of name and value. This is not specified by the spec. We just chose the arbitrary size */ #define NGHTTP2_HD_MAX_NV 65536 /* Default size of maximum table buffer size for encoder. Even if remote decoder notifies larger buffer size for its decoding, encoder only uses the memory up to this value. */ #define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12) /* Exported for unit test */ #define NGHTTP2_STATIC_TABLE_LENGTH 61 /* Generated by genlibtokenlookup.py */ typedef enum { NGHTTP2_TOKEN__AUTHORITY = 0, NGHTTP2_TOKEN__METHOD = 1, NGHTTP2_TOKEN__PATH = 3, NGHTTP2_TOKEN__SCHEME = 5, NGHTTP2_TOKEN__STATUS = 7, NGHTTP2_TOKEN_ACCEPT_CHARSET = 14, NGHTTP2_TOKEN_ACCEPT_ENCODING = 15, NGHTTP2_TOKEN_ACCEPT_LANGUAGE = 16, NGHTTP2_TOKEN_ACCEPT_RANGES = 17, NGHTTP2_TOKEN_ACCEPT = 18, NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN = 19, NGHTTP2_TOKEN_AGE = 20, NGHTTP2_TOKEN_ALLOW = 21, NGHTTP2_TOKEN_AUTHORIZATION = 22, NGHTTP2_TOKEN_CACHE_CONTROL = 23, NGHTTP2_TOKEN_CONTENT_DISPOSITION = 24, NGHTTP2_TOKEN_CONTENT_ENCODING = 25, NGHTTP2_TOKEN_CONTENT_LANGUAGE = 26, NGHTTP2_TOKEN_CONTENT_LENGTH = 27, NGHTTP2_TOKEN_CONTENT_LOCATION = 28, NGHTTP2_TOKEN_CONTENT_RANGE = 29, NGHTTP2_TOKEN_CONTENT_TYPE = 30, NGHTTP2_TOKEN_COOKIE = 31, NGHTTP2_TOKEN_DATE = 32, NGHTTP2_TOKEN_ETAG = 33, NGHTTP2_TOKEN_EXPECT = 34, NGHTTP2_TOKEN_EXPIRES = 35, NGHTTP2_TOKEN_FROM = 36, NGHTTP2_TOKEN_HOST = 37, NGHTTP2_TOKEN_IF_MATCH = 38, NGHTTP2_TOKEN_IF_MODIFIED_SINCE = 39, NGHTTP2_TOKEN_IF_NONE_MATCH = 40, NGHTTP2_TOKEN_IF_RANGE = 41, NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE = 42, NGHTTP2_TOKEN_LAST_MODIFIED = 43, NGHTTP2_TOKEN_LINK = 44, NGHTTP2_TOKEN_LOCATION = 45, NGHTTP2_TOKEN_MAX_FORWARDS = 46, NGHTTP2_TOKEN_PROXY_AUTHENTICATE = 47, NGHTTP2_TOKEN_PROXY_AUTHORIZATION = 48, NGHTTP2_TOKEN_RANGE = 49, NGHTTP2_TOKEN_REFERER = 50, NGHTTP2_TOKEN_REFRESH = 51, NGHTTP2_TOKEN_RETRY_AFTER = 52, NGHTTP2_TOKEN_SERVER = 53, NGHTTP2_TOKEN_SET_COOKIE = 54, NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY = 55, NGHTTP2_TOKEN_TRANSFER_ENCODING = 56, NGHTTP2_TOKEN_USER_AGENT = 57, NGHTTP2_TOKEN_VARY = 58, NGHTTP2_TOKEN_VIA = 59, NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60, NGHTTP2_TOKEN_TE, NGHTTP2_TOKEN_CONNECTION, NGHTTP2_TOKEN_KEEP_ALIVE, NGHTTP2_TOKEN_PROXY_CONNECTION, NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN__PROTOCOL, NGHTTP2_TOKEN_PRIORITY, } nghttp2_token; struct nghttp2_hd_entry; typedef struct nghttp2_hd_entry nghttp2_hd_entry; typedef struct { /* The buffer containing header field name. NULL-termination is guaranteed. */ nghttp2_rcbuf *name; /* The buffer containing header field value. NULL-termination is guaranteed. */ nghttp2_rcbuf *value; /* nghttp2_token value for name. It could be -1 if we have no token for that header field name. */ int32_t token; /* Bitwise OR of one or more of nghttp2_nv_flag. */ uint8_t flags; } nghttp2_hd_nv; struct nghttp2_hd_entry { /* The header field name/value pair */ nghttp2_hd_nv nv; /* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry APIs to keep backward compatibility. */ nghttp2_nv cnv; /* The next entry which shares same bucket in hash table. */ nghttp2_hd_entry *next; /* The sequence number. We will increment it by one whenever we store nghttp2_hd_entry to dynamic header table. */ uint32_t seq; /* The hash value for header name (nv.name). */ uint32_t hash; }; /* The entry used for static header table. */ typedef struct { nghttp2_rcbuf name; nghttp2_rcbuf value; nghttp2_nv cnv; int32_t token; uint32_t hash; } nghttp2_hd_static_entry; typedef struct { nghttp2_hd_entry **buffer; size_t mask; size_t first; size_t len; } nghttp2_hd_ringbuf; typedef enum { NGHTTP2_HD_OPCODE_NONE, NGHTTP2_HD_OPCODE_INDEXED, NGHTTP2_HD_OPCODE_NEWNAME, NGHTTP2_HD_OPCODE_INDNAME } nghttp2_hd_opcode; typedef enum { NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE, NGHTTP2_HD_STATE_INFLATE_START, NGHTTP2_HD_STATE_OPCODE, NGHTTP2_HD_STATE_READ_TABLE_SIZE, NGHTTP2_HD_STATE_READ_INDEX, NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN, NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN, NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF, NGHTTP2_HD_STATE_NEWNAME_READ_NAME, NGHTTP2_HD_STATE_CHECK_VALUELEN, NGHTTP2_HD_STATE_READ_VALUELEN, NGHTTP2_HD_STATE_READ_VALUEHUFF, NGHTTP2_HD_STATE_READ_VALUE } nghttp2_hd_inflate_state; typedef enum { NGHTTP2_HD_WITH_INDEXING, NGHTTP2_HD_WITHOUT_INDEXING, NGHTTP2_HD_NEVER_INDEXING } nghttp2_hd_indexing_mode; typedef struct { /* dynamic header table */ nghttp2_hd_ringbuf hd_table; /* Memory allocator */ nghttp2_mem *mem; /* Abstract buffer size of hd_table as described in the spec. This is the sum of length of name/value in hd_table + NGHTTP2_HD_ENTRY_OVERHEAD bytes overhead per each entry. */ size_t hd_table_bufsize; /* The effective header table size. */ size_t hd_table_bufsize_max; /* Next sequence number for nghttp2_hd_entry */ uint32_t next_seq; /* If inflate/deflate error occurred, this value is set to 1 and further invocation of inflate/deflate will fail with NGHTTP2_ERR_HEADER_COMP. */ uint8_t bad; } nghttp2_hd_context; #define HD_MAP_SIZE 128 typedef struct { nghttp2_hd_entry *table[HD_MAP_SIZE]; } nghttp2_hd_map; struct nghttp2_hd_deflater { nghttp2_hd_context ctx; nghttp2_hd_map map; /* The upper limit of the header table size the deflater accepts. */ size_t deflate_hd_table_bufsize_max; /* Minimum header table size notified in the next context update */ size_t min_hd_table_bufsize_max; /* If nonzero, send header table size using encoding context update in the next deflate process */ uint8_t notify_table_size_change; }; struct nghttp2_hd_inflater { nghttp2_hd_context ctx; /* Stores current state of huffman decoding */ nghttp2_hd_huff_decode_context huff_decode_ctx; /* header buffer */ nghttp2_buf namebuf, valuebuf; nghttp2_rcbuf *namercbuf, *valuercbuf; /* Pointer to the name/value pair which are used in the current header emission. */ nghttp2_rcbuf *nv_name_keep, *nv_value_keep; /* The number of bytes to read */ size_t left; /* The index in indexed repr or indexed name */ size_t index; /* The maximum header table size the inflater supports. This is the same value transmitted in SETTINGS_HEADER_TABLE_SIZE */ size_t settings_hd_table_bufsize_max; /* Minimum header table size set by nghttp2_hd_inflate_change_table_size */ size_t min_hd_table_bufsize_max; /* The number of next shift to decode integer */ size_t shift; nghttp2_hd_opcode opcode; nghttp2_hd_inflate_state state; /* nonzero if string is huffman encoded */ uint8_t huffman_encoded; /* nonzero if deflater requires that current entry is indexed */ uint8_t index_required; /* nonzero if deflater requires that current entry must not be indexed */ uint8_t no_index; }; /* * Initializes the |ent| members. The reference counts of nv->name * and nv->value are increased by one for each. */ void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv); /* * This function decreases the reference counts of nv->name and * nv->value. */ void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); /* * Initializes |deflater| for deflating name/values pairs. * * The encoder only uses up to * NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE bytes for header table * even if the larger value is specified later in * nghttp2_hd_change_table_size(). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. */ int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem); /* * Initializes |deflater| for deflating name/values pairs. * * The encoder only uses up to |max_deflate_dynamic_table_size| bytes * for header table even if the larger value is specified later in * nghttp2_hd_change_table_size(). * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. */ int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, size_t max_deflate_dynamic_table_size, nghttp2_mem *mem); /* * Deallocates any resources allocated for |deflater|. */ void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater); /* * Deflates the |nva|, which has the |nvlen| name/value pairs, into * the |bufs|. * * This function expands |bufs| as necessary to store the result. If * buffers is full and the process still requires more space, this * function fails and returns NGHTTP2_ERR_HEADER_COMP. * * After this function returns, it is safe to delete the |nva|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_HEADER_COMP * Deflation process has failed. * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, const nghttp2_nv *nva, size_t nvlen); /* * Initializes |inflater| for inflating name/values pairs. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. */ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem); /* * Deallocates any resources allocated for |inflater|. */ void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater); /* * Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv * instead of nghttp2_nv as output parameter |nv_out|. Other than * that return values and semantics are the same as * nghttp2_hd_inflate_hd(). */ nghttp2_ssize nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, nghttp2_hd_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final); /* For unittesting purpose */ int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index, nghttp2_nv *nv, int indexing_mode); /* For unittesting purpose */ int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv, int indexing_mode); /* For unittesting purpose */ int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size); /* For unittesting purpose */ nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index); /* For unittesting purpose */ nghttp2_ssize nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, uint32_t initial, size_t shift, uint8_t *in, uint8_t *last, size_t prefix); /* Huffman encoding/decoding functions */ /* * Counts the required bytes to encode |src| with length |len|. * * This function returns the number of required bytes to encode given * data, including padding of prefix of terminal symbol code. This * function always succeeds. */ size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len); /* * Encodes the given data |src| with length |srclen| to the |bufs|. * This function expands extra buffers in |bufs| if necessary. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_BUFFER_ERROR * Out of buffer space. */ int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src, size_t srclen); void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx); /* * Decodes the given data |src| with length |srclen|. The |ctx| must * be initialized by nghttp2_hd_huff_decode_context_init(). The result * will be written to |buf|. This function assumes that |buf| has the * enough room to store the decoded byte string. * * The caller must set the |fin| to nonzero if the given input is the * final block. * * This function returns the number of read bytes from the |in|. * * If this function fails, it returns one of the following negative * return codes: * * NGHTTP2_ERR_NOMEM * Out of memory. * NGHTTP2_ERR_HEADER_COMP * Decoding process has failed. */ nghttp2_ssize nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, nghttp2_buf *buf, const uint8_t *src, size_t srclen, int fin); /* * nghttp2_hd_huff_decode_failure_state returns nonzero if |ctx| * indicates that huffman decoding context is in failure state. */ int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx); #endif /* NGHTTP2_HD_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2_hd_huffman.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_hd_huffman.h" #include #include #include #include "nghttp2_hd.h" size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len) { size_t i; size_t nbits = 0; for (i = 0; i < len; ++i) { nbits += huff_sym_table[src[i]].nbits; } /* pad the prefix of EOS (256) */ return (nbits + 7) / 8; } int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src, size_t srclen) { const nghttp2_huff_sym *sym; const uint8_t *end = src + srclen; uint64_t code = 0; uint32_t x; size_t nbits = 0; size_t avail; int rv; avail = nghttp2_bufs_cur_avail(bufs); for (; src != end;) { sym = &huff_sym_table[*src++]; code |= (uint64_t)sym->code << (32 - nbits); nbits += sym->nbits; if (nbits < 32) { continue; } if (avail >= 4) { x = htonl((uint32_t)(code >> 32)); memcpy(bufs->cur->buf.last, &x, 4); bufs->cur->buf.last += 4; avail -= 4; code <<= 32; nbits -= 32; continue; } for (; nbits >= 8;) { rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56)); if (rv != 0) { return rv; } code <<= 8; nbits -= 8; } avail = nghttp2_bufs_cur_avail(bufs); } for (; nbits >= 8;) { rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56)); if (rv != 0) { return rv; } code <<= 8; nbits -= 8; } if (nbits) { rv = nghttp2_bufs_addb( bufs, (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1))); if (rv != 0) { return rv; } } return 0; } void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) { ctx->fstate = NGHTTP2_HUFF_ACCEPTED; } nghttp2_ssize nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, nghttp2_buf *buf, const uint8_t *src, size_t srclen, int final) { const uint8_t *end = src + srclen; nghttp2_huff_decode node = {ctx->fstate, 0}; const nghttp2_huff_decode *t = &node; uint8_t c; /* We use the decoding algorithm described in - http://graphics.ics.uci.edu/pub/Prefix.pdf [!!! NO LONGER VALID !!!] - https://ics.uci.edu/~dan/pubs/Prefix.pdf - https://github.com/nghttp2/nghttp2/files/15141264/Prefix.pdf */ for (; src != end;) { c = *src++; t = &huff_decode_table[t->fstate & 0x1ff][c >> 4]; if (t->fstate & NGHTTP2_HUFF_SYM) { *buf->last++ = t->sym; } t = &huff_decode_table[t->fstate & 0x1ff][c & 0xf]; if (t->fstate & NGHTTP2_HUFF_SYM) { *buf->last++ = t->sym; } } ctx->fstate = t->fstate; if (final && !(ctx->fstate & NGHTTP2_HUFF_ACCEPTED)) { return NGHTTP2_ERR_HEADER_COMP; } return (nghttp2_ssize)srclen; } int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx) { return ctx->fstate == 0x100; } ================================================ FILE: thirdparty/nghttp2/nghttp2_hd_huffman.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_HD_HUFFMAN_H #define NGHTTP2_HD_HUFFMAN_H #include #include typedef enum { /* FSA accepts this state as the end of huffman encoding sequence. */ NGHTTP2_HUFF_ACCEPTED = 1 << 14, /* This state emits symbol */ NGHTTP2_HUFF_SYM = 1 << 15, } nghttp2_huff_decode_flag; typedef struct { /* fstate is the current huffman decoding state, which is actually the node ID of internal huffman tree with nghttp2_huff_decode_flag OR-ed. We have 257 leaf nodes, but they are identical to root node other than emitting a symbol, so we have 256 internal nodes [1..255], inclusive. The node ID 256 is a special node and it is a terminal state that means decoding failed. */ uint16_t fstate; /* symbol if NGHTTP2_HUFF_SYM flag set */ uint8_t sym; } nghttp2_huff_decode; typedef nghttp2_huff_decode huff_decode_table_type[16]; typedef struct { /* fstate is the current huffman decoding state. */ uint16_t fstate; } nghttp2_hd_huff_decode_context; typedef struct { /* The number of bits in this code */ uint32_t nbits; /* Huffman code aligned to LSB */ uint32_t code; } nghttp2_huff_sym; extern const nghttp2_huff_sym huff_sym_table[]; extern const nghttp2_huff_decode huff_decode_table[][16]; #endif /* NGHTTP2_HD_HUFFMAN_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2_hd_huffman_data.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_hd_huffman.h" /* Generated by mkhufftbl.py */ const nghttp2_huff_sym huff_sym_table[] = { {13, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u}, {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u}, {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u}, {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u}, {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u}, {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u}, {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u}, {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u}, {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u}, {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u}, {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u}, {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u}, {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u}, {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u}, {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u}, {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u}, {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u}, {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u}, {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u}, {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u}, {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u}, {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u}, {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u}, {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u}, {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u}, {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u}, {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u}, {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u}, {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u}, {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u}, {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u}, {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u}, {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u}, {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u}, {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u}, {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u}, {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u}, {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u}, {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u}, {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u}, {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u}, {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u}, {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u}, {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u}, {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u}, {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u}, {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u}, {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u}, {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u}, {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u}, {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u}, {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u}, {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u}, {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u}, {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u}, {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u}, {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u}, {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u}, {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u}, {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u}, {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u}, {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u}, {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u}, {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u}, {30, 0xfffffffcu}}; const nghttp2_huff_decode huff_decode_table[][16] = { /* 0 */ { {0x04, 0}, {0x05, 0}, {0x07, 0}, {0x08, 0}, {0x0b, 0}, {0x0c, 0}, {0x10, 0}, {0x13, 0}, {0x19, 0}, {0x1c, 0}, {0x20, 0}, {0x23, 0}, {0x2a, 0}, {0x31, 0}, {0x39, 0}, {0x4040, 0}, }, /* 1 */ { {0xc000, 48}, {0xc000, 49}, {0xc000, 50}, {0xc000, 97}, {0xc000, 99}, {0xc000, 101}, {0xc000, 105}, {0xc000, 111}, {0xc000, 115}, {0xc000, 116}, {0x0d, 0}, {0x0e, 0}, {0x11, 0}, {0x12, 0}, {0x14, 0}, {0x15, 0}, }, /* 2 */ { {0x8001, 48}, {0xc016, 48}, {0x8001, 49}, {0xc016, 49}, {0x8001, 50}, {0xc016, 50}, {0x8001, 97}, {0xc016, 97}, {0x8001, 99}, {0xc016, 99}, {0x8001, 101}, {0xc016, 101}, {0x8001, 105}, {0xc016, 105}, {0x8001, 111}, {0xc016, 111}, }, /* 3 */ { {0x8002, 48}, {0x8009, 48}, {0x8017, 48}, {0xc028, 48}, {0x8002, 49}, {0x8009, 49}, {0x8017, 49}, {0xc028, 49}, {0x8002, 50}, {0x8009, 50}, {0x8017, 50}, {0xc028, 50}, {0x8002, 97}, {0x8009, 97}, {0x8017, 97}, {0xc028, 97}, }, /* 4 */ { {0x8003, 48}, {0x8006, 48}, {0x800a, 48}, {0x800f, 48}, {0x8018, 48}, {0x801f, 48}, {0x8029, 48}, {0xc038, 48}, {0x8003, 49}, {0x8006, 49}, {0x800a, 49}, {0x800f, 49}, {0x8018, 49}, {0x801f, 49}, {0x8029, 49}, {0xc038, 49}, }, /* 5 */ { {0x8003, 50}, {0x8006, 50}, {0x800a, 50}, {0x800f, 50}, {0x8018, 50}, {0x801f, 50}, {0x8029, 50}, {0xc038, 50}, {0x8003, 97}, {0x8006, 97}, {0x800a, 97}, {0x800f, 97}, {0x8018, 97}, {0x801f, 97}, {0x8029, 97}, {0xc038, 97}, }, /* 6 */ { {0x8002, 99}, {0x8009, 99}, {0x8017, 99}, {0xc028, 99}, {0x8002, 101}, {0x8009, 101}, {0x8017, 101}, {0xc028, 101}, {0x8002, 105}, {0x8009, 105}, {0x8017, 105}, {0xc028, 105}, {0x8002, 111}, {0x8009, 111}, {0x8017, 111}, {0xc028, 111}, }, /* 7 */ { {0x8003, 99}, {0x8006, 99}, {0x800a, 99}, {0x800f, 99}, {0x8018, 99}, {0x801f, 99}, {0x8029, 99}, {0xc038, 99}, {0x8003, 101}, {0x8006, 101}, {0x800a, 101}, {0x800f, 101}, {0x8018, 101}, {0x801f, 101}, {0x8029, 101}, {0xc038, 101}, }, /* 8 */ { {0x8003, 105}, {0x8006, 105}, {0x800a, 105}, {0x800f, 105}, {0x8018, 105}, {0x801f, 105}, {0x8029, 105}, {0xc038, 105}, {0x8003, 111}, {0x8006, 111}, {0x800a, 111}, {0x800f, 111}, {0x8018, 111}, {0x801f, 111}, {0x8029, 111}, {0xc038, 111}, }, /* 9 */ { {0x8001, 115}, {0xc016, 115}, {0x8001, 116}, {0xc016, 116}, {0xc000, 32}, {0xc000, 37}, {0xc000, 45}, {0xc000, 46}, {0xc000, 47}, {0xc000, 51}, {0xc000, 52}, {0xc000, 53}, {0xc000, 54}, {0xc000, 55}, {0xc000, 56}, {0xc000, 57}, }, /* 10 */ { {0x8002, 115}, {0x8009, 115}, {0x8017, 115}, {0xc028, 115}, {0x8002, 116}, {0x8009, 116}, {0x8017, 116}, {0xc028, 116}, {0x8001, 32}, {0xc016, 32}, {0x8001, 37}, {0xc016, 37}, {0x8001, 45}, {0xc016, 45}, {0x8001, 46}, {0xc016, 46}, }, /* 11 */ { {0x8003, 115}, {0x8006, 115}, {0x800a, 115}, {0x800f, 115}, {0x8018, 115}, {0x801f, 115}, {0x8029, 115}, {0xc038, 115}, {0x8003, 116}, {0x8006, 116}, {0x800a, 116}, {0x800f, 116}, {0x8018, 116}, {0x801f, 116}, {0x8029, 116}, {0xc038, 116}, }, /* 12 */ { {0x8002, 32}, {0x8009, 32}, {0x8017, 32}, {0xc028, 32}, {0x8002, 37}, {0x8009, 37}, {0x8017, 37}, {0xc028, 37}, {0x8002, 45}, {0x8009, 45}, {0x8017, 45}, {0xc028, 45}, {0x8002, 46}, {0x8009, 46}, {0x8017, 46}, {0xc028, 46}, }, /* 13 */ { {0x8003, 32}, {0x8006, 32}, {0x800a, 32}, {0x800f, 32}, {0x8018, 32}, {0x801f, 32}, {0x8029, 32}, {0xc038, 32}, {0x8003, 37}, {0x8006, 37}, {0x800a, 37}, {0x800f, 37}, {0x8018, 37}, {0x801f, 37}, {0x8029, 37}, {0xc038, 37}, }, /* 14 */ { {0x8003, 45}, {0x8006, 45}, {0x800a, 45}, {0x800f, 45}, {0x8018, 45}, {0x801f, 45}, {0x8029, 45}, {0xc038, 45}, {0x8003, 46}, {0x8006, 46}, {0x800a, 46}, {0x800f, 46}, {0x8018, 46}, {0x801f, 46}, {0x8029, 46}, {0xc038, 46}, }, /* 15 */ { {0x8001, 47}, {0xc016, 47}, {0x8001, 51}, {0xc016, 51}, {0x8001, 52}, {0xc016, 52}, {0x8001, 53}, {0xc016, 53}, {0x8001, 54}, {0xc016, 54}, {0x8001, 55}, {0xc016, 55}, {0x8001, 56}, {0xc016, 56}, {0x8001, 57}, {0xc016, 57}, }, /* 16 */ { {0x8002, 47}, {0x8009, 47}, {0x8017, 47}, {0xc028, 47}, {0x8002, 51}, {0x8009, 51}, {0x8017, 51}, {0xc028, 51}, {0x8002, 52}, {0x8009, 52}, {0x8017, 52}, {0xc028, 52}, {0x8002, 53}, {0x8009, 53}, {0x8017, 53}, {0xc028, 53}, }, /* 17 */ { {0x8003, 47}, {0x8006, 47}, {0x800a, 47}, {0x800f, 47}, {0x8018, 47}, {0x801f, 47}, {0x8029, 47}, {0xc038, 47}, {0x8003, 51}, {0x8006, 51}, {0x800a, 51}, {0x800f, 51}, {0x8018, 51}, {0x801f, 51}, {0x8029, 51}, {0xc038, 51}, }, /* 18 */ { {0x8003, 52}, {0x8006, 52}, {0x800a, 52}, {0x800f, 52}, {0x8018, 52}, {0x801f, 52}, {0x8029, 52}, {0xc038, 52}, {0x8003, 53}, {0x8006, 53}, {0x800a, 53}, {0x800f, 53}, {0x8018, 53}, {0x801f, 53}, {0x8029, 53}, {0xc038, 53}, }, /* 19 */ { {0x8002, 54}, {0x8009, 54}, {0x8017, 54}, {0xc028, 54}, {0x8002, 55}, {0x8009, 55}, {0x8017, 55}, {0xc028, 55}, {0x8002, 56}, {0x8009, 56}, {0x8017, 56}, {0xc028, 56}, {0x8002, 57}, {0x8009, 57}, {0x8017, 57}, {0xc028, 57}, }, /* 20 */ { {0x8003, 54}, {0x8006, 54}, {0x800a, 54}, {0x800f, 54}, {0x8018, 54}, {0x801f, 54}, {0x8029, 54}, {0xc038, 54}, {0x8003, 55}, {0x8006, 55}, {0x800a, 55}, {0x800f, 55}, {0x8018, 55}, {0x801f, 55}, {0x8029, 55}, {0xc038, 55}, }, /* 21 */ { {0x8003, 56}, {0x8006, 56}, {0x800a, 56}, {0x800f, 56}, {0x8018, 56}, {0x801f, 56}, {0x8029, 56}, {0xc038, 56}, {0x8003, 57}, {0x8006, 57}, {0x800a, 57}, {0x800f, 57}, {0x8018, 57}, {0x801f, 57}, {0x8029, 57}, {0xc038, 57}, }, /* 22 */ { {0x1a, 0}, {0x1b, 0}, {0x1d, 0}, {0x1e, 0}, {0x21, 0}, {0x22, 0}, {0x24, 0}, {0x25, 0}, {0x2b, 0}, {0x2e, 0}, {0x32, 0}, {0x35, 0}, {0x3a, 0}, {0x3d, 0}, {0x41, 0}, {0x4044, 0}, }, /* 23 */ { {0xc000, 61}, {0xc000, 65}, {0xc000, 95}, {0xc000, 98}, {0xc000, 100}, {0xc000, 102}, {0xc000, 103}, {0xc000, 104}, {0xc000, 108}, {0xc000, 109}, {0xc000, 110}, {0xc000, 112}, {0xc000, 114}, {0xc000, 117}, {0x26, 0}, {0x27, 0}, }, /* 24 */ { {0x8001, 61}, {0xc016, 61}, {0x8001, 65}, {0xc016, 65}, {0x8001, 95}, {0xc016, 95}, {0x8001, 98}, {0xc016, 98}, {0x8001, 100}, {0xc016, 100}, {0x8001, 102}, {0xc016, 102}, {0x8001, 103}, {0xc016, 103}, {0x8001, 104}, {0xc016, 104}, }, /* 25 */ { {0x8002, 61}, {0x8009, 61}, {0x8017, 61}, {0xc028, 61}, {0x8002, 65}, {0x8009, 65}, {0x8017, 65}, {0xc028, 65}, {0x8002, 95}, {0x8009, 95}, {0x8017, 95}, {0xc028, 95}, {0x8002, 98}, {0x8009, 98}, {0x8017, 98}, {0xc028, 98}, }, /* 26 */ { {0x8003, 61}, {0x8006, 61}, {0x800a, 61}, {0x800f, 61}, {0x8018, 61}, {0x801f, 61}, {0x8029, 61}, {0xc038, 61}, {0x8003, 65}, {0x8006, 65}, {0x800a, 65}, {0x800f, 65}, {0x8018, 65}, {0x801f, 65}, {0x8029, 65}, {0xc038, 65}, }, /* 27 */ { {0x8003, 95}, {0x8006, 95}, {0x800a, 95}, {0x800f, 95}, {0x8018, 95}, {0x801f, 95}, {0x8029, 95}, {0xc038, 95}, {0x8003, 98}, {0x8006, 98}, {0x800a, 98}, {0x800f, 98}, {0x8018, 98}, {0x801f, 98}, {0x8029, 98}, {0xc038, 98}, }, /* 28 */ { {0x8002, 100}, {0x8009, 100}, {0x8017, 100}, {0xc028, 100}, {0x8002, 102}, {0x8009, 102}, {0x8017, 102}, {0xc028, 102}, {0x8002, 103}, {0x8009, 103}, {0x8017, 103}, {0xc028, 103}, {0x8002, 104}, {0x8009, 104}, {0x8017, 104}, {0xc028, 104}, }, /* 29 */ { {0x8003, 100}, {0x8006, 100}, {0x800a, 100}, {0x800f, 100}, {0x8018, 100}, {0x801f, 100}, {0x8029, 100}, {0xc038, 100}, {0x8003, 102}, {0x8006, 102}, {0x800a, 102}, {0x800f, 102}, {0x8018, 102}, {0x801f, 102}, {0x8029, 102}, {0xc038, 102}, }, /* 30 */ { {0x8003, 103}, {0x8006, 103}, {0x800a, 103}, {0x800f, 103}, {0x8018, 103}, {0x801f, 103}, {0x8029, 103}, {0xc038, 103}, {0x8003, 104}, {0x8006, 104}, {0x800a, 104}, {0x800f, 104}, {0x8018, 104}, {0x801f, 104}, {0x8029, 104}, {0xc038, 104}, }, /* 31 */ { {0x8001, 108}, {0xc016, 108}, {0x8001, 109}, {0xc016, 109}, {0x8001, 110}, {0xc016, 110}, {0x8001, 112}, {0xc016, 112}, {0x8001, 114}, {0xc016, 114}, {0x8001, 117}, {0xc016, 117}, {0xc000, 58}, {0xc000, 66}, {0xc000, 67}, {0xc000, 68}, }, /* 32 */ { {0x8002, 108}, {0x8009, 108}, {0x8017, 108}, {0xc028, 108}, {0x8002, 109}, {0x8009, 109}, {0x8017, 109}, {0xc028, 109}, {0x8002, 110}, {0x8009, 110}, {0x8017, 110}, {0xc028, 110}, {0x8002, 112}, {0x8009, 112}, {0x8017, 112}, {0xc028, 112}, }, /* 33 */ { {0x8003, 108}, {0x8006, 108}, {0x800a, 108}, {0x800f, 108}, {0x8018, 108}, {0x801f, 108}, {0x8029, 108}, {0xc038, 108}, {0x8003, 109}, {0x8006, 109}, {0x800a, 109}, {0x800f, 109}, {0x8018, 109}, {0x801f, 109}, {0x8029, 109}, {0xc038, 109}, }, /* 34 */ { {0x8003, 110}, {0x8006, 110}, {0x800a, 110}, {0x800f, 110}, {0x8018, 110}, {0x801f, 110}, {0x8029, 110}, {0xc038, 110}, {0x8003, 112}, {0x8006, 112}, {0x800a, 112}, {0x800f, 112}, {0x8018, 112}, {0x801f, 112}, {0x8029, 112}, {0xc038, 112}, }, /* 35 */ { {0x8002, 114}, {0x8009, 114}, {0x8017, 114}, {0xc028, 114}, {0x8002, 117}, {0x8009, 117}, {0x8017, 117}, {0xc028, 117}, {0x8001, 58}, {0xc016, 58}, {0x8001, 66}, {0xc016, 66}, {0x8001, 67}, {0xc016, 67}, {0x8001, 68}, {0xc016, 68}, }, /* 36 */ { {0x8003, 114}, {0x8006, 114}, {0x800a, 114}, {0x800f, 114}, {0x8018, 114}, {0x801f, 114}, {0x8029, 114}, {0xc038, 114}, {0x8003, 117}, {0x8006, 117}, {0x800a, 117}, {0x800f, 117}, {0x8018, 117}, {0x801f, 117}, {0x8029, 117}, {0xc038, 117}, }, /* 37 */ { {0x8002, 58}, {0x8009, 58}, {0x8017, 58}, {0xc028, 58}, {0x8002, 66}, {0x8009, 66}, {0x8017, 66}, {0xc028, 66}, {0x8002, 67}, {0x8009, 67}, {0x8017, 67}, {0xc028, 67}, {0x8002, 68}, {0x8009, 68}, {0x8017, 68}, {0xc028, 68}, }, /* 38 */ { {0x8003, 58}, {0x8006, 58}, {0x800a, 58}, {0x800f, 58}, {0x8018, 58}, {0x801f, 58}, {0x8029, 58}, {0xc038, 58}, {0x8003, 66}, {0x8006, 66}, {0x800a, 66}, {0x800f, 66}, {0x8018, 66}, {0x801f, 66}, {0x8029, 66}, {0xc038, 66}, }, /* 39 */ { {0x8003, 67}, {0x8006, 67}, {0x800a, 67}, {0x800f, 67}, {0x8018, 67}, {0x801f, 67}, {0x8029, 67}, {0xc038, 67}, {0x8003, 68}, {0x8006, 68}, {0x800a, 68}, {0x800f, 68}, {0x8018, 68}, {0x801f, 68}, {0x8029, 68}, {0xc038, 68}, }, /* 40 */ { {0x2c, 0}, {0x2d, 0}, {0x2f, 0}, {0x30, 0}, {0x33, 0}, {0x34, 0}, {0x36, 0}, {0x37, 0}, {0x3b, 0}, {0x3c, 0}, {0x3e, 0}, {0x3f, 0}, {0x42, 0}, {0x43, 0}, {0x45, 0}, {0x4048, 0}, }, /* 41 */ { {0xc000, 69}, {0xc000, 70}, {0xc000, 71}, {0xc000, 72}, {0xc000, 73}, {0xc000, 74}, {0xc000, 75}, {0xc000, 76}, {0xc000, 77}, {0xc000, 78}, {0xc000, 79}, {0xc000, 80}, {0xc000, 81}, {0xc000, 82}, {0xc000, 83}, {0xc000, 84}, }, /* 42 */ { {0x8001, 69}, {0xc016, 69}, {0x8001, 70}, {0xc016, 70}, {0x8001, 71}, {0xc016, 71}, {0x8001, 72}, {0xc016, 72}, {0x8001, 73}, {0xc016, 73}, {0x8001, 74}, {0xc016, 74}, {0x8001, 75}, {0xc016, 75}, {0x8001, 76}, {0xc016, 76}, }, /* 43 */ { {0x8002, 69}, {0x8009, 69}, {0x8017, 69}, {0xc028, 69}, {0x8002, 70}, {0x8009, 70}, {0x8017, 70}, {0xc028, 70}, {0x8002, 71}, {0x8009, 71}, {0x8017, 71}, {0xc028, 71}, {0x8002, 72}, {0x8009, 72}, {0x8017, 72}, {0xc028, 72}, }, /* 44 */ { {0x8003, 69}, {0x8006, 69}, {0x800a, 69}, {0x800f, 69}, {0x8018, 69}, {0x801f, 69}, {0x8029, 69}, {0xc038, 69}, {0x8003, 70}, {0x8006, 70}, {0x800a, 70}, {0x800f, 70}, {0x8018, 70}, {0x801f, 70}, {0x8029, 70}, {0xc038, 70}, }, /* 45 */ { {0x8003, 71}, {0x8006, 71}, {0x800a, 71}, {0x800f, 71}, {0x8018, 71}, {0x801f, 71}, {0x8029, 71}, {0xc038, 71}, {0x8003, 72}, {0x8006, 72}, {0x800a, 72}, {0x800f, 72}, {0x8018, 72}, {0x801f, 72}, {0x8029, 72}, {0xc038, 72}, }, /* 46 */ { {0x8002, 73}, {0x8009, 73}, {0x8017, 73}, {0xc028, 73}, {0x8002, 74}, {0x8009, 74}, {0x8017, 74}, {0xc028, 74}, {0x8002, 75}, {0x8009, 75}, {0x8017, 75}, {0xc028, 75}, {0x8002, 76}, {0x8009, 76}, {0x8017, 76}, {0xc028, 76}, }, /* 47 */ { {0x8003, 73}, {0x8006, 73}, {0x800a, 73}, {0x800f, 73}, {0x8018, 73}, {0x801f, 73}, {0x8029, 73}, {0xc038, 73}, {0x8003, 74}, {0x8006, 74}, {0x800a, 74}, {0x800f, 74}, {0x8018, 74}, {0x801f, 74}, {0x8029, 74}, {0xc038, 74}, }, /* 48 */ { {0x8003, 75}, {0x8006, 75}, {0x800a, 75}, {0x800f, 75}, {0x8018, 75}, {0x801f, 75}, {0x8029, 75}, {0xc038, 75}, {0x8003, 76}, {0x8006, 76}, {0x800a, 76}, {0x800f, 76}, {0x8018, 76}, {0x801f, 76}, {0x8029, 76}, {0xc038, 76}, }, /* 49 */ { {0x8001, 77}, {0xc016, 77}, {0x8001, 78}, {0xc016, 78}, {0x8001, 79}, {0xc016, 79}, {0x8001, 80}, {0xc016, 80}, {0x8001, 81}, {0xc016, 81}, {0x8001, 82}, {0xc016, 82}, {0x8001, 83}, {0xc016, 83}, {0x8001, 84}, {0xc016, 84}, }, /* 50 */ { {0x8002, 77}, {0x8009, 77}, {0x8017, 77}, {0xc028, 77}, {0x8002, 78}, {0x8009, 78}, {0x8017, 78}, {0xc028, 78}, {0x8002, 79}, {0x8009, 79}, {0x8017, 79}, {0xc028, 79}, {0x8002, 80}, {0x8009, 80}, {0x8017, 80}, {0xc028, 80}, }, /* 51 */ { {0x8003, 77}, {0x8006, 77}, {0x800a, 77}, {0x800f, 77}, {0x8018, 77}, {0x801f, 77}, {0x8029, 77}, {0xc038, 77}, {0x8003, 78}, {0x8006, 78}, {0x800a, 78}, {0x800f, 78}, {0x8018, 78}, {0x801f, 78}, {0x8029, 78}, {0xc038, 78}, }, /* 52 */ { {0x8003, 79}, {0x8006, 79}, {0x800a, 79}, {0x800f, 79}, {0x8018, 79}, {0x801f, 79}, {0x8029, 79}, {0xc038, 79}, {0x8003, 80}, {0x8006, 80}, {0x800a, 80}, {0x800f, 80}, {0x8018, 80}, {0x801f, 80}, {0x8029, 80}, {0xc038, 80}, }, /* 53 */ { {0x8002, 81}, {0x8009, 81}, {0x8017, 81}, {0xc028, 81}, {0x8002, 82}, {0x8009, 82}, {0x8017, 82}, {0xc028, 82}, {0x8002, 83}, {0x8009, 83}, {0x8017, 83}, {0xc028, 83}, {0x8002, 84}, {0x8009, 84}, {0x8017, 84}, {0xc028, 84}, }, /* 54 */ { {0x8003, 81}, {0x8006, 81}, {0x800a, 81}, {0x800f, 81}, {0x8018, 81}, {0x801f, 81}, {0x8029, 81}, {0xc038, 81}, {0x8003, 82}, {0x8006, 82}, {0x800a, 82}, {0x800f, 82}, {0x8018, 82}, {0x801f, 82}, {0x8029, 82}, {0xc038, 82}, }, /* 55 */ { {0x8003, 83}, {0x8006, 83}, {0x800a, 83}, {0x800f, 83}, {0x8018, 83}, {0x801f, 83}, {0x8029, 83}, {0xc038, 83}, {0x8003, 84}, {0x8006, 84}, {0x800a, 84}, {0x800f, 84}, {0x8018, 84}, {0x801f, 84}, {0x8029, 84}, {0xc038, 84}, }, /* 56 */ { {0xc000, 85}, {0xc000, 86}, {0xc000, 87}, {0xc000, 89}, {0xc000, 106}, {0xc000, 107}, {0xc000, 113}, {0xc000, 118}, {0xc000, 119}, {0xc000, 120}, {0xc000, 121}, {0xc000, 122}, {0x46, 0}, {0x47, 0}, {0x49, 0}, {0x404a, 0}, }, /* 57 */ { {0x8001, 85}, {0xc016, 85}, {0x8001, 86}, {0xc016, 86}, {0x8001, 87}, {0xc016, 87}, {0x8001, 89}, {0xc016, 89}, {0x8001, 106}, {0xc016, 106}, {0x8001, 107}, {0xc016, 107}, {0x8001, 113}, {0xc016, 113}, {0x8001, 118}, {0xc016, 118}, }, /* 58 */ { {0x8002, 85}, {0x8009, 85}, {0x8017, 85}, {0xc028, 85}, {0x8002, 86}, {0x8009, 86}, {0x8017, 86}, {0xc028, 86}, {0x8002, 87}, {0x8009, 87}, {0x8017, 87}, {0xc028, 87}, {0x8002, 89}, {0x8009, 89}, {0x8017, 89}, {0xc028, 89}, }, /* 59 */ { {0x8003, 85}, {0x8006, 85}, {0x800a, 85}, {0x800f, 85}, {0x8018, 85}, {0x801f, 85}, {0x8029, 85}, {0xc038, 85}, {0x8003, 86}, {0x8006, 86}, {0x800a, 86}, {0x800f, 86}, {0x8018, 86}, {0x801f, 86}, {0x8029, 86}, {0xc038, 86}, }, /* 60 */ { {0x8003, 87}, {0x8006, 87}, {0x800a, 87}, {0x800f, 87}, {0x8018, 87}, {0x801f, 87}, {0x8029, 87}, {0xc038, 87}, {0x8003, 89}, {0x8006, 89}, {0x800a, 89}, {0x800f, 89}, {0x8018, 89}, {0x801f, 89}, {0x8029, 89}, {0xc038, 89}, }, /* 61 */ { {0x8002, 106}, {0x8009, 106}, {0x8017, 106}, {0xc028, 106}, {0x8002, 107}, {0x8009, 107}, {0x8017, 107}, {0xc028, 107}, {0x8002, 113}, {0x8009, 113}, {0x8017, 113}, {0xc028, 113}, {0x8002, 118}, {0x8009, 118}, {0x8017, 118}, {0xc028, 118}, }, /* 62 */ { {0x8003, 106}, {0x8006, 106}, {0x800a, 106}, {0x800f, 106}, {0x8018, 106}, {0x801f, 106}, {0x8029, 106}, {0xc038, 106}, {0x8003, 107}, {0x8006, 107}, {0x800a, 107}, {0x800f, 107}, {0x8018, 107}, {0x801f, 107}, {0x8029, 107}, {0xc038, 107}, }, /* 63 */ { {0x8003, 113}, {0x8006, 113}, {0x800a, 113}, {0x800f, 113}, {0x8018, 113}, {0x801f, 113}, {0x8029, 113}, {0xc038, 113}, {0x8003, 118}, {0x8006, 118}, {0x800a, 118}, {0x800f, 118}, {0x8018, 118}, {0x801f, 118}, {0x8029, 118}, {0xc038, 118}, }, /* 64 */ { {0x8001, 119}, {0xc016, 119}, {0x8001, 120}, {0xc016, 120}, {0x8001, 121}, {0xc016, 121}, {0x8001, 122}, {0xc016, 122}, {0xc000, 38}, {0xc000, 42}, {0xc000, 44}, {0xc000, 59}, {0xc000, 88}, {0xc000, 90}, {0x4b, 0}, {0x4e, 0}, }, /* 65 */ { {0x8002, 119}, {0x8009, 119}, {0x8017, 119}, {0xc028, 119}, {0x8002, 120}, {0x8009, 120}, {0x8017, 120}, {0xc028, 120}, {0x8002, 121}, {0x8009, 121}, {0x8017, 121}, {0xc028, 121}, {0x8002, 122}, {0x8009, 122}, {0x8017, 122}, {0xc028, 122}, }, /* 66 */ { {0x8003, 119}, {0x8006, 119}, {0x800a, 119}, {0x800f, 119}, {0x8018, 119}, {0x801f, 119}, {0x8029, 119}, {0xc038, 119}, {0x8003, 120}, {0x8006, 120}, {0x800a, 120}, {0x800f, 120}, {0x8018, 120}, {0x801f, 120}, {0x8029, 120}, {0xc038, 120}, }, /* 67 */ { {0x8003, 121}, {0x8006, 121}, {0x800a, 121}, {0x800f, 121}, {0x8018, 121}, {0x801f, 121}, {0x8029, 121}, {0xc038, 121}, {0x8003, 122}, {0x8006, 122}, {0x800a, 122}, {0x800f, 122}, {0x8018, 122}, {0x801f, 122}, {0x8029, 122}, {0xc038, 122}, }, /* 68 */ { {0x8001, 38}, {0xc016, 38}, {0x8001, 42}, {0xc016, 42}, {0x8001, 44}, {0xc016, 44}, {0x8001, 59}, {0xc016, 59}, {0x8001, 88}, {0xc016, 88}, {0x8001, 90}, {0xc016, 90}, {0x4c, 0}, {0x4d, 0}, {0x4f, 0}, {0x51, 0}, }, /* 69 */ { {0x8002, 38}, {0x8009, 38}, {0x8017, 38}, {0xc028, 38}, {0x8002, 42}, {0x8009, 42}, {0x8017, 42}, {0xc028, 42}, {0x8002, 44}, {0x8009, 44}, {0x8017, 44}, {0xc028, 44}, {0x8002, 59}, {0x8009, 59}, {0x8017, 59}, {0xc028, 59}, }, /* 70 */ { {0x8003, 38}, {0x8006, 38}, {0x800a, 38}, {0x800f, 38}, {0x8018, 38}, {0x801f, 38}, {0x8029, 38}, {0xc038, 38}, {0x8003, 42}, {0x8006, 42}, {0x800a, 42}, {0x800f, 42}, {0x8018, 42}, {0x801f, 42}, {0x8029, 42}, {0xc038, 42}, }, /* 71 */ { {0x8003, 44}, {0x8006, 44}, {0x800a, 44}, {0x800f, 44}, {0x8018, 44}, {0x801f, 44}, {0x8029, 44}, {0xc038, 44}, {0x8003, 59}, {0x8006, 59}, {0x800a, 59}, {0x800f, 59}, {0x8018, 59}, {0x801f, 59}, {0x8029, 59}, {0xc038, 59}, }, /* 72 */ { {0x8002, 88}, {0x8009, 88}, {0x8017, 88}, {0xc028, 88}, {0x8002, 90}, {0x8009, 90}, {0x8017, 90}, {0xc028, 90}, {0xc000, 33}, {0xc000, 34}, {0xc000, 40}, {0xc000, 41}, {0xc000, 63}, {0x50, 0}, {0x52, 0}, {0x54, 0}, }, /* 73 */ { {0x8003, 88}, {0x8006, 88}, {0x800a, 88}, {0x800f, 88}, {0x8018, 88}, {0x801f, 88}, {0x8029, 88}, {0xc038, 88}, {0x8003, 90}, {0x8006, 90}, {0x800a, 90}, {0x800f, 90}, {0x8018, 90}, {0x801f, 90}, {0x8029, 90}, {0xc038, 90}, }, /* 74 */ { {0x8001, 33}, {0xc016, 33}, {0x8001, 34}, {0xc016, 34}, {0x8001, 40}, {0xc016, 40}, {0x8001, 41}, {0xc016, 41}, {0x8001, 63}, {0xc016, 63}, {0xc000, 39}, {0xc000, 43}, {0xc000, 124}, {0x53, 0}, {0x55, 0}, {0x58, 0}, }, /* 75 */ { {0x8002, 33}, {0x8009, 33}, {0x8017, 33}, {0xc028, 33}, {0x8002, 34}, {0x8009, 34}, {0x8017, 34}, {0xc028, 34}, {0x8002, 40}, {0x8009, 40}, {0x8017, 40}, {0xc028, 40}, {0x8002, 41}, {0x8009, 41}, {0x8017, 41}, {0xc028, 41}, }, /* 76 */ { {0x8003, 33}, {0x8006, 33}, {0x800a, 33}, {0x800f, 33}, {0x8018, 33}, {0x801f, 33}, {0x8029, 33}, {0xc038, 33}, {0x8003, 34}, {0x8006, 34}, {0x800a, 34}, {0x800f, 34}, {0x8018, 34}, {0x801f, 34}, {0x8029, 34}, {0xc038, 34}, }, /* 77 */ { {0x8003, 40}, {0x8006, 40}, {0x800a, 40}, {0x800f, 40}, {0x8018, 40}, {0x801f, 40}, {0x8029, 40}, {0xc038, 40}, {0x8003, 41}, {0x8006, 41}, {0x800a, 41}, {0x800f, 41}, {0x8018, 41}, {0x801f, 41}, {0x8029, 41}, {0xc038, 41}, }, /* 78 */ { {0x8002, 63}, {0x8009, 63}, {0x8017, 63}, {0xc028, 63}, {0x8001, 39}, {0xc016, 39}, {0x8001, 43}, {0xc016, 43}, {0x8001, 124}, {0xc016, 124}, {0xc000, 35}, {0xc000, 62}, {0x56, 0}, {0x57, 0}, {0x59, 0}, {0x5a, 0}, }, /* 79 */ { {0x8003, 63}, {0x8006, 63}, {0x800a, 63}, {0x800f, 63}, {0x8018, 63}, {0x801f, 63}, {0x8029, 63}, {0xc038, 63}, {0x8002, 39}, {0x8009, 39}, {0x8017, 39}, {0xc028, 39}, {0x8002, 43}, {0x8009, 43}, {0x8017, 43}, {0xc028, 43}, }, /* 80 */ { {0x8003, 39}, {0x8006, 39}, {0x800a, 39}, {0x800f, 39}, {0x8018, 39}, {0x801f, 39}, {0x8029, 39}, {0xc038, 39}, {0x8003, 43}, {0x8006, 43}, {0x800a, 43}, {0x800f, 43}, {0x8018, 43}, {0x801f, 43}, {0x8029, 43}, {0xc038, 43}, }, /* 81 */ { {0x8002, 124}, {0x8009, 124}, {0x8017, 124}, {0xc028, 124}, {0x8001, 35}, {0xc016, 35}, {0x8001, 62}, {0xc016, 62}, {0xc000, 0}, {0xc000, 36}, {0xc000, 64}, {0xc000, 91}, {0xc000, 93}, {0xc000, 126}, {0x5b, 0}, {0x5c, 0}, }, /* 82 */ { {0x8003, 124}, {0x8006, 124}, {0x800a, 124}, {0x800f, 124}, {0x8018, 124}, {0x801f, 124}, {0x8029, 124}, {0xc038, 124}, {0x8002, 35}, {0x8009, 35}, {0x8017, 35}, {0xc028, 35}, {0x8002, 62}, {0x8009, 62}, {0x8017, 62}, {0xc028, 62}, }, /* 83 */ { {0x8003, 35}, {0x8006, 35}, {0x800a, 35}, {0x800f, 35}, {0x8018, 35}, {0x801f, 35}, {0x8029, 35}, {0xc038, 35}, {0x8003, 62}, {0x8006, 62}, {0x800a, 62}, {0x800f, 62}, {0x8018, 62}, {0x801f, 62}, {0x8029, 62}, {0xc038, 62}, }, /* 84 */ { {0x8001, 0}, {0xc016, 0}, {0x8001, 36}, {0xc016, 36}, {0x8001, 64}, {0xc016, 64}, {0x8001, 91}, {0xc016, 91}, {0x8001, 93}, {0xc016, 93}, {0x8001, 126}, {0xc016, 126}, {0xc000, 94}, {0xc000, 125}, {0x5d, 0}, {0x5e, 0}, }, /* 85 */ { {0x8002, 0}, {0x8009, 0}, {0x8017, 0}, {0xc028, 0}, {0x8002, 36}, {0x8009, 36}, {0x8017, 36}, {0xc028, 36}, {0x8002, 64}, {0x8009, 64}, {0x8017, 64}, {0xc028, 64}, {0x8002, 91}, {0x8009, 91}, {0x8017, 91}, {0xc028, 91}, }, /* 86 */ { {0x8003, 0}, {0x8006, 0}, {0x800a, 0}, {0x800f, 0}, {0x8018, 0}, {0x801f, 0}, {0x8029, 0}, {0xc038, 0}, {0x8003, 36}, {0x8006, 36}, {0x800a, 36}, {0x800f, 36}, {0x8018, 36}, {0x801f, 36}, {0x8029, 36}, {0xc038, 36}, }, /* 87 */ { {0x8003, 64}, {0x8006, 64}, {0x800a, 64}, {0x800f, 64}, {0x8018, 64}, {0x801f, 64}, {0x8029, 64}, {0xc038, 64}, {0x8003, 91}, {0x8006, 91}, {0x800a, 91}, {0x800f, 91}, {0x8018, 91}, {0x801f, 91}, {0x8029, 91}, {0xc038, 91}, }, /* 88 */ { {0x8002, 93}, {0x8009, 93}, {0x8017, 93}, {0xc028, 93}, {0x8002, 126}, {0x8009, 126}, {0x8017, 126}, {0xc028, 126}, {0x8001, 94}, {0xc016, 94}, {0x8001, 125}, {0xc016, 125}, {0xc000, 60}, {0xc000, 96}, {0xc000, 123}, {0x5f, 0}, }, /* 89 */ { {0x8003, 93}, {0x8006, 93}, {0x800a, 93}, {0x800f, 93}, {0x8018, 93}, {0x801f, 93}, {0x8029, 93}, {0xc038, 93}, {0x8003, 126}, {0x8006, 126}, {0x800a, 126}, {0x800f, 126}, {0x8018, 126}, {0x801f, 126}, {0x8029, 126}, {0xc038, 126}, }, /* 90 */ { {0x8002, 94}, {0x8009, 94}, {0x8017, 94}, {0xc028, 94}, {0x8002, 125}, {0x8009, 125}, {0x8017, 125}, {0xc028, 125}, {0x8001, 60}, {0xc016, 60}, {0x8001, 96}, {0xc016, 96}, {0x8001, 123}, {0xc016, 123}, {0x60, 0}, {0x6e, 0}, }, /* 91 */ { {0x8003, 94}, {0x8006, 94}, {0x800a, 94}, {0x800f, 94}, {0x8018, 94}, {0x801f, 94}, {0x8029, 94}, {0xc038, 94}, {0x8003, 125}, {0x8006, 125}, {0x800a, 125}, {0x800f, 125}, {0x8018, 125}, {0x801f, 125}, {0x8029, 125}, {0xc038, 125}, }, /* 92 */ { {0x8002, 60}, {0x8009, 60}, {0x8017, 60}, {0xc028, 60}, {0x8002, 96}, {0x8009, 96}, {0x8017, 96}, {0xc028, 96}, {0x8002, 123}, {0x8009, 123}, {0x8017, 123}, {0xc028, 123}, {0x61, 0}, {0x65, 0}, {0x6f, 0}, {0x85, 0}, }, /* 93 */ { {0x8003, 60}, {0x8006, 60}, {0x800a, 60}, {0x800f, 60}, {0x8018, 60}, {0x801f, 60}, {0x8029, 60}, {0xc038, 60}, {0x8003, 96}, {0x8006, 96}, {0x800a, 96}, {0x800f, 96}, {0x8018, 96}, {0x801f, 96}, {0x8029, 96}, {0xc038, 96}, }, /* 94 */ { {0x8003, 123}, {0x8006, 123}, {0x800a, 123}, {0x800f, 123}, {0x8018, 123}, {0x801f, 123}, {0x8029, 123}, {0xc038, 123}, {0x62, 0}, {0x63, 0}, {0x66, 0}, {0x69, 0}, {0x70, 0}, {0x77, 0}, {0x86, 0}, {0x99, 0}, }, /* 95 */ { {0xc000, 92}, {0xc000, 195}, {0xc000, 208}, {0x64, 0}, {0x67, 0}, {0x68, 0}, {0x6a, 0}, {0x6b, 0}, {0x71, 0}, {0x74, 0}, {0x78, 0}, {0x7e, 0}, {0x87, 0}, {0x8e, 0}, {0x9a, 0}, {0xa9, 0}, }, /* 96 */ { {0x8001, 92}, {0xc016, 92}, {0x8001, 195}, {0xc016, 195}, {0x8001, 208}, {0xc016, 208}, {0xc000, 128}, {0xc000, 130}, {0xc000, 131}, {0xc000, 162}, {0xc000, 184}, {0xc000, 194}, {0xc000, 224}, {0xc000, 226}, {0x6c, 0}, {0x6d, 0}, }, /* 97 */ { {0x8002, 92}, {0x8009, 92}, {0x8017, 92}, {0xc028, 92}, {0x8002, 195}, {0x8009, 195}, {0x8017, 195}, {0xc028, 195}, {0x8002, 208}, {0x8009, 208}, {0x8017, 208}, {0xc028, 208}, {0x8001, 128}, {0xc016, 128}, {0x8001, 130}, {0xc016, 130}, }, /* 98 */ { {0x8003, 92}, {0x8006, 92}, {0x800a, 92}, {0x800f, 92}, {0x8018, 92}, {0x801f, 92}, {0x8029, 92}, {0xc038, 92}, {0x8003, 195}, {0x8006, 195}, {0x800a, 195}, {0x800f, 195}, {0x8018, 195}, {0x801f, 195}, {0x8029, 195}, {0xc038, 195}, }, /* 99 */ { {0x8003, 208}, {0x8006, 208}, {0x800a, 208}, {0x800f, 208}, {0x8018, 208}, {0x801f, 208}, {0x8029, 208}, {0xc038, 208}, {0x8002, 128}, {0x8009, 128}, {0x8017, 128}, {0xc028, 128}, {0x8002, 130}, {0x8009, 130}, {0x8017, 130}, {0xc028, 130}, }, /* 100 */ { {0x8003, 128}, {0x8006, 128}, {0x800a, 128}, {0x800f, 128}, {0x8018, 128}, {0x801f, 128}, {0x8029, 128}, {0xc038, 128}, {0x8003, 130}, {0x8006, 130}, {0x800a, 130}, {0x800f, 130}, {0x8018, 130}, {0x801f, 130}, {0x8029, 130}, {0xc038, 130}, }, /* 101 */ { {0x8001, 131}, {0xc016, 131}, {0x8001, 162}, {0xc016, 162}, {0x8001, 184}, {0xc016, 184}, {0x8001, 194}, {0xc016, 194}, {0x8001, 224}, {0xc016, 224}, {0x8001, 226}, {0xc016, 226}, {0xc000, 153}, {0xc000, 161}, {0xc000, 167}, {0xc000, 172}, }, /* 102 */ { {0x8002, 131}, {0x8009, 131}, {0x8017, 131}, {0xc028, 131}, {0x8002, 162}, {0x8009, 162}, {0x8017, 162}, {0xc028, 162}, {0x8002, 184}, {0x8009, 184}, {0x8017, 184}, {0xc028, 184}, {0x8002, 194}, {0x8009, 194}, {0x8017, 194}, {0xc028, 194}, }, /* 103 */ { {0x8003, 131}, {0x8006, 131}, {0x800a, 131}, {0x800f, 131}, {0x8018, 131}, {0x801f, 131}, {0x8029, 131}, {0xc038, 131}, {0x8003, 162}, {0x8006, 162}, {0x800a, 162}, {0x800f, 162}, {0x8018, 162}, {0x801f, 162}, {0x8029, 162}, {0xc038, 162}, }, /* 104 */ { {0x8003, 184}, {0x8006, 184}, {0x800a, 184}, {0x800f, 184}, {0x8018, 184}, {0x801f, 184}, {0x8029, 184}, {0xc038, 184}, {0x8003, 194}, {0x8006, 194}, {0x800a, 194}, {0x800f, 194}, {0x8018, 194}, {0x801f, 194}, {0x8029, 194}, {0xc038, 194}, }, /* 105 */ { {0x8002, 224}, {0x8009, 224}, {0x8017, 224}, {0xc028, 224}, {0x8002, 226}, {0x8009, 226}, {0x8017, 226}, {0xc028, 226}, {0x8001, 153}, {0xc016, 153}, {0x8001, 161}, {0xc016, 161}, {0x8001, 167}, {0xc016, 167}, {0x8001, 172}, {0xc016, 172}, }, /* 106 */ { {0x8003, 224}, {0x8006, 224}, {0x800a, 224}, {0x800f, 224}, {0x8018, 224}, {0x801f, 224}, {0x8029, 224}, {0xc038, 224}, {0x8003, 226}, {0x8006, 226}, {0x800a, 226}, {0x800f, 226}, {0x8018, 226}, {0x801f, 226}, {0x8029, 226}, {0xc038, 226}, }, /* 107 */ { {0x8002, 153}, {0x8009, 153}, {0x8017, 153}, {0xc028, 153}, {0x8002, 161}, {0x8009, 161}, {0x8017, 161}, {0xc028, 161}, {0x8002, 167}, {0x8009, 167}, {0x8017, 167}, {0xc028, 167}, {0x8002, 172}, {0x8009, 172}, {0x8017, 172}, {0xc028, 172}, }, /* 108 */ { {0x8003, 153}, {0x8006, 153}, {0x800a, 153}, {0x800f, 153}, {0x8018, 153}, {0x801f, 153}, {0x8029, 153}, {0xc038, 153}, {0x8003, 161}, {0x8006, 161}, {0x800a, 161}, {0x800f, 161}, {0x8018, 161}, {0x801f, 161}, {0x8029, 161}, {0xc038, 161}, }, /* 109 */ { {0x8003, 167}, {0x8006, 167}, {0x800a, 167}, {0x800f, 167}, {0x8018, 167}, {0x801f, 167}, {0x8029, 167}, {0xc038, 167}, {0x8003, 172}, {0x8006, 172}, {0x800a, 172}, {0x800f, 172}, {0x8018, 172}, {0x801f, 172}, {0x8029, 172}, {0xc038, 172}, }, /* 110 */ { {0x72, 0}, {0x73, 0}, {0x75, 0}, {0x76, 0}, {0x79, 0}, {0x7b, 0}, {0x7f, 0}, {0x82, 0}, {0x88, 0}, {0x8b, 0}, {0x8f, 0}, {0x92, 0}, {0x9b, 0}, {0xa2, 0}, {0xaa, 0}, {0xb4, 0}, }, /* 111 */ { {0xc000, 176}, {0xc000, 177}, {0xc000, 179}, {0xc000, 209}, {0xc000, 216}, {0xc000, 217}, {0xc000, 227}, {0xc000, 229}, {0xc000, 230}, {0x7a, 0}, {0x7c, 0}, {0x7d, 0}, {0x80, 0}, {0x81, 0}, {0x83, 0}, {0x84, 0}, }, /* 112 */ { {0x8001, 176}, {0xc016, 176}, {0x8001, 177}, {0xc016, 177}, {0x8001, 179}, {0xc016, 179}, {0x8001, 209}, {0xc016, 209}, {0x8001, 216}, {0xc016, 216}, {0x8001, 217}, {0xc016, 217}, {0x8001, 227}, {0xc016, 227}, {0x8001, 229}, {0xc016, 229}, }, /* 113 */ { {0x8002, 176}, {0x8009, 176}, {0x8017, 176}, {0xc028, 176}, {0x8002, 177}, {0x8009, 177}, {0x8017, 177}, {0xc028, 177}, {0x8002, 179}, {0x8009, 179}, {0x8017, 179}, {0xc028, 179}, {0x8002, 209}, {0x8009, 209}, {0x8017, 209}, {0xc028, 209}, }, /* 114 */ { {0x8003, 176}, {0x8006, 176}, {0x800a, 176}, {0x800f, 176}, {0x8018, 176}, {0x801f, 176}, {0x8029, 176}, {0xc038, 176}, {0x8003, 177}, {0x8006, 177}, {0x800a, 177}, {0x800f, 177}, {0x8018, 177}, {0x801f, 177}, {0x8029, 177}, {0xc038, 177}, }, /* 115 */ { {0x8003, 179}, {0x8006, 179}, {0x800a, 179}, {0x800f, 179}, {0x8018, 179}, {0x801f, 179}, {0x8029, 179}, {0xc038, 179}, {0x8003, 209}, {0x8006, 209}, {0x800a, 209}, {0x800f, 209}, {0x8018, 209}, {0x801f, 209}, {0x8029, 209}, {0xc038, 209}, }, /* 116 */ { {0x8002, 216}, {0x8009, 216}, {0x8017, 216}, {0xc028, 216}, {0x8002, 217}, {0x8009, 217}, {0x8017, 217}, {0xc028, 217}, {0x8002, 227}, {0x8009, 227}, {0x8017, 227}, {0xc028, 227}, {0x8002, 229}, {0x8009, 229}, {0x8017, 229}, {0xc028, 229}, }, /* 117 */ { {0x8003, 216}, {0x8006, 216}, {0x800a, 216}, {0x800f, 216}, {0x8018, 216}, {0x801f, 216}, {0x8029, 216}, {0xc038, 216}, {0x8003, 217}, {0x8006, 217}, {0x800a, 217}, {0x800f, 217}, {0x8018, 217}, {0x801f, 217}, {0x8029, 217}, {0xc038, 217}, }, /* 118 */ { {0x8003, 227}, {0x8006, 227}, {0x800a, 227}, {0x800f, 227}, {0x8018, 227}, {0x801f, 227}, {0x8029, 227}, {0xc038, 227}, {0x8003, 229}, {0x8006, 229}, {0x800a, 229}, {0x800f, 229}, {0x8018, 229}, {0x801f, 229}, {0x8029, 229}, {0xc038, 229}, }, /* 119 */ { {0x8001, 230}, {0xc016, 230}, {0xc000, 129}, {0xc000, 132}, {0xc000, 133}, {0xc000, 134}, {0xc000, 136}, {0xc000, 146}, {0xc000, 154}, {0xc000, 156}, {0xc000, 160}, {0xc000, 163}, {0xc000, 164}, {0xc000, 169}, {0xc000, 170}, {0xc000, 173}, }, /* 120 */ { {0x8002, 230}, {0x8009, 230}, {0x8017, 230}, {0xc028, 230}, {0x8001, 129}, {0xc016, 129}, {0x8001, 132}, {0xc016, 132}, {0x8001, 133}, {0xc016, 133}, {0x8001, 134}, {0xc016, 134}, {0x8001, 136}, {0xc016, 136}, {0x8001, 146}, {0xc016, 146}, }, /* 121 */ { {0x8003, 230}, {0x8006, 230}, {0x800a, 230}, {0x800f, 230}, {0x8018, 230}, {0x801f, 230}, {0x8029, 230}, {0xc038, 230}, {0x8002, 129}, {0x8009, 129}, {0x8017, 129}, {0xc028, 129}, {0x8002, 132}, {0x8009, 132}, {0x8017, 132}, {0xc028, 132}, }, /* 122 */ { {0x8003, 129}, {0x8006, 129}, {0x800a, 129}, {0x800f, 129}, {0x8018, 129}, {0x801f, 129}, {0x8029, 129}, {0xc038, 129}, {0x8003, 132}, {0x8006, 132}, {0x800a, 132}, {0x800f, 132}, {0x8018, 132}, {0x801f, 132}, {0x8029, 132}, {0xc038, 132}, }, /* 123 */ { {0x8002, 133}, {0x8009, 133}, {0x8017, 133}, {0xc028, 133}, {0x8002, 134}, {0x8009, 134}, {0x8017, 134}, {0xc028, 134}, {0x8002, 136}, {0x8009, 136}, {0x8017, 136}, {0xc028, 136}, {0x8002, 146}, {0x8009, 146}, {0x8017, 146}, {0xc028, 146}, }, /* 124 */ { {0x8003, 133}, {0x8006, 133}, {0x800a, 133}, {0x800f, 133}, {0x8018, 133}, {0x801f, 133}, {0x8029, 133}, {0xc038, 133}, {0x8003, 134}, {0x8006, 134}, {0x800a, 134}, {0x800f, 134}, {0x8018, 134}, {0x801f, 134}, {0x8029, 134}, {0xc038, 134}, }, /* 125 */ { {0x8003, 136}, {0x8006, 136}, {0x800a, 136}, {0x800f, 136}, {0x8018, 136}, {0x801f, 136}, {0x8029, 136}, {0xc038, 136}, {0x8003, 146}, {0x8006, 146}, {0x800a, 146}, {0x800f, 146}, {0x8018, 146}, {0x801f, 146}, {0x8029, 146}, {0xc038, 146}, }, /* 126 */ { {0x8001, 154}, {0xc016, 154}, {0x8001, 156}, {0xc016, 156}, {0x8001, 160}, {0xc016, 160}, {0x8001, 163}, {0xc016, 163}, {0x8001, 164}, {0xc016, 164}, {0x8001, 169}, {0xc016, 169}, {0x8001, 170}, {0xc016, 170}, {0x8001, 173}, {0xc016, 173}, }, /* 127 */ { {0x8002, 154}, {0x8009, 154}, {0x8017, 154}, {0xc028, 154}, {0x8002, 156}, {0x8009, 156}, {0x8017, 156}, {0xc028, 156}, {0x8002, 160}, {0x8009, 160}, {0x8017, 160}, {0xc028, 160}, {0x8002, 163}, {0x8009, 163}, {0x8017, 163}, {0xc028, 163}, }, /* 128 */ { {0x8003, 154}, {0x8006, 154}, {0x800a, 154}, {0x800f, 154}, {0x8018, 154}, {0x801f, 154}, {0x8029, 154}, {0xc038, 154}, {0x8003, 156}, {0x8006, 156}, {0x800a, 156}, {0x800f, 156}, {0x8018, 156}, {0x801f, 156}, {0x8029, 156}, {0xc038, 156}, }, /* 129 */ { {0x8003, 160}, {0x8006, 160}, {0x800a, 160}, {0x800f, 160}, {0x8018, 160}, {0x801f, 160}, {0x8029, 160}, {0xc038, 160}, {0x8003, 163}, {0x8006, 163}, {0x800a, 163}, {0x800f, 163}, {0x8018, 163}, {0x801f, 163}, {0x8029, 163}, {0xc038, 163}, }, /* 130 */ { {0x8002, 164}, {0x8009, 164}, {0x8017, 164}, {0xc028, 164}, {0x8002, 169}, {0x8009, 169}, {0x8017, 169}, {0xc028, 169}, {0x8002, 170}, {0x8009, 170}, {0x8017, 170}, {0xc028, 170}, {0x8002, 173}, {0x8009, 173}, {0x8017, 173}, {0xc028, 173}, }, /* 131 */ { {0x8003, 164}, {0x8006, 164}, {0x800a, 164}, {0x800f, 164}, {0x8018, 164}, {0x801f, 164}, {0x8029, 164}, {0xc038, 164}, {0x8003, 169}, {0x8006, 169}, {0x800a, 169}, {0x800f, 169}, {0x8018, 169}, {0x801f, 169}, {0x8029, 169}, {0xc038, 169}, }, /* 132 */ { {0x8003, 170}, {0x8006, 170}, {0x800a, 170}, {0x800f, 170}, {0x8018, 170}, {0x801f, 170}, {0x8029, 170}, {0xc038, 170}, {0x8003, 173}, {0x8006, 173}, {0x800a, 173}, {0x800f, 173}, {0x8018, 173}, {0x801f, 173}, {0x8029, 173}, {0xc038, 173}, }, /* 133 */ { {0x89, 0}, {0x8a, 0}, {0x8c, 0}, {0x8d, 0}, {0x90, 0}, {0x91, 0}, {0x93, 0}, {0x96, 0}, {0x9c, 0}, {0x9f, 0}, {0xa3, 0}, {0xa6, 0}, {0xab, 0}, {0xae, 0}, {0xb5, 0}, {0xbe, 0}, }, /* 134 */ { {0xc000, 178}, {0xc000, 181}, {0xc000, 185}, {0xc000, 186}, {0xc000, 187}, {0xc000, 189}, {0xc000, 190}, {0xc000, 196}, {0xc000, 198}, {0xc000, 228}, {0xc000, 232}, {0xc000, 233}, {0x94, 0}, {0x95, 0}, {0x97, 0}, {0x98, 0}, }, /* 135 */ { {0x8001, 178}, {0xc016, 178}, {0x8001, 181}, {0xc016, 181}, {0x8001, 185}, {0xc016, 185}, {0x8001, 186}, {0xc016, 186}, {0x8001, 187}, {0xc016, 187}, {0x8001, 189}, {0xc016, 189}, {0x8001, 190}, {0xc016, 190}, {0x8001, 196}, {0xc016, 196}, }, /* 136 */ { {0x8002, 178}, {0x8009, 178}, {0x8017, 178}, {0xc028, 178}, {0x8002, 181}, {0x8009, 181}, {0x8017, 181}, {0xc028, 181}, {0x8002, 185}, {0x8009, 185}, {0x8017, 185}, {0xc028, 185}, {0x8002, 186}, {0x8009, 186}, {0x8017, 186}, {0xc028, 186}, }, /* 137 */ { {0x8003, 178}, {0x8006, 178}, {0x800a, 178}, {0x800f, 178}, {0x8018, 178}, {0x801f, 178}, {0x8029, 178}, {0xc038, 178}, {0x8003, 181}, {0x8006, 181}, {0x800a, 181}, {0x800f, 181}, {0x8018, 181}, {0x801f, 181}, {0x8029, 181}, {0xc038, 181}, }, /* 138 */ { {0x8003, 185}, {0x8006, 185}, {0x800a, 185}, {0x800f, 185}, {0x8018, 185}, {0x801f, 185}, {0x8029, 185}, {0xc038, 185}, {0x8003, 186}, {0x8006, 186}, {0x800a, 186}, {0x800f, 186}, {0x8018, 186}, {0x801f, 186}, {0x8029, 186}, {0xc038, 186}, }, /* 139 */ { {0x8002, 187}, {0x8009, 187}, {0x8017, 187}, {0xc028, 187}, {0x8002, 189}, {0x8009, 189}, {0x8017, 189}, {0xc028, 189}, {0x8002, 190}, {0x8009, 190}, {0x8017, 190}, {0xc028, 190}, {0x8002, 196}, {0x8009, 196}, {0x8017, 196}, {0xc028, 196}, }, /* 140 */ { {0x8003, 187}, {0x8006, 187}, {0x800a, 187}, {0x800f, 187}, {0x8018, 187}, {0x801f, 187}, {0x8029, 187}, {0xc038, 187}, {0x8003, 189}, {0x8006, 189}, {0x800a, 189}, {0x800f, 189}, {0x8018, 189}, {0x801f, 189}, {0x8029, 189}, {0xc038, 189}, }, /* 141 */ { {0x8003, 190}, {0x8006, 190}, {0x800a, 190}, {0x800f, 190}, {0x8018, 190}, {0x801f, 190}, {0x8029, 190}, {0xc038, 190}, {0x8003, 196}, {0x8006, 196}, {0x800a, 196}, {0x800f, 196}, {0x8018, 196}, {0x801f, 196}, {0x8029, 196}, {0xc038, 196}, }, /* 142 */ { {0x8001, 198}, {0xc016, 198}, {0x8001, 228}, {0xc016, 228}, {0x8001, 232}, {0xc016, 232}, {0x8001, 233}, {0xc016, 233}, {0xc000, 1}, {0xc000, 135}, {0xc000, 137}, {0xc000, 138}, {0xc000, 139}, {0xc000, 140}, {0xc000, 141}, {0xc000, 143}, }, /* 143 */ { {0x8002, 198}, {0x8009, 198}, {0x8017, 198}, {0xc028, 198}, {0x8002, 228}, {0x8009, 228}, {0x8017, 228}, {0xc028, 228}, {0x8002, 232}, {0x8009, 232}, {0x8017, 232}, {0xc028, 232}, {0x8002, 233}, {0x8009, 233}, {0x8017, 233}, {0xc028, 233}, }, /* 144 */ { {0x8003, 198}, {0x8006, 198}, {0x800a, 198}, {0x800f, 198}, {0x8018, 198}, {0x801f, 198}, {0x8029, 198}, {0xc038, 198}, {0x8003, 228}, {0x8006, 228}, {0x800a, 228}, {0x800f, 228}, {0x8018, 228}, {0x801f, 228}, {0x8029, 228}, {0xc038, 228}, }, /* 145 */ { {0x8003, 232}, {0x8006, 232}, {0x800a, 232}, {0x800f, 232}, {0x8018, 232}, {0x801f, 232}, {0x8029, 232}, {0xc038, 232}, {0x8003, 233}, {0x8006, 233}, {0x800a, 233}, {0x800f, 233}, {0x8018, 233}, {0x801f, 233}, {0x8029, 233}, {0xc038, 233}, }, /* 146 */ { {0x8001, 1}, {0xc016, 1}, {0x8001, 135}, {0xc016, 135}, {0x8001, 137}, {0xc016, 137}, {0x8001, 138}, {0xc016, 138}, {0x8001, 139}, {0xc016, 139}, {0x8001, 140}, {0xc016, 140}, {0x8001, 141}, {0xc016, 141}, {0x8001, 143}, {0xc016, 143}, }, /* 147 */ { {0x8002, 1}, {0x8009, 1}, {0x8017, 1}, {0xc028, 1}, {0x8002, 135}, {0x8009, 135}, {0x8017, 135}, {0xc028, 135}, {0x8002, 137}, {0x8009, 137}, {0x8017, 137}, {0xc028, 137}, {0x8002, 138}, {0x8009, 138}, {0x8017, 138}, {0xc028, 138}, }, /* 148 */ { {0x8003, 1}, {0x8006, 1}, {0x800a, 1}, {0x800f, 1}, {0x8018, 1}, {0x801f, 1}, {0x8029, 1}, {0xc038, 1}, {0x8003, 135}, {0x8006, 135}, {0x800a, 135}, {0x800f, 135}, {0x8018, 135}, {0x801f, 135}, {0x8029, 135}, {0xc038, 135}, }, /* 149 */ { {0x8003, 137}, {0x8006, 137}, {0x800a, 137}, {0x800f, 137}, {0x8018, 137}, {0x801f, 137}, {0x8029, 137}, {0xc038, 137}, {0x8003, 138}, {0x8006, 138}, {0x800a, 138}, {0x800f, 138}, {0x8018, 138}, {0x801f, 138}, {0x8029, 138}, {0xc038, 138}, }, /* 150 */ { {0x8002, 139}, {0x8009, 139}, {0x8017, 139}, {0xc028, 139}, {0x8002, 140}, {0x8009, 140}, {0x8017, 140}, {0xc028, 140}, {0x8002, 141}, {0x8009, 141}, {0x8017, 141}, {0xc028, 141}, {0x8002, 143}, {0x8009, 143}, {0x8017, 143}, {0xc028, 143}, }, /* 151 */ { {0x8003, 139}, {0x8006, 139}, {0x800a, 139}, {0x800f, 139}, {0x8018, 139}, {0x801f, 139}, {0x8029, 139}, {0xc038, 139}, {0x8003, 140}, {0x8006, 140}, {0x800a, 140}, {0x800f, 140}, {0x8018, 140}, {0x801f, 140}, {0x8029, 140}, {0xc038, 140}, }, /* 152 */ { {0x8003, 141}, {0x8006, 141}, {0x800a, 141}, {0x800f, 141}, {0x8018, 141}, {0x801f, 141}, {0x8029, 141}, {0xc038, 141}, {0x8003, 143}, {0x8006, 143}, {0x800a, 143}, {0x800f, 143}, {0x8018, 143}, {0x801f, 143}, {0x8029, 143}, {0xc038, 143}, }, /* 153 */ { {0x9d, 0}, {0x9e, 0}, {0xa0, 0}, {0xa1, 0}, {0xa4, 0}, {0xa5, 0}, {0xa7, 0}, {0xa8, 0}, {0xac, 0}, {0xad, 0}, {0xaf, 0}, {0xb1, 0}, {0xb6, 0}, {0xb9, 0}, {0xbf, 0}, {0xcf, 0}, }, /* 154 */ { {0xc000, 147}, {0xc000, 149}, {0xc000, 150}, {0xc000, 151}, {0xc000, 152}, {0xc000, 155}, {0xc000, 157}, {0xc000, 158}, {0xc000, 165}, {0xc000, 166}, {0xc000, 168}, {0xc000, 174}, {0xc000, 175}, {0xc000, 180}, {0xc000, 182}, {0xc000, 183}, }, /* 155 */ { {0x8001, 147}, {0xc016, 147}, {0x8001, 149}, {0xc016, 149}, {0x8001, 150}, {0xc016, 150}, {0x8001, 151}, {0xc016, 151}, {0x8001, 152}, {0xc016, 152}, {0x8001, 155}, {0xc016, 155}, {0x8001, 157}, {0xc016, 157}, {0x8001, 158}, {0xc016, 158}, }, /* 156 */ { {0x8002, 147}, {0x8009, 147}, {0x8017, 147}, {0xc028, 147}, {0x8002, 149}, {0x8009, 149}, {0x8017, 149}, {0xc028, 149}, {0x8002, 150}, {0x8009, 150}, {0x8017, 150}, {0xc028, 150}, {0x8002, 151}, {0x8009, 151}, {0x8017, 151}, {0xc028, 151}, }, /* 157 */ { {0x8003, 147}, {0x8006, 147}, {0x800a, 147}, {0x800f, 147}, {0x8018, 147}, {0x801f, 147}, {0x8029, 147}, {0xc038, 147}, {0x8003, 149}, {0x8006, 149}, {0x800a, 149}, {0x800f, 149}, {0x8018, 149}, {0x801f, 149}, {0x8029, 149}, {0xc038, 149}, }, /* 158 */ { {0x8003, 150}, {0x8006, 150}, {0x800a, 150}, {0x800f, 150}, {0x8018, 150}, {0x801f, 150}, {0x8029, 150}, {0xc038, 150}, {0x8003, 151}, {0x8006, 151}, {0x800a, 151}, {0x800f, 151}, {0x8018, 151}, {0x801f, 151}, {0x8029, 151}, {0xc038, 151}, }, /* 159 */ { {0x8002, 152}, {0x8009, 152}, {0x8017, 152}, {0xc028, 152}, {0x8002, 155}, {0x8009, 155}, {0x8017, 155}, {0xc028, 155}, {0x8002, 157}, {0x8009, 157}, {0x8017, 157}, {0xc028, 157}, {0x8002, 158}, {0x8009, 158}, {0x8017, 158}, {0xc028, 158}, }, /* 160 */ { {0x8003, 152}, {0x8006, 152}, {0x800a, 152}, {0x800f, 152}, {0x8018, 152}, {0x801f, 152}, {0x8029, 152}, {0xc038, 152}, {0x8003, 155}, {0x8006, 155}, {0x800a, 155}, {0x800f, 155}, {0x8018, 155}, {0x801f, 155}, {0x8029, 155}, {0xc038, 155}, }, /* 161 */ { {0x8003, 157}, {0x8006, 157}, {0x800a, 157}, {0x800f, 157}, {0x8018, 157}, {0x801f, 157}, {0x8029, 157}, {0xc038, 157}, {0x8003, 158}, {0x8006, 158}, {0x800a, 158}, {0x800f, 158}, {0x8018, 158}, {0x801f, 158}, {0x8029, 158}, {0xc038, 158}, }, /* 162 */ { {0x8001, 165}, {0xc016, 165}, {0x8001, 166}, {0xc016, 166}, {0x8001, 168}, {0xc016, 168}, {0x8001, 174}, {0xc016, 174}, {0x8001, 175}, {0xc016, 175}, {0x8001, 180}, {0xc016, 180}, {0x8001, 182}, {0xc016, 182}, {0x8001, 183}, {0xc016, 183}, }, /* 163 */ { {0x8002, 165}, {0x8009, 165}, {0x8017, 165}, {0xc028, 165}, {0x8002, 166}, {0x8009, 166}, {0x8017, 166}, {0xc028, 166}, {0x8002, 168}, {0x8009, 168}, {0x8017, 168}, {0xc028, 168}, {0x8002, 174}, {0x8009, 174}, {0x8017, 174}, {0xc028, 174}, }, /* 164 */ { {0x8003, 165}, {0x8006, 165}, {0x800a, 165}, {0x800f, 165}, {0x8018, 165}, {0x801f, 165}, {0x8029, 165}, {0xc038, 165}, {0x8003, 166}, {0x8006, 166}, {0x800a, 166}, {0x800f, 166}, {0x8018, 166}, {0x801f, 166}, {0x8029, 166}, {0xc038, 166}, }, /* 165 */ { {0x8003, 168}, {0x8006, 168}, {0x800a, 168}, {0x800f, 168}, {0x8018, 168}, {0x801f, 168}, {0x8029, 168}, {0xc038, 168}, {0x8003, 174}, {0x8006, 174}, {0x800a, 174}, {0x800f, 174}, {0x8018, 174}, {0x801f, 174}, {0x8029, 174}, {0xc038, 174}, }, /* 166 */ { {0x8002, 175}, {0x8009, 175}, {0x8017, 175}, {0xc028, 175}, {0x8002, 180}, {0x8009, 180}, {0x8017, 180}, {0xc028, 180}, {0x8002, 182}, {0x8009, 182}, {0x8017, 182}, {0xc028, 182}, {0x8002, 183}, {0x8009, 183}, {0x8017, 183}, {0xc028, 183}, }, /* 167 */ { {0x8003, 175}, {0x8006, 175}, {0x800a, 175}, {0x800f, 175}, {0x8018, 175}, {0x801f, 175}, {0x8029, 175}, {0xc038, 175}, {0x8003, 180}, {0x8006, 180}, {0x800a, 180}, {0x800f, 180}, {0x8018, 180}, {0x801f, 180}, {0x8029, 180}, {0xc038, 180}, }, /* 168 */ { {0x8003, 182}, {0x8006, 182}, {0x800a, 182}, {0x800f, 182}, {0x8018, 182}, {0x801f, 182}, {0x8029, 182}, {0xc038, 182}, {0x8003, 183}, {0x8006, 183}, {0x800a, 183}, {0x800f, 183}, {0x8018, 183}, {0x801f, 183}, {0x8029, 183}, {0xc038, 183}, }, /* 169 */ { {0xc000, 188}, {0xc000, 191}, {0xc000, 197}, {0xc000, 231}, {0xc000, 239}, {0xb0, 0}, {0xb2, 0}, {0xb3, 0}, {0xb7, 0}, {0xb8, 0}, {0xba, 0}, {0xbb, 0}, {0xc0, 0}, {0xc7, 0}, {0xd0, 0}, {0xdf, 0}, }, /* 170 */ { {0x8001, 188}, {0xc016, 188}, {0x8001, 191}, {0xc016, 191}, {0x8001, 197}, {0xc016, 197}, {0x8001, 231}, {0xc016, 231}, {0x8001, 239}, {0xc016, 239}, {0xc000, 9}, {0xc000, 142}, {0xc000, 144}, {0xc000, 145}, {0xc000, 148}, {0xc000, 159}, }, /* 171 */ { {0x8002, 188}, {0x8009, 188}, {0x8017, 188}, {0xc028, 188}, {0x8002, 191}, {0x8009, 191}, {0x8017, 191}, {0xc028, 191}, {0x8002, 197}, {0x8009, 197}, {0x8017, 197}, {0xc028, 197}, {0x8002, 231}, {0x8009, 231}, {0x8017, 231}, {0xc028, 231}, }, /* 172 */ { {0x8003, 188}, {0x8006, 188}, {0x800a, 188}, {0x800f, 188}, {0x8018, 188}, {0x801f, 188}, {0x8029, 188}, {0xc038, 188}, {0x8003, 191}, {0x8006, 191}, {0x800a, 191}, {0x800f, 191}, {0x8018, 191}, {0x801f, 191}, {0x8029, 191}, {0xc038, 191}, }, /* 173 */ { {0x8003, 197}, {0x8006, 197}, {0x800a, 197}, {0x800f, 197}, {0x8018, 197}, {0x801f, 197}, {0x8029, 197}, {0xc038, 197}, {0x8003, 231}, {0x8006, 231}, {0x800a, 231}, {0x800f, 231}, {0x8018, 231}, {0x801f, 231}, {0x8029, 231}, {0xc038, 231}, }, /* 174 */ { {0x8002, 239}, {0x8009, 239}, {0x8017, 239}, {0xc028, 239}, {0x8001, 9}, {0xc016, 9}, {0x8001, 142}, {0xc016, 142}, {0x8001, 144}, {0xc016, 144}, {0x8001, 145}, {0xc016, 145}, {0x8001, 148}, {0xc016, 148}, {0x8001, 159}, {0xc016, 159}, }, /* 175 */ { {0x8003, 239}, {0x8006, 239}, {0x800a, 239}, {0x800f, 239}, {0x8018, 239}, {0x801f, 239}, {0x8029, 239}, {0xc038, 239}, {0x8002, 9}, {0x8009, 9}, {0x8017, 9}, {0xc028, 9}, {0x8002, 142}, {0x8009, 142}, {0x8017, 142}, {0xc028, 142}, }, /* 176 */ { {0x8003, 9}, {0x8006, 9}, {0x800a, 9}, {0x800f, 9}, {0x8018, 9}, {0x801f, 9}, {0x8029, 9}, {0xc038, 9}, {0x8003, 142}, {0x8006, 142}, {0x800a, 142}, {0x800f, 142}, {0x8018, 142}, {0x801f, 142}, {0x8029, 142}, {0xc038, 142}, }, /* 177 */ { {0x8002, 144}, {0x8009, 144}, {0x8017, 144}, {0xc028, 144}, {0x8002, 145}, {0x8009, 145}, {0x8017, 145}, {0xc028, 145}, {0x8002, 148}, {0x8009, 148}, {0x8017, 148}, {0xc028, 148}, {0x8002, 159}, {0x8009, 159}, {0x8017, 159}, {0xc028, 159}, }, /* 178 */ { {0x8003, 144}, {0x8006, 144}, {0x800a, 144}, {0x800f, 144}, {0x8018, 144}, {0x801f, 144}, {0x8029, 144}, {0xc038, 144}, {0x8003, 145}, {0x8006, 145}, {0x800a, 145}, {0x800f, 145}, {0x8018, 145}, {0x801f, 145}, {0x8029, 145}, {0xc038, 145}, }, /* 179 */ { {0x8003, 148}, {0x8006, 148}, {0x800a, 148}, {0x800f, 148}, {0x8018, 148}, {0x801f, 148}, {0x8029, 148}, {0xc038, 148}, {0x8003, 159}, {0x8006, 159}, {0x800a, 159}, {0x800f, 159}, {0x8018, 159}, {0x801f, 159}, {0x8029, 159}, {0xc038, 159}, }, /* 180 */ { {0xc000, 171}, {0xc000, 206}, {0xc000, 215}, {0xc000, 225}, {0xc000, 236}, {0xc000, 237}, {0xbc, 0}, {0xbd, 0}, {0xc1, 0}, {0xc4, 0}, {0xc8, 0}, {0xcb, 0}, {0xd1, 0}, {0xd8, 0}, {0xe0, 0}, {0xee, 0}, }, /* 181 */ { {0x8001, 171}, {0xc016, 171}, {0x8001, 206}, {0xc016, 206}, {0x8001, 215}, {0xc016, 215}, {0x8001, 225}, {0xc016, 225}, {0x8001, 236}, {0xc016, 236}, {0x8001, 237}, {0xc016, 237}, {0xc000, 199}, {0xc000, 207}, {0xc000, 234}, {0xc000, 235}, }, /* 182 */ { {0x8002, 171}, {0x8009, 171}, {0x8017, 171}, {0xc028, 171}, {0x8002, 206}, {0x8009, 206}, {0x8017, 206}, {0xc028, 206}, {0x8002, 215}, {0x8009, 215}, {0x8017, 215}, {0xc028, 215}, {0x8002, 225}, {0x8009, 225}, {0x8017, 225}, {0xc028, 225}, }, /* 183 */ { {0x8003, 171}, {0x8006, 171}, {0x800a, 171}, {0x800f, 171}, {0x8018, 171}, {0x801f, 171}, {0x8029, 171}, {0xc038, 171}, {0x8003, 206}, {0x8006, 206}, {0x800a, 206}, {0x800f, 206}, {0x8018, 206}, {0x801f, 206}, {0x8029, 206}, {0xc038, 206}, }, /* 184 */ { {0x8003, 215}, {0x8006, 215}, {0x800a, 215}, {0x800f, 215}, {0x8018, 215}, {0x801f, 215}, {0x8029, 215}, {0xc038, 215}, {0x8003, 225}, {0x8006, 225}, {0x800a, 225}, {0x800f, 225}, {0x8018, 225}, {0x801f, 225}, {0x8029, 225}, {0xc038, 225}, }, /* 185 */ { {0x8002, 236}, {0x8009, 236}, {0x8017, 236}, {0xc028, 236}, {0x8002, 237}, {0x8009, 237}, {0x8017, 237}, {0xc028, 237}, {0x8001, 199}, {0xc016, 199}, {0x8001, 207}, {0xc016, 207}, {0x8001, 234}, {0xc016, 234}, {0x8001, 235}, {0xc016, 235}, }, /* 186 */ { {0x8003, 236}, {0x8006, 236}, {0x800a, 236}, {0x800f, 236}, {0x8018, 236}, {0x801f, 236}, {0x8029, 236}, {0xc038, 236}, {0x8003, 237}, {0x8006, 237}, {0x800a, 237}, {0x800f, 237}, {0x8018, 237}, {0x801f, 237}, {0x8029, 237}, {0xc038, 237}, }, /* 187 */ { {0x8002, 199}, {0x8009, 199}, {0x8017, 199}, {0xc028, 199}, {0x8002, 207}, {0x8009, 207}, {0x8017, 207}, {0xc028, 207}, {0x8002, 234}, {0x8009, 234}, {0x8017, 234}, {0xc028, 234}, {0x8002, 235}, {0x8009, 235}, {0x8017, 235}, {0xc028, 235}, }, /* 188 */ { {0x8003, 199}, {0x8006, 199}, {0x800a, 199}, {0x800f, 199}, {0x8018, 199}, {0x801f, 199}, {0x8029, 199}, {0xc038, 199}, {0x8003, 207}, {0x8006, 207}, {0x800a, 207}, {0x800f, 207}, {0x8018, 207}, {0x801f, 207}, {0x8029, 207}, {0xc038, 207}, }, /* 189 */ { {0x8003, 234}, {0x8006, 234}, {0x800a, 234}, {0x800f, 234}, {0x8018, 234}, {0x801f, 234}, {0x8029, 234}, {0xc038, 234}, {0x8003, 235}, {0x8006, 235}, {0x800a, 235}, {0x800f, 235}, {0x8018, 235}, {0x801f, 235}, {0x8029, 235}, {0xc038, 235}, }, /* 190 */ { {0xc2, 0}, {0xc3, 0}, {0xc5, 0}, {0xc6, 0}, {0xc9, 0}, {0xca, 0}, {0xcc, 0}, {0xcd, 0}, {0xd2, 0}, {0xd5, 0}, {0xd9, 0}, {0xdc, 0}, {0xe1, 0}, {0xe7, 0}, {0xef, 0}, {0xf6, 0}, }, /* 191 */ { {0xc000, 192}, {0xc000, 193}, {0xc000, 200}, {0xc000, 201}, {0xc000, 202}, {0xc000, 205}, {0xc000, 210}, {0xc000, 213}, {0xc000, 218}, {0xc000, 219}, {0xc000, 238}, {0xc000, 240}, {0xc000, 242}, {0xc000, 243}, {0xc000, 255}, {0xce, 0}, }, /* 192 */ { {0x8001, 192}, {0xc016, 192}, {0x8001, 193}, {0xc016, 193}, {0x8001, 200}, {0xc016, 200}, {0x8001, 201}, {0xc016, 201}, {0x8001, 202}, {0xc016, 202}, {0x8001, 205}, {0xc016, 205}, {0x8001, 210}, {0xc016, 210}, {0x8001, 213}, {0xc016, 213}, }, /* 193 */ { {0x8002, 192}, {0x8009, 192}, {0x8017, 192}, {0xc028, 192}, {0x8002, 193}, {0x8009, 193}, {0x8017, 193}, {0xc028, 193}, {0x8002, 200}, {0x8009, 200}, {0x8017, 200}, {0xc028, 200}, {0x8002, 201}, {0x8009, 201}, {0x8017, 201}, {0xc028, 201}, }, /* 194 */ { {0x8003, 192}, {0x8006, 192}, {0x800a, 192}, {0x800f, 192}, {0x8018, 192}, {0x801f, 192}, {0x8029, 192}, {0xc038, 192}, {0x8003, 193}, {0x8006, 193}, {0x800a, 193}, {0x800f, 193}, {0x8018, 193}, {0x801f, 193}, {0x8029, 193}, {0xc038, 193}, }, /* 195 */ { {0x8003, 200}, {0x8006, 200}, {0x800a, 200}, {0x800f, 200}, {0x8018, 200}, {0x801f, 200}, {0x8029, 200}, {0xc038, 200}, {0x8003, 201}, {0x8006, 201}, {0x800a, 201}, {0x800f, 201}, {0x8018, 201}, {0x801f, 201}, {0x8029, 201}, {0xc038, 201}, }, /* 196 */ { {0x8002, 202}, {0x8009, 202}, {0x8017, 202}, {0xc028, 202}, {0x8002, 205}, {0x8009, 205}, {0x8017, 205}, {0xc028, 205}, {0x8002, 210}, {0x8009, 210}, {0x8017, 210}, {0xc028, 210}, {0x8002, 213}, {0x8009, 213}, {0x8017, 213}, {0xc028, 213}, }, /* 197 */ { {0x8003, 202}, {0x8006, 202}, {0x800a, 202}, {0x800f, 202}, {0x8018, 202}, {0x801f, 202}, {0x8029, 202}, {0xc038, 202}, {0x8003, 205}, {0x8006, 205}, {0x800a, 205}, {0x800f, 205}, {0x8018, 205}, {0x801f, 205}, {0x8029, 205}, {0xc038, 205}, }, /* 198 */ { {0x8003, 210}, {0x8006, 210}, {0x800a, 210}, {0x800f, 210}, {0x8018, 210}, {0x801f, 210}, {0x8029, 210}, {0xc038, 210}, {0x8003, 213}, {0x8006, 213}, {0x800a, 213}, {0x800f, 213}, {0x8018, 213}, {0x801f, 213}, {0x8029, 213}, {0xc038, 213}, }, /* 199 */ { {0x8001, 218}, {0xc016, 218}, {0x8001, 219}, {0xc016, 219}, {0x8001, 238}, {0xc016, 238}, {0x8001, 240}, {0xc016, 240}, {0x8001, 242}, {0xc016, 242}, {0x8001, 243}, {0xc016, 243}, {0x8001, 255}, {0xc016, 255}, {0xc000, 203}, {0xc000, 204}, }, /* 200 */ { {0x8002, 218}, {0x8009, 218}, {0x8017, 218}, {0xc028, 218}, {0x8002, 219}, {0x8009, 219}, {0x8017, 219}, {0xc028, 219}, {0x8002, 238}, {0x8009, 238}, {0x8017, 238}, {0xc028, 238}, {0x8002, 240}, {0x8009, 240}, {0x8017, 240}, {0xc028, 240}, }, /* 201 */ { {0x8003, 218}, {0x8006, 218}, {0x800a, 218}, {0x800f, 218}, {0x8018, 218}, {0x801f, 218}, {0x8029, 218}, {0xc038, 218}, {0x8003, 219}, {0x8006, 219}, {0x800a, 219}, {0x800f, 219}, {0x8018, 219}, {0x801f, 219}, {0x8029, 219}, {0xc038, 219}, }, /* 202 */ { {0x8003, 238}, {0x8006, 238}, {0x800a, 238}, {0x800f, 238}, {0x8018, 238}, {0x801f, 238}, {0x8029, 238}, {0xc038, 238}, {0x8003, 240}, {0x8006, 240}, {0x800a, 240}, {0x800f, 240}, {0x8018, 240}, {0x801f, 240}, {0x8029, 240}, {0xc038, 240}, }, /* 203 */ { {0x8002, 242}, {0x8009, 242}, {0x8017, 242}, {0xc028, 242}, {0x8002, 243}, {0x8009, 243}, {0x8017, 243}, {0xc028, 243}, {0x8002, 255}, {0x8009, 255}, {0x8017, 255}, {0xc028, 255}, {0x8001, 203}, {0xc016, 203}, {0x8001, 204}, {0xc016, 204}, }, /* 204 */ { {0x8003, 242}, {0x8006, 242}, {0x800a, 242}, {0x800f, 242}, {0x8018, 242}, {0x801f, 242}, {0x8029, 242}, {0xc038, 242}, {0x8003, 243}, {0x8006, 243}, {0x800a, 243}, {0x800f, 243}, {0x8018, 243}, {0x801f, 243}, {0x8029, 243}, {0xc038, 243}, }, /* 205 */ { {0x8003, 255}, {0x8006, 255}, {0x800a, 255}, {0x800f, 255}, {0x8018, 255}, {0x801f, 255}, {0x8029, 255}, {0xc038, 255}, {0x8002, 203}, {0x8009, 203}, {0x8017, 203}, {0xc028, 203}, {0x8002, 204}, {0x8009, 204}, {0x8017, 204}, {0xc028, 204}, }, /* 206 */ { {0x8003, 203}, {0x8006, 203}, {0x800a, 203}, {0x800f, 203}, {0x8018, 203}, {0x801f, 203}, {0x8029, 203}, {0xc038, 203}, {0x8003, 204}, {0x8006, 204}, {0x800a, 204}, {0x800f, 204}, {0x8018, 204}, {0x801f, 204}, {0x8029, 204}, {0xc038, 204}, }, /* 207 */ { {0xd3, 0}, {0xd4, 0}, {0xd6, 0}, {0xd7, 0}, {0xda, 0}, {0xdb, 0}, {0xdd, 0}, {0xde, 0}, {0xe2, 0}, {0xe4, 0}, {0xe8, 0}, {0xeb, 0}, {0xf0, 0}, {0xf3, 0}, {0xf7, 0}, {0xfa, 0}, }, /* 208 */ { {0xc000, 211}, {0xc000, 212}, {0xc000, 214}, {0xc000, 221}, {0xc000, 222}, {0xc000, 223}, {0xc000, 241}, {0xc000, 244}, {0xc000, 245}, {0xc000, 246}, {0xc000, 247}, {0xc000, 248}, {0xc000, 250}, {0xc000, 251}, {0xc000, 252}, {0xc000, 253}, }, /* 209 */ { {0x8001, 211}, {0xc016, 211}, {0x8001, 212}, {0xc016, 212}, {0x8001, 214}, {0xc016, 214}, {0x8001, 221}, {0xc016, 221}, {0x8001, 222}, {0xc016, 222}, {0x8001, 223}, {0xc016, 223}, {0x8001, 241}, {0xc016, 241}, {0x8001, 244}, {0xc016, 244}, }, /* 210 */ { {0x8002, 211}, {0x8009, 211}, {0x8017, 211}, {0xc028, 211}, {0x8002, 212}, {0x8009, 212}, {0x8017, 212}, {0xc028, 212}, {0x8002, 214}, {0x8009, 214}, {0x8017, 214}, {0xc028, 214}, {0x8002, 221}, {0x8009, 221}, {0x8017, 221}, {0xc028, 221}, }, /* 211 */ { {0x8003, 211}, {0x8006, 211}, {0x800a, 211}, {0x800f, 211}, {0x8018, 211}, {0x801f, 211}, {0x8029, 211}, {0xc038, 211}, {0x8003, 212}, {0x8006, 212}, {0x800a, 212}, {0x800f, 212}, {0x8018, 212}, {0x801f, 212}, {0x8029, 212}, {0xc038, 212}, }, /* 212 */ { {0x8003, 214}, {0x8006, 214}, {0x800a, 214}, {0x800f, 214}, {0x8018, 214}, {0x801f, 214}, {0x8029, 214}, {0xc038, 214}, {0x8003, 221}, {0x8006, 221}, {0x800a, 221}, {0x800f, 221}, {0x8018, 221}, {0x801f, 221}, {0x8029, 221}, {0xc038, 221}, }, /* 213 */ { {0x8002, 222}, {0x8009, 222}, {0x8017, 222}, {0xc028, 222}, {0x8002, 223}, {0x8009, 223}, {0x8017, 223}, {0xc028, 223}, {0x8002, 241}, {0x8009, 241}, {0x8017, 241}, {0xc028, 241}, {0x8002, 244}, {0x8009, 244}, {0x8017, 244}, {0xc028, 244}, }, /* 214 */ { {0x8003, 222}, {0x8006, 222}, {0x800a, 222}, {0x800f, 222}, {0x8018, 222}, {0x801f, 222}, {0x8029, 222}, {0xc038, 222}, {0x8003, 223}, {0x8006, 223}, {0x800a, 223}, {0x800f, 223}, {0x8018, 223}, {0x801f, 223}, {0x8029, 223}, {0xc038, 223}, }, /* 215 */ { {0x8003, 241}, {0x8006, 241}, {0x800a, 241}, {0x800f, 241}, {0x8018, 241}, {0x801f, 241}, {0x8029, 241}, {0xc038, 241}, {0x8003, 244}, {0x8006, 244}, {0x800a, 244}, {0x800f, 244}, {0x8018, 244}, {0x801f, 244}, {0x8029, 244}, {0xc038, 244}, }, /* 216 */ { {0x8001, 245}, {0xc016, 245}, {0x8001, 246}, {0xc016, 246}, {0x8001, 247}, {0xc016, 247}, {0x8001, 248}, {0xc016, 248}, {0x8001, 250}, {0xc016, 250}, {0x8001, 251}, {0xc016, 251}, {0x8001, 252}, {0xc016, 252}, {0x8001, 253}, {0xc016, 253}, }, /* 217 */ { {0x8002, 245}, {0x8009, 245}, {0x8017, 245}, {0xc028, 245}, {0x8002, 246}, {0x8009, 246}, {0x8017, 246}, {0xc028, 246}, {0x8002, 247}, {0x8009, 247}, {0x8017, 247}, {0xc028, 247}, {0x8002, 248}, {0x8009, 248}, {0x8017, 248}, {0xc028, 248}, }, /* 218 */ { {0x8003, 245}, {0x8006, 245}, {0x800a, 245}, {0x800f, 245}, {0x8018, 245}, {0x801f, 245}, {0x8029, 245}, {0xc038, 245}, {0x8003, 246}, {0x8006, 246}, {0x800a, 246}, {0x800f, 246}, {0x8018, 246}, {0x801f, 246}, {0x8029, 246}, {0xc038, 246}, }, /* 219 */ { {0x8003, 247}, {0x8006, 247}, {0x800a, 247}, {0x800f, 247}, {0x8018, 247}, {0x801f, 247}, {0x8029, 247}, {0xc038, 247}, {0x8003, 248}, {0x8006, 248}, {0x800a, 248}, {0x800f, 248}, {0x8018, 248}, {0x801f, 248}, {0x8029, 248}, {0xc038, 248}, }, /* 220 */ { {0x8002, 250}, {0x8009, 250}, {0x8017, 250}, {0xc028, 250}, {0x8002, 251}, {0x8009, 251}, {0x8017, 251}, {0xc028, 251}, {0x8002, 252}, {0x8009, 252}, {0x8017, 252}, {0xc028, 252}, {0x8002, 253}, {0x8009, 253}, {0x8017, 253}, {0xc028, 253}, }, /* 221 */ { {0x8003, 250}, {0x8006, 250}, {0x800a, 250}, {0x800f, 250}, {0x8018, 250}, {0x801f, 250}, {0x8029, 250}, {0xc038, 250}, {0x8003, 251}, {0x8006, 251}, {0x800a, 251}, {0x800f, 251}, {0x8018, 251}, {0x801f, 251}, {0x8029, 251}, {0xc038, 251}, }, /* 222 */ { {0x8003, 252}, {0x8006, 252}, {0x800a, 252}, {0x800f, 252}, {0x8018, 252}, {0x801f, 252}, {0x8029, 252}, {0xc038, 252}, {0x8003, 253}, {0x8006, 253}, {0x800a, 253}, {0x800f, 253}, {0x8018, 253}, {0x801f, 253}, {0x8029, 253}, {0xc038, 253}, }, /* 223 */ { {0xc000, 254}, {0xe3, 0}, {0xe5, 0}, {0xe6, 0}, {0xe9, 0}, {0xea, 0}, {0xec, 0}, {0xed, 0}, {0xf1, 0}, {0xf2, 0}, {0xf4, 0}, {0xf5, 0}, {0xf8, 0}, {0xf9, 0}, {0xfb, 0}, {0xfc, 0}, }, /* 224 */ { {0x8001, 254}, {0xc016, 254}, {0xc000, 2}, {0xc000, 3}, {0xc000, 4}, {0xc000, 5}, {0xc000, 6}, {0xc000, 7}, {0xc000, 8}, {0xc000, 11}, {0xc000, 12}, {0xc000, 14}, {0xc000, 15}, {0xc000, 16}, {0xc000, 17}, {0xc000, 18}, }, /* 225 */ { {0x8002, 254}, {0x8009, 254}, {0x8017, 254}, {0xc028, 254}, {0x8001, 2}, {0xc016, 2}, {0x8001, 3}, {0xc016, 3}, {0x8001, 4}, {0xc016, 4}, {0x8001, 5}, {0xc016, 5}, {0x8001, 6}, {0xc016, 6}, {0x8001, 7}, {0xc016, 7}, }, /* 226 */ { {0x8003, 254}, {0x8006, 254}, {0x800a, 254}, {0x800f, 254}, {0x8018, 254}, {0x801f, 254}, {0x8029, 254}, {0xc038, 254}, {0x8002, 2}, {0x8009, 2}, {0x8017, 2}, {0xc028, 2}, {0x8002, 3}, {0x8009, 3}, {0x8017, 3}, {0xc028, 3}, }, /* 227 */ { {0x8003, 2}, {0x8006, 2}, {0x800a, 2}, {0x800f, 2}, {0x8018, 2}, {0x801f, 2}, {0x8029, 2}, {0xc038, 2}, {0x8003, 3}, {0x8006, 3}, {0x800a, 3}, {0x800f, 3}, {0x8018, 3}, {0x801f, 3}, {0x8029, 3}, {0xc038, 3}, }, /* 228 */ { {0x8002, 4}, {0x8009, 4}, {0x8017, 4}, {0xc028, 4}, {0x8002, 5}, {0x8009, 5}, {0x8017, 5}, {0xc028, 5}, {0x8002, 6}, {0x8009, 6}, {0x8017, 6}, {0xc028, 6}, {0x8002, 7}, {0x8009, 7}, {0x8017, 7}, {0xc028, 7}, }, /* 229 */ { {0x8003, 4}, {0x8006, 4}, {0x800a, 4}, {0x800f, 4}, {0x8018, 4}, {0x801f, 4}, {0x8029, 4}, {0xc038, 4}, {0x8003, 5}, {0x8006, 5}, {0x800a, 5}, {0x800f, 5}, {0x8018, 5}, {0x801f, 5}, {0x8029, 5}, {0xc038, 5}, }, /* 230 */ { {0x8003, 6}, {0x8006, 6}, {0x800a, 6}, {0x800f, 6}, {0x8018, 6}, {0x801f, 6}, {0x8029, 6}, {0xc038, 6}, {0x8003, 7}, {0x8006, 7}, {0x800a, 7}, {0x800f, 7}, {0x8018, 7}, {0x801f, 7}, {0x8029, 7}, {0xc038, 7}, }, /* 231 */ { {0x8001, 8}, {0xc016, 8}, {0x8001, 11}, {0xc016, 11}, {0x8001, 12}, {0xc016, 12}, {0x8001, 14}, {0xc016, 14}, {0x8001, 15}, {0xc016, 15}, {0x8001, 16}, {0xc016, 16}, {0x8001, 17}, {0xc016, 17}, {0x8001, 18}, {0xc016, 18}, }, /* 232 */ { {0x8002, 8}, {0x8009, 8}, {0x8017, 8}, {0xc028, 8}, {0x8002, 11}, {0x8009, 11}, {0x8017, 11}, {0xc028, 11}, {0x8002, 12}, {0x8009, 12}, {0x8017, 12}, {0xc028, 12}, {0x8002, 14}, {0x8009, 14}, {0x8017, 14}, {0xc028, 14}, }, /* 233 */ { {0x8003, 8}, {0x8006, 8}, {0x800a, 8}, {0x800f, 8}, {0x8018, 8}, {0x801f, 8}, {0x8029, 8}, {0xc038, 8}, {0x8003, 11}, {0x8006, 11}, {0x800a, 11}, {0x800f, 11}, {0x8018, 11}, {0x801f, 11}, {0x8029, 11}, {0xc038, 11}, }, /* 234 */ { {0x8003, 12}, {0x8006, 12}, {0x800a, 12}, {0x800f, 12}, {0x8018, 12}, {0x801f, 12}, {0x8029, 12}, {0xc038, 12}, {0x8003, 14}, {0x8006, 14}, {0x800a, 14}, {0x800f, 14}, {0x8018, 14}, {0x801f, 14}, {0x8029, 14}, {0xc038, 14}, }, /* 235 */ { {0x8002, 15}, {0x8009, 15}, {0x8017, 15}, {0xc028, 15}, {0x8002, 16}, {0x8009, 16}, {0x8017, 16}, {0xc028, 16}, {0x8002, 17}, {0x8009, 17}, {0x8017, 17}, {0xc028, 17}, {0x8002, 18}, {0x8009, 18}, {0x8017, 18}, {0xc028, 18}, }, /* 236 */ { {0x8003, 15}, {0x8006, 15}, {0x800a, 15}, {0x800f, 15}, {0x8018, 15}, {0x801f, 15}, {0x8029, 15}, {0xc038, 15}, {0x8003, 16}, {0x8006, 16}, {0x800a, 16}, {0x800f, 16}, {0x8018, 16}, {0x801f, 16}, {0x8029, 16}, {0xc038, 16}, }, /* 237 */ { {0x8003, 17}, {0x8006, 17}, {0x800a, 17}, {0x800f, 17}, {0x8018, 17}, {0x801f, 17}, {0x8029, 17}, {0xc038, 17}, {0x8003, 18}, {0x8006, 18}, {0x800a, 18}, {0x800f, 18}, {0x8018, 18}, {0x801f, 18}, {0x8029, 18}, {0xc038, 18}, }, /* 238 */ { {0xc000, 19}, {0xc000, 20}, {0xc000, 21}, {0xc000, 23}, {0xc000, 24}, {0xc000, 25}, {0xc000, 26}, {0xc000, 27}, {0xc000, 28}, {0xc000, 29}, {0xc000, 30}, {0xc000, 31}, {0xc000, 127}, {0xc000, 220}, {0xc000, 249}, {0xfd, 0}, }, /* 239 */ { {0x8001, 19}, {0xc016, 19}, {0x8001, 20}, {0xc016, 20}, {0x8001, 21}, {0xc016, 21}, {0x8001, 23}, {0xc016, 23}, {0x8001, 24}, {0xc016, 24}, {0x8001, 25}, {0xc016, 25}, {0x8001, 26}, {0xc016, 26}, {0x8001, 27}, {0xc016, 27}, }, /* 240 */ { {0x8002, 19}, {0x8009, 19}, {0x8017, 19}, {0xc028, 19}, {0x8002, 20}, {0x8009, 20}, {0x8017, 20}, {0xc028, 20}, {0x8002, 21}, {0x8009, 21}, {0x8017, 21}, {0xc028, 21}, {0x8002, 23}, {0x8009, 23}, {0x8017, 23}, {0xc028, 23}, }, /* 241 */ { {0x8003, 19}, {0x8006, 19}, {0x800a, 19}, {0x800f, 19}, {0x8018, 19}, {0x801f, 19}, {0x8029, 19}, {0xc038, 19}, {0x8003, 20}, {0x8006, 20}, {0x800a, 20}, {0x800f, 20}, {0x8018, 20}, {0x801f, 20}, {0x8029, 20}, {0xc038, 20}, }, /* 242 */ { {0x8003, 21}, {0x8006, 21}, {0x800a, 21}, {0x800f, 21}, {0x8018, 21}, {0x801f, 21}, {0x8029, 21}, {0xc038, 21}, {0x8003, 23}, {0x8006, 23}, {0x800a, 23}, {0x800f, 23}, {0x8018, 23}, {0x801f, 23}, {0x8029, 23}, {0xc038, 23}, }, /* 243 */ { {0x8002, 24}, {0x8009, 24}, {0x8017, 24}, {0xc028, 24}, {0x8002, 25}, {0x8009, 25}, {0x8017, 25}, {0xc028, 25}, {0x8002, 26}, {0x8009, 26}, {0x8017, 26}, {0xc028, 26}, {0x8002, 27}, {0x8009, 27}, {0x8017, 27}, {0xc028, 27}, }, /* 244 */ { {0x8003, 24}, {0x8006, 24}, {0x800a, 24}, {0x800f, 24}, {0x8018, 24}, {0x801f, 24}, {0x8029, 24}, {0xc038, 24}, {0x8003, 25}, {0x8006, 25}, {0x800a, 25}, {0x800f, 25}, {0x8018, 25}, {0x801f, 25}, {0x8029, 25}, {0xc038, 25}, }, /* 245 */ { {0x8003, 26}, {0x8006, 26}, {0x800a, 26}, {0x800f, 26}, {0x8018, 26}, {0x801f, 26}, {0x8029, 26}, {0xc038, 26}, {0x8003, 27}, {0x8006, 27}, {0x800a, 27}, {0x800f, 27}, {0x8018, 27}, {0x801f, 27}, {0x8029, 27}, {0xc038, 27}, }, /* 246 */ { {0x8001, 28}, {0xc016, 28}, {0x8001, 29}, {0xc016, 29}, {0x8001, 30}, {0xc016, 30}, {0x8001, 31}, {0xc016, 31}, {0x8001, 127}, {0xc016, 127}, {0x8001, 220}, {0xc016, 220}, {0x8001, 249}, {0xc016, 249}, {0xfe, 0}, {0xff, 0}, }, /* 247 */ { {0x8002, 28}, {0x8009, 28}, {0x8017, 28}, {0xc028, 28}, {0x8002, 29}, {0x8009, 29}, {0x8017, 29}, {0xc028, 29}, {0x8002, 30}, {0x8009, 30}, {0x8017, 30}, {0xc028, 30}, {0x8002, 31}, {0x8009, 31}, {0x8017, 31}, {0xc028, 31}, }, /* 248 */ { {0x8003, 28}, {0x8006, 28}, {0x800a, 28}, {0x800f, 28}, {0x8018, 28}, {0x801f, 28}, {0x8029, 28}, {0xc038, 28}, {0x8003, 29}, {0x8006, 29}, {0x800a, 29}, {0x800f, 29}, {0x8018, 29}, {0x801f, 29}, {0x8029, 29}, {0xc038, 29}, }, /* 249 */ { {0x8003, 30}, {0x8006, 30}, {0x800a, 30}, {0x800f, 30}, {0x8018, 30}, {0x801f, 30}, {0x8029, 30}, {0xc038, 30}, {0x8003, 31}, {0x8006, 31}, {0x800a, 31}, {0x800f, 31}, {0x8018, 31}, {0x801f, 31}, {0x8029, 31}, {0xc038, 31}, }, /* 250 */ { {0x8002, 127}, {0x8009, 127}, {0x8017, 127}, {0xc028, 127}, {0x8002, 220}, {0x8009, 220}, {0x8017, 220}, {0xc028, 220}, {0x8002, 249}, {0x8009, 249}, {0x8017, 249}, {0xc028, 249}, {0xc000, 10}, {0xc000, 13}, {0xc000, 22}, {0x100, 0}, }, /* 251 */ { {0x8003, 127}, {0x8006, 127}, {0x800a, 127}, {0x800f, 127}, {0x8018, 127}, {0x801f, 127}, {0x8029, 127}, {0xc038, 127}, {0x8003, 220}, {0x8006, 220}, {0x800a, 220}, {0x800f, 220}, {0x8018, 220}, {0x801f, 220}, {0x8029, 220}, {0xc038, 220}, }, /* 252 */ { {0x8003, 249}, {0x8006, 249}, {0x800a, 249}, {0x800f, 249}, {0x8018, 249}, {0x801f, 249}, {0x8029, 249}, {0xc038, 249}, {0x8001, 10}, {0xc016, 10}, {0x8001, 13}, {0xc016, 13}, {0x8001, 22}, {0xc016, 22}, {0x100, 0}, {0x100, 0}, }, /* 253 */ { {0x8002, 10}, {0x8009, 10}, {0x8017, 10}, {0xc028, 10}, {0x8002, 13}, {0x8009, 13}, {0x8017, 13}, {0xc028, 13}, {0x8002, 22}, {0x8009, 22}, {0x8017, 22}, {0xc028, 22}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, }, /* 254 */ { {0x8003, 10}, {0x8006, 10}, {0x800a, 10}, {0x800f, 10}, {0x8018, 10}, {0x801f, 10}, {0x8029, 10}, {0xc038, 10}, {0x8003, 13}, {0x8006, 13}, {0x800a, 13}, {0x800f, 13}, {0x8018, 13}, {0x801f, 13}, {0x8029, 13}, {0xc038, 13}, }, /* 255 */ { {0x8003, 22}, {0x8006, 22}, {0x800a, 22}, {0x800f, 22}, {0x8018, 22}, {0x801f, 22}, {0x8029, 22}, {0xc038, 22}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, }, /* 256 */ { {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, {0x100, 0}, }, }; ================================================ FILE: thirdparty/nghttp2/nghttp2_helper.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2012 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2.h" #include "nghttp2_mem.h" #include #include #include const char *nghttp2_strerror(int error_code) { switch (error_code) { case 0: return "Success"; case NGHTTP2_ERR_INVALID_ARGUMENT: return "Invalid argument"; case NGHTTP2_ERR_BUFFER_ERROR: return "Out of buffer space"; case NGHTTP2_ERR_UNSUPPORTED_VERSION: return "Unsupported SPDY version"; case NGHTTP2_ERR_WOULDBLOCK: return "Operation would block"; case NGHTTP2_ERR_PROTO: return "Protocol error"; case NGHTTP2_ERR_INVALID_FRAME: return "Invalid frame octets"; case NGHTTP2_ERR_EOF: return "EOF"; case NGHTTP2_ERR_DEFERRED: return "Data transfer deferred"; case NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: return "No more Stream ID available"; case NGHTTP2_ERR_STREAM_CLOSED: return "Stream was already closed or invalid"; case NGHTTP2_ERR_STREAM_CLOSING: return "Stream is closing"; case NGHTTP2_ERR_STREAM_SHUT_WR: return "The transmission is not allowed for this stream"; case NGHTTP2_ERR_INVALID_STREAM_ID: return "Stream ID is invalid"; case NGHTTP2_ERR_INVALID_STREAM_STATE: return "Invalid stream state"; case NGHTTP2_ERR_DEFERRED_DATA_EXIST: return "Another DATA frame has already been deferred"; case NGHTTP2_ERR_START_STREAM_NOT_ALLOWED: return "request HEADERS is not allowed"; case NGHTTP2_ERR_GOAWAY_ALREADY_SENT: return "GOAWAY has already been sent"; case NGHTTP2_ERR_INVALID_HEADER_BLOCK: return "Invalid header block"; case NGHTTP2_ERR_INVALID_STATE: return "Invalid state"; case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE: return "The user callback function failed due to the temporal error"; case NGHTTP2_ERR_FRAME_SIZE_ERROR: return "The length of the frame is invalid"; case NGHTTP2_ERR_HEADER_COMP: return "Header compression/decompression error"; case NGHTTP2_ERR_FLOW_CONTROL: return "Flow control error"; case NGHTTP2_ERR_INSUFF_BUFSIZE: return "Insufficient buffer size given to function"; case NGHTTP2_ERR_PAUSE: return "Callback was paused by the application"; case NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS: return "Too many inflight SETTINGS"; case NGHTTP2_ERR_PUSH_DISABLED: return "Server push is disabled by peer"; case NGHTTP2_ERR_DATA_EXIST: return "DATA or HEADERS frame has already been submitted for the stream"; case NGHTTP2_ERR_SESSION_CLOSING: return "The current session is closing"; case NGHTTP2_ERR_HTTP_HEADER: return "Invalid HTTP header field was received"; case NGHTTP2_ERR_HTTP_MESSAGING: return "Violation in HTTP messaging rule"; case NGHTTP2_ERR_REFUSED_STREAM: return "Stream was refused"; case NGHTTP2_ERR_INTERNAL: return "Internal error"; case NGHTTP2_ERR_CANCEL: return "Cancel"; case NGHTTP2_ERR_SETTINGS_EXPECTED: return "When a local endpoint expects to receive SETTINGS frame, it " "receives an other type of frame"; case NGHTTP2_ERR_NOMEM: return "Out of memory"; case NGHTTP2_ERR_CALLBACK_FAILURE: return "The user callback function failed"; case NGHTTP2_ERR_BAD_CLIENT_MAGIC: return "Received bad client magic byte string"; case NGHTTP2_ERR_FLOODED: return "Flooding was detected in this HTTP/2 session, and it must be " "closed"; case NGHTTP2_ERR_TOO_MANY_SETTINGS: return "SETTINGS frame contained more than the maximum allowed entries"; case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS: return "Too many CONTINUATION frames following a HEADER frame"; default: return "Unknown error code"; } } ================================================ FILE: thirdparty/nghttp2/nghttp2_mem.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_mem.h" static void *default_malloc(size_t size, void *mem_user_data) { (void)mem_user_data; return malloc(size); } static void default_free(void *ptr, void *mem_user_data) { (void)mem_user_data; free(ptr); } static void *default_calloc(size_t nmemb, size_t size, void *mem_user_data) { (void)mem_user_data; return calloc(nmemb, size); } static void *default_realloc(void *ptr, size_t size, void *mem_user_data) { (void)mem_user_data; return realloc(ptr, size); } static nghttp2_mem mem_default = {NULL, default_malloc, default_free, default_calloc, default_realloc}; nghttp2_mem *nghttp2_mem_default(void) { return &mem_default; } void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size) { return mem->malloc(size, mem->mem_user_data); } void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) { mem->free(ptr, mem->mem_user_data); } void nghttp2_mem_free2(nghttp2_free free_func, void *ptr, void *mem_user_data) { free_func(ptr, mem_user_data); } void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) { return mem->calloc(nmemb, size, mem->mem_user_data); } void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size) { return mem->realloc(ptr, size, mem->mem_user_data); } ================================================ FILE: thirdparty/nghttp2/nghttp2_mem.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2014 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_MEM_H #define NGHTTP2_MEM_H #include "nghttp2.h" /* The default, system standard memory allocator */ nghttp2_mem *nghttp2_mem_default(void); /* Convenient wrapper functions to call allocator function in |mem|. */ void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size); void nghttp2_mem_free(nghttp2_mem *mem, void *ptr); void nghttp2_mem_free2(nghttp2_free free_func, void *ptr, void *mem_user_data); void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size); void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size); #endif /* NGHTTP2_MEM_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2_rcbuf.c ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2016 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "nghttp2_rcbuf.h" #include #include #include "nghttp2_mem.h" int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem) { uint8_t *p; p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size); if (p == NULL) { return NGHTTP2_ERR_NOMEM; } *rcbuf_ptr = (void *)p; (*rcbuf_ptr)->mem_user_data = mem->mem_user_data; (*rcbuf_ptr)->free = mem->free; (*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf); (*rcbuf_ptr)->len = size; (*rcbuf_ptr)->ref = 1; return 0; } int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, size_t srclen, nghttp2_mem *mem) { int rv; rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem); if (rv != 0) { return rv; } (*rcbuf_ptr)->len = srclen; *nghttp2_cpymem((*rcbuf_ptr)->base, src, srclen) = '\0'; return 0; } /* * Frees |rcbuf| itself, regardless of its reference cout. */ void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) { nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data); } void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) { if (rcbuf->ref == -1) { return; } ++rcbuf->ref; } void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) { if (rcbuf == NULL || rcbuf->ref == -1) { return; } assert(rcbuf->ref > 0); if (--rcbuf->ref == 0) { nghttp2_rcbuf_del(rcbuf); } } nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) { nghttp2_vec res = {rcbuf->base, rcbuf->len}; return res; } ================================================ FILE: thirdparty/nghttp2/nghttp2_rcbuf.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2016 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2_RCBUF_H #define NGHTTP2_RCBUF_H #include "nghttp2.h" struct nghttp2_rcbuf { /* custom memory allocator belongs to the mem parameter when creating this object. */ void *mem_user_data; nghttp2_free free; /* The pointer to the underlying buffer */ uint8_t *base; /* Size of buffer pointed by |base|. */ size_t len; /* Reference count */ int32_t ref; }; /* * Allocates nghttp2_rcbuf object with |size| as initial buffer size. * When the function succeeds, the reference count becomes 1. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM: * Out of memory. */ int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem); /* * Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of * length |srclen|. This function allocates additional byte at the * end and puts '\0' into it, so that the resulting buffer could be * used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to * |srclen|. * * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM: * Out of memory. */ int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, size_t srclen, nghttp2_mem *mem); /* * Frees |rcbuf| itself, regardless of its reference cout. */ void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf); #endif /* NGHTTP2_RCBUF_H */ ================================================ FILE: thirdparty/nghttp2/nghttp2ver.h ================================================ /* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NGHTTP2VER_H #define NGHTTP2VER_H /** * @macro * Version number of the nghttp2 library release */ #define NGHTTP2_VERSION "1.64.0" /** * @macro * Numerical representation of the version number of the nghttp2 library * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ #define NGHTTP2_VERSION_NUM 0x014000 #endif /* NGHTTP2VER_H */ ================================================ FILE: thirdparty/nlohmann/LICENSE.MIT ================================================ MIT License Copyright (c) 2013-2021 Niels Lohmann 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. ================================================ FILE: thirdparty/nlohmann/adl_serializer.hpp ================================================ #pragma once #include #include #include namespace nlohmann { template struct adl_serializer { /*! @brief convert a JSON value to any value type This function is usually called by the `get()` function of the @ref basic_json class (either explicit or via conversion operators). @param[in] j JSON value to read from @param[in,out] val value to write to */ template static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( noexcept(::nlohmann::from_json(std::forward(j), val))) -> decltype(::nlohmann::from_json(std::forward(j), val), void()) { ::nlohmann::from_json(std::forward(j), val); } /*! @brief convert any value type to a JSON value This function is usually called by the constructors of the @ref basic_json class. @param[in,out] j JSON value to write to @param[in] val value to read from */ template static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( noexcept(::nlohmann::to_json(j, std::forward(val)))) -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) { ::nlohmann::to_json(j, std::forward(val)); } }; } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/conversions/from_json.hpp ================================================ #pragma once #include // transform #include // array #include // and, not #include // forward_list #include // inserter, front_inserter, end #include // map #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible #include // unordered_map #include // pair, declval #include // valarray #include #include #include #include #include namespace nlohmann { namespace detail { template void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(not j.is_null())) { JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); } n = nullptr; } // overloads for basic_json template parameters template::value and not std::is_same::value, int> = 0> void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(not j.is_boolean())) { JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); } b = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(not j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template < typename BasicJsonType, typename ConstructibleStringType, enable_if_t < is_constructible_string_type::value and not std::is_same::value, int > = 0 > void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_HEDLEY_UNLIKELY(not j.is_string())) { JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); } s = *j.template get_ptr(); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); e = static_cast(val); } // forward_list doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(not j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.clear(); std::transform(j.rbegin(), j.rend(), std::front_inserter(l), [](const BasicJsonType & i) { return i.template get(); }); } // valarray doesn't have an insert method template::value, int> = 0> void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(not j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } l.resize(j.size()); std::copy(j.begin(), j.end(), std::begin(l)); } template auto from_json(const BasicJsonType& j, T (&arr)[N]) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); } } template auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( arr.reserve(std::declval()), j.template get(), void()) { using std::end; ConstructibleArrayType ret; ret.reserve(j.size()); std::transform(j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; ConstructibleArrayType ret; std::transform( j.begin(), j.end(), std::inserter(ret, end(ret)), [](const BasicJsonType & i) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType return i.template get(); }); arr = std::move(ret); } template ::value and not is_constructible_object_type::value and not is_constructible_string_type::value and not is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), j.template get(), void()) { if (JSON_HEDLEY_UNLIKELY(not j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } from_json_array_impl(j, arr, priority_tag<3> {}); } template::value, int> = 0> void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(not j.is_object())) { JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); } ConstructibleObjectType ret; auto inner_object = j.template get_ptr(); using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(ret, ret.begin()), [](typename BasicJsonType::object_t::value_type const & p) { return value_type(p.first, p.second.template get()); }); obj = std::move(ret); } // overload for arithmetic types, not chosen for basic_json template arguments // (BooleanType, etc..); note: Is it really necessary to provide explicit // overloads for boolean_t etc. in case of a custom BooleanType which is not // an arithmetic type? template::value and not std::is_same::value and not std::is_same::value and not std::is_same::value and not std::is_same::value, int> = 0> void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { case value_t::number_unsigned: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_integer: { val = static_cast(*j.template get_ptr()); break; } case value_t::number_float: { val = static_cast(*j.template get_ptr()); break; } case value_t::boolean: { val = static_cast(*j.template get_ptr()); break; } default: JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); } } template void from_json(const BasicJsonType& j, std::pair& p) { p = {j.at(0).template get(), j.at(1).template get()}; } template void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) { t = std::make_tuple(j.at(Idx).template get::type>()...); } template void from_json(const BasicJsonType& j, std::tuple& t) { from_json_tuple_impl(j, t, index_sequence_for {}); } template ::value>> void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(not j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(not p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } template ::value>> void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(not j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); } m.clear(); for (const auto& p : j) { if (JSON_HEDLEY_UNLIKELY(not p.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); } m.emplace(p.at(0).template get(), p.at(1).template get()); } } struct from_json_fn { template auto operator()(const BasicJsonType& j, T& val) const noexcept(noexcept(from_json(j, val))) -> decltype(from_json(j, val), void()) { return from_json(j, val); } }; } // namespace detail /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace { constexpr const auto& from_json = detail::static_const::value; } // namespace } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/conversions/to_chars.hpp ================================================ #pragma once #include // array #include // assert #include // or, and, not #include // signbit, isfinite #include // intN_t, uintN_t #include // memcpy, memmove #include // numeric_limits #include // conditional #include namespace nlohmann { namespace detail { /*! @brief implements the Grisu2 algorithm for binary to decimal floating-point conversion. This implementation is a slightly modified version of the reference implementation which may be obtained from http://florian.loitsch.com/publications (bench.tar.gz). The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996 */ namespace dtoa_impl { template Target reinterpret_bits(const Source source) { static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); Target target; std::memcpy(&target, &source, sizeof(Source)); return target; } struct diyfp // f * 2^e { static constexpr int kPrecision = 64; // = q std::uint64_t f = 0; int e = 0; constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} /*! @brief returns x - y @pre x.e == y.e and x.f >= y.f */ static diyfp sub(const diyfp& x, const diyfp& y) noexcept { assert(x.e == y.e); assert(x.f >= y.f); return {x.f - y.f, x.e}; } /*! @brief returns x * y @note The result is rounded. (Only the upper q bits are returned.) */ static diyfp mul(const diyfp& x, const diyfp& y) noexcept { static_assert(kPrecision == 64, "internal error"); // Computes: // f = round((x.f * y.f) / 2^q) // e = x.e + y.e + q // Emulate the 64-bit * 64-bit multiplication: // // p = u * v // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) // // (Since Q might be larger than 2^32 - 1) // // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) // // (Q_hi + H does not overflow a 64-bit int) // // = p_lo + 2^64 p_hi const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; const std::uint64_t u_hi = x.f >> 32u; const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; const std::uint64_t v_hi = y.f >> 32u; const std::uint64_t p0 = u_lo * v_lo; const std::uint64_t p1 = u_lo * v_hi; const std::uint64_t p2 = u_hi * v_lo; const std::uint64_t p3 = u_hi * v_hi; const std::uint64_t p0_hi = p0 >> 32u; const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; const std::uint64_t p1_hi = p1 >> 32u; const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; const std::uint64_t p2_hi = p2 >> 32u; std::uint64_t Q = p0_hi + p1_lo + p2_lo; // The full product might now be computed as // // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) // p_lo = p0_lo + (Q << 32) // // But in this particular case here, the full p_lo is not required. // Effectively we only need to add the highest bit in p_lo to p_hi (and // Q_hi + 1 does not overflow). Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); return {h, x.e + y.e + 64}; } /*! @brief normalize x such that the significand is >= 2^(q-1) @pre x.f != 0 */ static diyfp normalize(diyfp x) noexcept { assert(x.f != 0); while ((x.f >> 63u) == 0) { x.f <<= 1u; x.e--; } return x; } /*! @brief normalize x such that the result has the exponent E @pre e >= x.e and the upper e - x.e bits of x.f must be zero. */ static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept { const int delta = x.e - target_exponent; assert(delta >= 0); assert(((x.f << delta) >> delta) == x.f); return {x.f << delta, target_exponent}; } }; struct boundaries { diyfp w; diyfp minus; diyfp plus; }; /*! Compute the (normalized) diyfp representing the input number 'value' and its boundaries. @pre value must be finite and positive */ template boundaries compute_boundaries(FloatType value) { assert(std::isfinite(value)); assert(value > 0); // Convert the IEEE representation into a diyfp. // // If v is denormal: // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) // If v is normalized: // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); constexpr int kMinExp = 1 - kBias; constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) using bits_type = typename std::conditional::type; const std::uint64_t bits = reinterpret_bits(value); const std::uint64_t E = bits >> (kPrecision - 1); const std::uint64_t F = bits & (kHiddenBit - 1); const bool is_denormal = E == 0; const diyfp v = is_denormal ? diyfp(F, kMinExp) : diyfp(F + kHiddenBit, static_cast(E) - kBias); // Compute the boundaries m- and m+ of the floating-point value // v = f * 2^e. // // Determine v- and v+, the floating-point predecessor and successor if v, // respectively. // // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) // // v+ = v + 2^e // // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ // between m- and m+ round to v, regardless of how the input rounding // algorithm breaks ties. // // ---+-------------+-------------+-------------+-------------+--- (A) // v- m- v m+ v+ // // -----------------+------+------+-------------+-------------+--- (B) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 and E > 1; const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer ? diyfp(4 * v.f - 1, v.e - 2) // (B) : diyfp(2 * v.f - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); // Determine w- = m- such that e_(w-) = e_(w+). const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); return {diyfp::normalize(v), w_minus, w_plus}; } // Given normalized diyfp w, Grisu needs to find a (normalized) cached // power-of-ten c, such that the exponent of the product c * w = f * 2^e lies // within a certain range [alpha, gamma] (Definition 3.2 from [1]) // // alpha <= e = e_c + e_w + q <= gamma // // or // // f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q // <= f_c * f_w * 2^gamma // // Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies // // 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma // // or // // 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) // // The choice of (alpha,gamma) determines the size of the table and the form of // the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well // in practice: // // The idea is to cut the number c * w = f * 2^e into two parts, which can be // processed independently: An integral part p1, and a fractional part p2: // // f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e // = (f div 2^-e) + (f mod 2^-e) * 2^e // = p1 + p2 * 2^e // // The conversion of p1 into decimal form requires a series of divisions and // modulos by (a power of) 10. These operations are faster for 32-bit than for // 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be // achieved by choosing // // -e >= 32 or e <= -32 := gamma // // In order to convert the fractional part // // p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... // // into decimal form, the fraction is repeatedly multiplied by 10 and the digits // d[-i] are extracted in order: // // (10 * p2) div 2^-e = d[-1] // (10 * p2) mod 2^-e = d[-2] / 10^1 + ... // // The multiplication by 10 must not overflow. It is sufficient to choose // // 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. // // Since p2 = f mod 2^-e < 2^-e, // // -e <= 60 or e >= -60 := alpha constexpr int kAlpha = -60; constexpr int kGamma = -32; struct cached_power // c = f * 2^e ~= 10^k { std::uint64_t f; int e; int k; }; /*! For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c satisfies (Definition 3.2 from [1]) alpha <= e_c + e + q <= gamma. */ inline cached_power get_cached_power_for_binary_exponent(int e) { // Now // // alpha <= e_c + e + q <= gamma (1) // ==> f_c * 2^alpha <= c * 2^e * 2^q // // and since the c's are normalized, 2^(q-1) <= f_c, // // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) // ==> 2^(alpha - e - 1) <= c // // If c were an exact power of ten, i.e. c = 10^k, one may determine k as // // k = ceil( log_10( 2^(alpha - e - 1) ) ) // = ceil( (alpha - e - 1) * log_10(2) ) // // From the paper: // "In theory the result of the procedure could be wrong since c is rounded, // and the computation itself is approximated [...]. In practice, however, // this simple function is sufficient." // // For IEEE double precision floating-point numbers converted into // normalized diyfp's w = f * 2^e, with q = 64, // // e >= -1022 (min IEEE exponent) // -52 (p - 1) // -52 (p - 1, possibly normalize denormal IEEE numbers) // -11 (normalize the diyfp) // = -1137 // // and // // e <= +1023 (max IEEE exponent) // -52 (p - 1) // -11 (normalize the diyfp) // = 960 // // This binary exponent range [-1137,960] results in a decimal exponent // range [-307,324]. One does not need to store a cached power for each // k in this range. For each such k it suffices to find a cached power // such that the exponent of the product lies in [alpha,gamma]. // This implies that the difference of the decimal exponents of adjacent // table entries must be less than or equal to // // floor( (gamma - alpha) * log_10(2) ) = 8. // // (A smaller distance gamma-alpha would require a larger table.) // NB: // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. constexpr int kCachedPowersMinDecExp = -300; constexpr int kCachedPowersDecStep = 8; static constexpr std::array kCachedPowers = { { { 0xAB70FE17C79AC6CA, -1060, -300 }, { 0xFF77B1FCBEBCDC4F, -1034, -292 }, { 0xBE5691EF416BD60C, -1007, -284 }, { 0x8DD01FAD907FFC3C, -980, -276 }, { 0xD3515C2831559A83, -954, -268 }, { 0x9D71AC8FADA6C9B5, -927, -260 }, { 0xEA9C227723EE8BCB, -901, -252 }, { 0xAECC49914078536D, -874, -244 }, { 0x823C12795DB6CE57, -847, -236 }, { 0xC21094364DFB5637, -821, -228 }, { 0x9096EA6F3848984F, -794, -220 }, { 0xD77485CB25823AC7, -768, -212 }, { 0xA086CFCD97BF97F4, -741, -204 }, { 0xEF340A98172AACE5, -715, -196 }, { 0xB23867FB2A35B28E, -688, -188 }, { 0x84C8D4DFD2C63F3B, -661, -180 }, { 0xC5DD44271AD3CDBA, -635, -172 }, { 0x936B9FCEBB25C996, -608, -164 }, { 0xDBAC6C247D62A584, -582, -156 }, { 0xA3AB66580D5FDAF6, -555, -148 }, { 0xF3E2F893DEC3F126, -529, -140 }, { 0xB5B5ADA8AAFF80B8, -502, -132 }, { 0x87625F056C7C4A8B, -475, -124 }, { 0xC9BCFF6034C13053, -449, -116 }, { 0x964E858C91BA2655, -422, -108 }, { 0xDFF9772470297EBD, -396, -100 }, { 0xA6DFBD9FB8E5B88F, -369, -92 }, { 0xF8A95FCF88747D94, -343, -84 }, { 0xB94470938FA89BCF, -316, -76 }, { 0x8A08F0F8BF0F156B, -289, -68 }, { 0xCDB02555653131B6, -263, -60 }, { 0x993FE2C6D07B7FAC, -236, -52 }, { 0xE45C10C42A2B3B06, -210, -44 }, { 0xAA242499697392D3, -183, -36 }, { 0xFD87B5F28300CA0E, -157, -28 }, { 0xBCE5086492111AEB, -130, -20 }, { 0x8CBCCC096F5088CC, -103, -12 }, { 0xD1B71758E219652C, -77, -4 }, { 0x9C40000000000000, -50, 4 }, { 0xE8D4A51000000000, -24, 12 }, { 0xAD78EBC5AC620000, 3, 20 }, { 0x813F3978F8940984, 30, 28 }, { 0xC097CE7BC90715B3, 56, 36 }, { 0x8F7E32CE7BEA5C70, 83, 44 }, { 0xD5D238A4ABE98068, 109, 52 }, { 0x9F4F2726179A2245, 136, 60 }, { 0xED63A231D4C4FB27, 162, 68 }, { 0xB0DE65388CC8ADA8, 189, 76 }, { 0x83C7088E1AAB65DB, 216, 84 }, { 0xC45D1DF942711D9A, 242, 92 }, { 0x924D692CA61BE758, 269, 100 }, { 0xDA01EE641A708DEA, 295, 108 }, { 0xA26DA3999AEF774A, 322, 116 }, { 0xF209787BB47D6B85, 348, 124 }, { 0xB454E4A179DD1877, 375, 132 }, { 0x865B86925B9BC5C2, 402, 140 }, { 0xC83553C5C8965D3D, 428, 148 }, { 0x952AB45CFA97A0B3, 455, 156 }, { 0xDE469FBD99A05FE3, 481, 164 }, { 0xA59BC234DB398C25, 508, 172 }, { 0xF6C69A72A3989F5C, 534, 180 }, { 0xB7DCBF5354E9BECE, 561, 188 }, { 0x88FCF317F22241E2, 588, 196 }, { 0xCC20CE9BD35C78A5, 614, 204 }, { 0x98165AF37B2153DF, 641, 212 }, { 0xE2A0B5DC971F303A, 667, 220 }, { 0xA8D9D1535CE3B396, 694, 228 }, { 0xFB9B7CD9A4A7443C, 720, 236 }, { 0xBB764C4CA7A44410, 747, 244 }, { 0x8BAB8EEFB6409C1A, 774, 252 }, { 0xD01FEF10A657842C, 800, 260 }, { 0x9B10A4E5E9913129, 827, 268 }, { 0xE7109BFBA19C0C9D, 853, 276 }, { 0xAC2820D9623BF429, 880, 284 }, { 0x80444B5E7AA7CF85, 907, 292 }, { 0xBF21E44003ACDD2D, 933, 300 }, { 0x8E679C2F5E44FF8F, 960, 308 }, { 0xD433179D9C8CB841, 986, 316 }, { 0x9E19DB92B4E31BA9, 1013, 324 }, } }; // This computation gives exactly the same results for k as // k = ceil((kAlpha - e - 1) * 0.30102999566398114) // for |e| <= 1500, but doesn't require floating-point operations. // NB: log_10(2) ~= 78913 / 2^18 assert(e >= -1500); assert(e <= 1500); const int f = kAlpha - e - 1; const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; assert(index >= 0); assert(static_cast(index) < kCachedPowers.size()); const cached_power cached = kCachedPowers[static_cast(index)]; assert(kAlpha <= cached.e + e + 64); assert(kGamma >= cached.e + e + 64); return cached; } /*! For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. For n == 0, returns 1 and sets pow10 := 1. */ inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) { // LCOV_EXCL_START if (n >= 1000000000) { pow10 = 1000000000; return 10; } // LCOV_EXCL_STOP else if (n >= 100000000) { pow10 = 100000000; return 9; } else if (n >= 10000000) { pow10 = 10000000; return 8; } else if (n >= 1000000) { pow10 = 1000000; return 7; } else if (n >= 100000) { pow10 = 100000; return 6; } else if (n >= 10000) { pow10 = 10000; return 5; } else if (n >= 1000) { pow10 = 1000; return 4; } else if (n >= 100) { pow10 = 100; return 3; } else if (n >= 10) { pow10 = 10; return 2; } else { pow10 = 1; return 1; } } inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, std::uint64_t rest, std::uint64_t ten_k) { assert(len >= 1); assert(dist <= delta); assert(rest <= delta); assert(ten_k > 0); // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // ten_k // <------> // <---- rest ----> // --------------[------------------+----+--------------]-------------- // w V // = buf * 10^k // // ten_k represents a unit-in-the-last-place in the decimal representation // stored in buf. // Decrement buf by ten_k while this takes buf closer to w. // The tests are written in this order to avoid overflow in unsigned // integer arithmetic. while (rest < dist and delta - rest >= ten_k and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) { assert(buf[len - 1] != '0'); buf[len - 1]--; rest += ten_k; } } /*! Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. M- and M+ must be normalized and share the same exponent -60 <= e <= -32. */ inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, diyfp M_minus, diyfp w, diyfp M_plus) { static_assert(kAlpha >= -60, "internal error"); static_assert(kGamma <= -32, "internal error"); // Generates the digits (and the exponent) of a decimal floating-point // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. // // <--------------------------- delta ----> // <---- dist ---------> // --------------[------------------+-------------------]-------------- // M- w M+ // // Grisu2 generates the digits of M+ from left to right and stops as soon as // V is in [M-,M+]. assert(M_plus.e >= kAlpha); assert(M_plus.e <= kGamma); std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): // // M+ = f * 2^e // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e // = ((p1 ) * 2^-e + (p2 )) * 2^e // = p1 + p2 * 2^e const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e // 1) // // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] assert(p1 > 0); std::uint32_t pow10; const int k = find_largest_pow10(p1, pow10); // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) // // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) // // M+ = p1 + p2 * 2^e // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e // = d[k-1] * 10^(k-1) + ( rest) * 2^e // // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) // // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] // // but stop as soon as // // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e int n = k; while (n > 0) { // Invariants: // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) // pow10 = 10^(n-1) <= p1 < 10^n // const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) // // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) // assert(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) // p1 = r; n--; // // M+ = buffer * 10^n + (p1 + p2 * 2^e) // pow10 = 10^n // // Now check if enough digits have been generated. // Compute // // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e // // Note: // Since rest and delta share the same exponent e, it suffices to // compare the significands. const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; if (rest <= delta) { // V = buffer * 10^n, with M- <= V <= M+. decimal_exponent += n; // We may now just stop. But instead look if the buffer could be // decremented to bring V closer to w. // // pow10 = 10^n is now 1 ulp in the decimal representation V. // The rounding procedure works with diyfp's with an implicit // exponent of e. // // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e // const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; grisu2_round(buffer, length, dist, delta, rest, ten_n); return; } pow10 /= 10; // // pow10 = 10^(n-1) <= p1 < 10^n // Invariants restored. } // 2) // // The digits of the integral part have been generated: // // M+ = d[k-1]...d[1]d[0] + p2 * 2^e // = buffer + p2 * 2^e // // Now generate the digits of the fractional part p2 * 2^e. // // Note: // No decimal point is generated: the exponent is adjusted instead. // // p2 actually represents the fraction // // p2 * 2^e // = p2 / 2^-e // = d[-1] / 10^1 + d[-2] / 10^2 + ... // // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) // // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) // // using // // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) // = ( d) * 2^-e + ( r) // // or // 10^m * p2 * 2^e = d + r * 2^e // // i.e. // // M+ = buffer + p2 * 2^e // = buffer + 10^-m * (d + r * 2^e) // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e // // and stop as soon as 10^-m * r * 2^e <= delta * 2^e assert(p2 > delta); int m = 0; for (;;) { // Invariant: // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e // = buffer * 10^-m + 10^-m * (p2 ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e // assert(p2 <= (std::numeric_limits::max)() / 10); p2 *= 10; const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e // // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e // assert(d <= 9); buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d // // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e // p2 = r; m++; // // M+ = buffer * 10^-m + 10^-m * p2 * 2^e // Invariant restored. // Check if enough digits have been generated. // // 10^-m * p2 * 2^e <= delta * 2^e // p2 * 2^e <= 10^m * delta * 2^e // p2 <= 10^m * delta delta *= 10; dist *= 10; if (p2 <= delta) { break; } } // V = buffer * 10^-m, with M- <= V <= M+. decimal_exponent -= m; // 1 ulp in the decimal representation is now 10^-m. // Since delta and dist are now scaled by 10^m, we need to do the // same with ulp in order to keep the units in sync. // // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e // const std::uint64_t ten_m = one.f; grisu2_round(buffer, length, dist, delta, p2, ten_m); // By construction this algorithm generates the shortest possible decimal // number (Loitsch, Theorem 6.2) which rounds back to w. // For an input number of precision p, at least // // N = 1 + ceil(p * log_10(2)) // // decimal digits are sufficient to identify all binary floating-point // numbers (Matula, "In-and-Out conversions"). // This implies that the algorithm does not produce more than N decimal // digits. // // N = 17 for p = 53 (IEEE double precision) // N = 9 for p = 24 (IEEE single precision) } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ JSON_HEDLEY_NON_NULL(1) inline void grisu2(char* buf, int& len, int& decimal_exponent, diyfp m_minus, diyfp v, diyfp m_plus) { assert(m_plus.e == m_minus.e); assert(m_plus.e == v.e); // --------(-----------------------+-----------------------)-------- (A) // m- v m+ // // --------------------(-----------+-----------------------)-------- (B) // m- v m+ // // First scale v (and m- and m+) such that the exponent is in the range // [alpha, gamma]. const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] const diyfp w = diyfp::mul(v, c_minus_k); const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); // ----(---+---)---------------(---+---)---------------(---+---)---- // w- w w+ // = c*m- = c*v = c*m+ // // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and // w+ are now off by a small amount. // In fact: // // w - v * 10^k < 1 ulp // // To account for this inaccuracy, add resp. subtract 1 ulp. // // --------+---[---------------(---+---)---------------]---+-------- // w- M- w M+ w+ // // Now any number in [M-, M+] (bounds included) will round to w when input, // regardless of how the input rounding algorithm breaks ties. // // And digit_gen generates the shortest possible such number in [M-, M+]. // Note that this does not mean that Grisu2 always generates the shortest // possible number in the interval (m-, m+). const diyfp M_minus(w_minus.f + 1, w_minus.e); const diyfp M_plus (w_plus.f - 1, w_plus.e ); decimal_exponent = -cached.k; // = -(-k) = k grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); } /*! v = buf * 10^decimal_exponent len is the length of the buffer (number of decimal digits) The buffer must be large enough, i.e. >= max_digits10. */ template JSON_HEDLEY_NON_NULL(1) void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) { static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, "internal error: not enough precision"); assert(std::isfinite(value)); assert(value > 0); // If the neighbors (and boundaries) of 'value' are always computed for double-precision // numbers, all float's can be recovered using strtod (and strtof). However, the resulting // decimal representations are not exactly "short". // // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) // says "value is converted to a string as if by std::sprintf in the default ("C") locale" // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' // does. // On the other hand, the documentation for 'std::to_chars' requires that "parsing the // representation using the corresponding std::from_chars function recovers value exactly". That // indicates that single precision floating-point numbers should be recovered using // 'std::strtof'. // // NB: If the neighbors are computed for single-precision numbers, there is a single float // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision // value is off by 1 ulp. #if 0 const boundaries w = compute_boundaries(static_cast(value)); #else const boundaries w = compute_boundaries(value); #endif grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); } /*! @brief appends a decimal representation of e to buf @return a pointer to the element following the exponent. @pre -1000 < e < 1000 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* append_exponent(char* buf, int e) { assert(e > -1000); assert(e < 1000); if (e < 0) { e = -e; *buf++ = '-'; } else { *buf++ = '+'; } auto k = static_cast(e); if (k < 10) { // Always print at least two digits in the exponent. // This is for compatibility with printf("%g"). *buf++ = '0'; *buf++ = static_cast('0' + k); } else if (k < 100) { *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } else { *buf++ = static_cast('0' + k / 100); k %= 100; *buf++ = static_cast('0' + k / 10); k %= 10; *buf++ = static_cast('0' + k); } return buf; } /*! @brief prettify v = buf * 10^decimal_exponent If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point notation. Otherwise it will be printed in exponential notation. @pre min_exp < 0 @pre max_exp > 0 */ JSON_HEDLEY_NON_NULL(1) JSON_HEDLEY_RETURNS_NON_NULL inline char* format_buffer(char* buf, int len, int decimal_exponent, int min_exp, int max_exp) { assert(min_exp < 0); assert(max_exp > 0); const int k = len; const int n = len + decimal_exponent; // v = buf * 10^(n-k) // k is the length of the buffer (number of decimal digits) // n is the position of the decimal point relative to the start of the buffer. if (k <= n and n <= max_exp) { // digits[000] // len <= max_exp + 2 std::memset(buf + k, '0', static_cast(n - k)); // Make it look like a floating-point number (#362, #378) buf[n + 0] = '.'; buf[n + 1] = '0'; return buf + (n + 2); } if (0 < n and n <= max_exp) { // dig.its // len <= max_digits10 + 1 assert(k > n); std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); buf[n] = '.'; return buf + (k + 1); } if (min_exp < n and n <= 0) { // 0.[000]digits // len <= 2 + (-min_exp - 1) + max_digits10 std::memmove(buf + (2 + -n), buf, static_cast(k)); buf[0] = '0'; buf[1] = '.'; std::memset(buf + 2, '0', static_cast(-n)); return buf + (2 + (-n) + k); } if (k == 1) { // dE+123 // len <= 1 + 5 buf += 1; } else { // d.igitsE+123 // len <= max_digits10 + 1 + 5 std::memmove(buf + 2, buf + 1, static_cast(k - 1)); buf[1] = '.'; buf += 1 + k; } *buf++ = 'e'; return append_exponent(buf, n - 1); } } // namespace dtoa_impl /*! @brief generates a decimal representation of the floating-point number value in [first, last). The format of the resulting decimal representation is similar to printf's %g format. Returns an iterator pointing past-the-end of the decimal representation. @note The input number must be finite, i.e. NaN's and Inf's are not supported. @note The buffer must be large enough. @note The result is NOT null-terminated. */ template JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_RETURNS_NON_NULL char* to_chars(char* first, const char* last, FloatType value) { static_cast(last); // maybe unused - fix warning assert(std::isfinite(value)); // Use signbit(value) instead of (value < 0) since signbit works for -0. if (std::signbit(value)) { value = -value; *first++ = '-'; } if (value == 0) // +-0 { *first++ = '0'; // Make it look like a floating-point number (#362, #378) *first++ = '.'; *first++ = '0'; return first; } assert(last - first >= std::numeric_limits::max_digits10); // Compute v = buffer * 10^decimal_exponent. // The decimal digits are stored in the buffer, which needs to be interpreted // as an unsigned decimal integer. // len is the length of the buffer, i.e. the number of decimal digits. int len = 0; int decimal_exponent = 0; dtoa_impl::grisu2(first, len, decimal_exponent, value); assert(len <= std::numeric_limits::max_digits10); // Format the buffer like printf("%.*g", prec, value) constexpr int kMinExp = -4; // Use digits10 here to increase compatibility with version 2. constexpr int kMaxExp = std::numeric_limits::digits10; assert(last - first >= kMaxExp + 2); assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); assert(last - first >= std::numeric_limits::max_digits10 + 6); return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); } } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/conversions/to_json.hpp ================================================ #pragma once #include // copy #include // or, and, not #include // begin, end #include // string #include // tuple, get #include // is_same, is_constructible, is_floating_point, is_enum, underlying_type #include // move, forward, declval, pair #include // valarray #include // vector #include #include #include #include namespace nlohmann { namespace detail { ////////////////// // constructors // ////////////////// template struct external_constructor; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept { j.m_type = value_t::boolean; j.m_value = b; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { j.m_type = value_t::string; j.m_value = s; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { j.m_type = value_t::string; j.m_value = std::move(s); j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleStringType& str) { j.m_type = value_t::string; j.m_value.string = j.template create(str); j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept { j.m_type = value_t::number_float; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept { j.m_type = value_t::number_unsigned; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept { j.m_type = value_t::number_integer; j.m_value = val; j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { j.m_type = value_t::array; j.m_value = arr; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { j.m_type = value_t::array; j.m_value = std::move(arr); j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { using std::begin; using std::end; j.m_type = value_t::array; j.m_value.array = j.template create(begin(arr), end(arr)); j.assert_invariant(); } template static void construct(BasicJsonType& j, const std::vector& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); for (const bool x : arr) { j.m_value.array->push_back(x); } j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->resize(arr.size()); if (arr.size() > 0) { std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); } j.assert_invariant(); } }; template<> struct external_constructor { template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { j.m_type = value_t::object; j.m_value = obj; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { j.m_type = value_t::object; j.m_value = std::move(obj); j.assert_invariant(); } template::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { using std::begin; using std::end; j.m_type = value_t::object; j.m_value.object = j.template create(begin(obj), end(obj)); j.assert_invariant(); } }; ///////////// // to_json // ///////////// template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } template ::value and not is_compatible_object_type< BasicJsonType, CompatibleArrayType>::value and not is_compatible_string_type::value and not is_basic_json::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template::value, int> = 0> void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template::value and not is_basic_json::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } template < typename BasicJsonType, typename T, std::size_t N, enable_if_t::value, int> = 0 > void to_json(BasicJsonType& j, const T(&arr)[N]) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } // for https://github.com/nlohmann/json/pull/1134 template < typename BasicJsonType, typename T, enable_if_t>::value, int> = 0> void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } struct to_json_fn { template auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) -> decltype(to_json(j, std::forward(val)), void()) { return to_json(j, std::forward(val)); } }; } // namespace detail /// namespace to hold default `to_json` function namespace { constexpr const auto& to_json = detail::static_const::value; } // namespace } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/exceptions.hpp ================================================ #pragma once #include // exception #include // runtime_error #include // to_string #include #include namespace nlohmann { namespace detail { //////////////// // exceptions // //////////////// /*! @brief general exception of the @ref basic_json class This class is an extension of `std::exception` objects with a member @a id for exception ids. It is used as the base class for all exceptions thrown by the @ref basic_json class. This class can hence be used as "wildcard" to catch exceptions. Subclasses: - @ref parse_error for exceptions indicating a parse error - @ref invalid_iterator for exceptions indicating errors with iterators - @ref type_error for exceptions indicating executing a member function with a wrong type - @ref out_of_range for exceptions indicating access out of the defined range - @ref other_error for exceptions indicating other library errors @internal @note To have nothrow-copy-constructible exceptions, we internally use `std::runtime_error` which can cope with arbitrary-length error messages. Intermediate strings are built with static functions and then passed to the actual constructor. @endinternal @liveexample{The following code shows how arbitrary library exceptions can be caught.,exception} @since version 3.0.0 */ class exception : public std::exception { public: /// returns the explanatory string JSON_HEDLEY_RETURNS_NON_NULL const char* what() const noexcept override { return m.what(); } /// the id of the exception const int id; protected: JSON_HEDLEY_NON_NULL(3) exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} static std::string name(const std::string& ename, int id_) { return "[json.exception." + ename + "." + std::to_string(id_) + "] "; } private: /// an exception object as storage for error messages std::runtime_error m; }; /*! @brief exception indicating a parse error This exception is thrown by the library when a parse error occurs. Parse errors can occur during the deserialization of JSON text, CBOR, MessagePack, as well as when using JSON Patch. Member @a byte holds the byte index of the last read character in the input file. Exceptions have ids 1xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). @liveexample{The following code shows how a `parse_error` exception can be caught.,parse_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class parse_error : public exception { public: /*! @brief create a parse error exception @param[in] id_ the id of the exception @param[in] pos the position where the error occurred (or with chars_read_total=0 if the position cannot be determined) @param[in] what_arg the explanatory string @return parse_error object */ static parse_error create(int id_, const position_t& pos, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + position_string(pos) + ": " + what_arg; return parse_error(id_, pos.chars_read_total, w.c_str()); } static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) { std::string w = exception::name("parse_error", id_) + "parse error" + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + ": " + what_arg; return parse_error(id_, byte_, w.c_str()); } /*! @brief byte index of the parse error The byte index of the last read character in the input file. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also holds true when reading a byte vector (CBOR or MessagePack). */ const std::size_t byte; private: parse_error(int id_, std::size_t byte_, const char* what_arg) : exception(id_, what_arg), byte(byte_) {} static std::string position_string(const position_t& pos) { return " at line " + std::to_string(pos.lines_read + 1) + ", column " + std::to_string(pos.chars_read_current_line); } }; /*! @brief exception indicating errors with iterators This exception is thrown if iterators passed to a library function do not match the expected semantics. Exceptions have ids 2xx. name / id | example message | description ----------------------------------- | --------------- | ------------------------- json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). @liveexample{The following code shows how an `invalid_iterator` exception can be caught.,invalid_iterator} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class invalid_iterator : public exception { public: static invalid_iterator create(int id_, const std::string& what_arg) { std::string w = exception::name("invalid_iterator", id_) + what_arg; return invalid_iterator(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) invalid_iterator(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating executing a member function with a wrong type This exception is thrown in case of a type error; that is, a library function is executed on a JSON value whose type does not match the expected semantics. Exceptions have ids 3xx. name / id | example message | description ----------------------------- | --------------- | ------------------------- json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | @liveexample{The following code shows how a `type_error` exception can be caught.,type_error} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref out_of_range for exceptions indicating access out of the defined range @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class type_error : public exception { public: static type_error create(int id_, const std::string& what_arg) { std::string w = exception::name("type_error", id_) + what_arg; return type_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating access out of the defined range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance in case of array indices or nonexisting object keys. Exceptions have ids 4xx. name / id | example message | description ------------------------------- | --------------- | ------------------------- json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. | json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | @liveexample{The following code shows how an `out_of_range` exception can be caught.,out_of_range} @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class out_of_range : public exception { public: static out_of_range create(int id_, const std::string& what_arg) { std::string w = exception::name("out_of_range", id_) + what_arg; return out_of_range(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating other library errors This exception is thrown in case of errors that cannot be classified with the other exception types. Exceptions have ids 5xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. @sa - @ref exception for the base class of the library exceptions @sa - @ref parse_error for exceptions indicating a parse error @sa - @ref invalid_iterator for exceptions indicating errors with iterators @sa - @ref type_error for exceptions indicating executing a member function with a wrong type @sa - @ref out_of_range for exceptions indicating access out of the defined range @liveexample{The following code shows how an `other_error` exception can be caught.,other_error} @since version 3.0.0 */ class other_error : public exception { public: static other_error create(int id_, const std::string& what_arg) { std::string w = exception::name("other_error", id_) + what_arg; return other_error(id_, w.c_str()); } private: JSON_HEDLEY_NON_NULL(3) other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/binary_reader.hpp ================================================ #pragma once #include // generate_n #include // array #include // assert #include // ldexp #include // size_t #include // uint8_t, uint16_t, uint32_t, uint64_t #include // snprintf #include // memcpy #include // back_inserter #include // numeric_limits #include // char_traits, string #include // make_pair, move #include #include #include #include #include #include namespace nlohmann { namespace detail { /////////////////// // binary reader // /////////////////// /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using json_sax_t = SAX; public: /*! @brief create a binary reader @param[in] adapter input adapter to read from */ explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) { (void)detail::is_sax_static_asserts {}; assert(ia); } // make class move-only binary_reader(const binary_reader&) = delete; binary_reader(binary_reader&&) = default; binary_reader& operator=(const binary_reader&) = delete; binary_reader& operator=(binary_reader&&) = default; ~binary_reader() = default; /*! @param[in] format the binary format to parse @param[in] sax_ a SAX event processor @param[in] strict whether to expect the input to be consumed completed @return */ JSON_HEDLEY_NON_NULL(3) bool sax_parse(const input_format_t format, json_sax_t* sax_, const bool strict = true) { sax = sax_; bool result = false; switch (format) { case input_format_t::bson: result = parse_bson_internal(); break; case input_format_t::cbor: result = parse_cbor_internal(); break; case input_format_t::msgpack: result = parse_msgpack_internal(); break; case input_format_t::ubjson: result = parse_ubjson_internal(); break; default: // LCOV_EXCL_LINE assert(false); // LCOV_EXCL_LINE } // strict mode: next byte must be EOF if (result and strict) { if (format == input_format_t::ubjson) { get_ignore_noop(); } else { get(); } if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) { return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); } } return result; } /*! @brief determine system byte order @return true if and only if system's byte order is little endian @note from http://stackoverflow.com/a/1001328/266378 */ static constexpr bool little_endianess(int num = 1) noexcept { return *reinterpret_cast(&num) == 1; } private: ////////// // BSON // ////////// /*! @brief Reads in a BSON-object and passes it to the SAX-parser. @return whether a valid BSON-value was passed to the SAX parser */ bool parse_bson_internal() { std::int32_t document_size; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(not sax->start_object(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_bson_element_list(/*is_array*/false))) { return false; } return sax->end_object(); } /*! @brief Parses a C-style string from the BSON input. @param[in, out] result A reference to the string variable where the read string is to be stored. @return `true` if the \x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ bool get_bson_cstr(string_t& result) { auto out = std::back_inserter(result); while (true) { get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::bson, "cstring"))) { return false; } if (current == 0x00) { return true; } *out++ = static_cast(current); } return true; } /*! @brief Parses a zero-terminated string of length @a len from the BSON input. @param[in] len The length (including the zero-byte at the end) of the string to be read. @param[in, out] result A reference to the string variable where the read string is to be stored. @tparam NumberType The type of the length @a len @pre len >= 1 @return `true` if the string was successfully parsed */ template bool get_bson_string(const NumberType len, string_t& result) { if (JSON_HEDLEY_UNLIKELY(len < 1)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); } return get_string(input_format_t::bson, len - static_cast(1), result) and get() != std::char_traits::eof(); } /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @param[in] element_type_parse_position The position in the input stream, where the `element_type` was read. @warning Not all BSON element types are supported yet. An unsupported @a element_type will give rise to a parse_error.114: Unsupported BSON record type 0x... @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_internal(const int element_type, const std::size_t element_type_parse_position) { switch (element_type) { case 0x01: // double { double number; return get_number(input_format_t::bson, number) and sax->number_float(static_cast(number), ""); } case 0x02: // string { std::int32_t len; string_t value; return get_number(input_format_t::bson, len) and get_bson_string(len, value) and sax->string(value); } case 0x03: // object { return parse_bson_internal(); } case 0x04: // array { return parse_bson_array(); } case 0x08: // boolean { return sax->boolean(get() != 0); } case 0x0A: // null { return sax->null(); } case 0x10: // int32 { std::int32_t value; return get_number(input_format_t::bson, value) and sax->number_integer(value); } case 0x12: // int64 { std::int64_t value; return get_number(input_format_t::bson, value) and sax->number_integer(value); } default: // anything else not supported (yet) { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); } } } /*! @brief Read a BSON element list (as specified in the BSON-spec) The same binary layout is used for objects and arrays, hence it must be indicated with the argument @a is_array which one is expected (true --> array, false --> object). @param[in] is_array Determines if the element list being read is to be treated as an object (@a is_array == false), or as an array (@a is_array == true). @return whether a valid BSON-object/array was passed to the SAX parser */ bool parse_bson_element_list(const bool is_array) { string_t key; while (int element_type = get()) { if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list"))) { return false; } const std::size_t element_type_parse_position = chars_read; if (JSON_HEDLEY_UNLIKELY(not get_bson_cstr(key))) { return false; } if (not is_array and not sax->key(key)) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_bson_element_internal(element_type, element_type_parse_position))) { return false; } // get_bson_cstr only appends key.clear(); } return true; } /*! @brief Reads an array from the BSON input and passes it to the SAX-parser. @return whether a valid BSON-array was passed to the SAX parser */ bool parse_bson_array() { std::int32_t document_size; get_number(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY(not sax->start_array(std::size_t(-1)))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_bson_element_list(/*is_array*/true))) { return false; } return sax->end_array(); } ////////// // CBOR // ////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether a valid CBOR value was passed to the SAX parser */ bool parse_cbor_internal(const bool get_char = true) { switch (get_char ? get() : current) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::cbor, "value"); // Integer 0x00..0x17 (0..23) case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: return sax->number_unsigned(static_cast(current)); case 0x18: // Unsigned integer (one-byte uint8_t follows) { std::uint8_t number; return get_number(input_format_t::cbor, number) and sax->number_unsigned(number); } case 0x19: // Unsigned integer (two-byte uint16_t follows) { std::uint16_t number; return get_number(input_format_t::cbor, number) and sax->number_unsigned(number); } case 0x1A: // Unsigned integer (four-byte uint32_t follows) { std::uint32_t number; return get_number(input_format_t::cbor, number) and sax->number_unsigned(number); } case 0x1B: // Unsigned integer (eight-byte uint64_t follows) { std::uint64_t number; return get_number(input_format_t::cbor, number) and sax->number_unsigned(number); } // Negative integer -1-0x00..-1-0x17 (-1..-24) case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: return sax->number_integer(static_cast(0x20 - 1 - current)); case 0x38: // Negative integer (one-byte uint8_t follows) { std::uint8_t number; return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast(-1) - number); } case 0x39: // Negative integer -1-n (two-byte uint16_t follows) { std::uint16_t number; return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast(-1) - number); } case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { std::uint32_t number; return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast(-1) - number); } case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { std::uint64_t number; return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast(-1) - static_cast(number)); } // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) case 0x7F: // UTF-8 string (indefinite length) { string_t s; return get_cbor_string(s) and sax->string(s); } // array (0x00..0x17 data items follow) case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: return get_cbor_array(static_cast(static_cast(current) & 0x1Fu)); case 0x98: // array (one-byte uint8_t for n follows) { std::uint8_t len; return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast(len)); } case 0x99: // array (two-byte uint16_t for n follow) { std::uint16_t len; return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast(len)); } case 0x9A: // array (four-byte uint32_t for n follow) { std::uint32_t len; return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast(len)); } case 0x9B: // array (eight-byte uint64_t for n follow) { std::uint64_t len; return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast(len)); } case 0x9F: // array (indefinite length) return get_cbor_array(std::size_t(-1)); // map (0x00..0x17 pairs of data items follow) case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: return get_cbor_object(static_cast(static_cast(current) & 0x1Fu)); case 0xB8: // map (one-byte uint8_t for n follows) { std::uint8_t len; return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast(len)); } case 0xB9: // map (two-byte uint16_t for n follow) { std::uint16_t len; return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast(len)); } case 0xBA: // map (four-byte uint32_t for n follow) { std::uint32_t len; return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast(len)); } case 0xBB: // map (eight-byte uint64_t for n follow) { std::uint64_t len; return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast(len)); } case 0xBF: // map (indefinite length) return get_cbor_object(std::size_t(-1)); case 0xF4: // false return sax->boolean(false); case 0xF5: // true return sax->boolean(true); case 0xF6: // null return sax->null(); case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const int byte1_raw = get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number"))) { return false; } const int byte2_raw = get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number"))) { return false; } const auto byte1 = static_cast(byte1_raw); const auto byte2 = static_cast(byte2_raw); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added // to IEEE 754 in 2008, today's programming platforms often // still only have limited support for them. It is very // easy to include at least decoding support for them even // without such support. An example of a small decoder for // half-precision floating-point numbers in the C language // is shown in Fig. 3. const auto half = static_cast((byte1 << 8u) + byte2); const double val = [&half] { const int exp = (half >> 10u) & 0x1Fu; const unsigned int mant = half & 0x3FFu; assert(0 <= exp and exp <= 32); assert(mant <= 1024); switch (exp) { case 0: return std::ldexp(mant, -24); case 31: return (mant == 0) ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); default: return std::ldexp(mant + 1024, exp - 25); } }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) : static_cast(val), ""); } case 0xFA: // Single-Precision Float (four-byte IEEE 754) { float number; return get_number(input_format_t::cbor, number) and sax->number_float(static_cast(number), ""); } case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { double number; return get_number(input_format_t::cbor, number) and sax->number_float(static_cast(number), ""); } default: // anything else (0xFF is handled inside the other types) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a CBOR string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. Additionally, CBOR's strings with indefinite lengths are supported. @param[out] result created string @return whether string creation completed */ bool get_cbor_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::cbor, "string"))) { return false; } switch (current) { // UTF-8 string (0x00..0x17 bytes follow) case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { std::uint8_t len; return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { std::uint16_t len; return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result); } case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { std::uint32_t len; return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result); } case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { std::uint64_t len; return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result); } case 0x7F: // UTF-8 string (indefinite length) { while (get() != 0xFF) { string_t chunk; if (not get_cbor_string(chunk)) { return false; } result.append(chunk); } return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); } } } /*! @param[in] len the length of the array or std::size_t(-1) for an array of indefinite size @return whether array creation completed */ bool get_cbor_array(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(not sax->start_array(len))) { return false; } if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(not parse_cbor_internal())) { return false; } } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(not parse_cbor_internal(false))) { return false; } } } return sax->end_array(); } /*! @param[in] len the length of the object or std::size_t(-1) for an object of indefinite size @return whether object creation completed */ bool get_cbor_object(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(not sax->start_object(len))) { return false; } string_t key; if (len != std::size_t(-1)) { for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(not get_cbor_string(key) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_cbor_internal())) { return false; } key.clear(); } } else { while (get() != 0xFF) { if (JSON_HEDLEY_UNLIKELY(not get_cbor_string(key) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_cbor_internal())) { return false; } key.clear(); } } return sax->end_object(); } ///////////// // MsgPack // ///////////// /*! @return whether a valid MessagePack value was passed to the SAX parser */ bool parse_msgpack_internal() { switch (get()) { // EOF case std::char_traits::eof(): return unexpect_eof(input_format_t::msgpack, "value"); // positive fixint case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: return sax->number_unsigned(static_cast(current)); // fixmap case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); // fixarray case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xD9: // str 8 case 0xDA: // str 16 case 0xDB: // str 32 { string_t s; return get_msgpack_string(s) and sax->string(s); } case 0xC0: // nil return sax->null(); case 0xC2: // false return sax->boolean(false); case 0xC3: // true return sax->boolean(true); case 0xCA: // float 32 { float number; return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast(number), ""); } case 0xCB: // float 64 { double number; return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast(number), ""); } case 0xCC: // uint 8 { std::uint8_t number; return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number); } case 0xCD: // uint 16 { std::uint16_t number; return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number); } case 0xCE: // uint 32 { std::uint32_t number; return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number); } case 0xCF: // uint 64 { std::uint64_t number; return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number); } case 0xD0: // int 8 { std::int8_t number; return get_number(input_format_t::msgpack, number) and sax->number_integer(number); } case 0xD1: // int 16 { std::int16_t number; return get_number(input_format_t::msgpack, number) and sax->number_integer(number); } case 0xD2: // int 32 { std::int32_t number; return get_number(input_format_t::msgpack, number) and sax->number_integer(number); } case 0xD3: // int 64 { std::int64_t number; return get_number(input_format_t::msgpack, number) and sax->number_integer(number); } case 0xDC: // array 16 { std::uint16_t len; return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len; return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast(len)); } case 0xDE: // map 16 { std::uint16_t len; return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast(len)); } case 0xDF: // map 32 { std::uint32_t len; return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast(len)); } // negative fixint case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: return sax->number_integer(static_cast(current)); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); } } } /*! @brief reads a MessagePack string This function first reads starting bytes to determine the expected string length and then copies this number of bytes into a string. @param[out] result created string @return whether string creation completed */ bool get_msgpack_string(string_t& result) { if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::msgpack, "string"))) { return false; } switch (current) { // fixstr case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: { return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); } case 0xD9: // str 8 { std::uint8_t len; return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result); } case 0xDA: // str 16 { std::uint16_t len; return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result); } case 0xDB: // str 32 { std::uint32_t len; return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result); } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); } } } /*! @param[in] len the length of the array @return whether array creation completed */ bool get_msgpack_array(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(not sax->start_array(len))) { return false; } for (std::size_t i = 0; i < len; ++i) { if (JSON_HEDLEY_UNLIKELY(not parse_msgpack_internal())) { return false; } } return sax->end_array(); } /*! @param[in] len the length of the object @return whether object creation completed */ bool get_msgpack_object(const std::size_t len) { if (JSON_HEDLEY_UNLIKELY(not sax->start_object(len))) { return false; } string_t key; for (std::size_t i = 0; i < len; ++i) { get(); if (JSON_HEDLEY_UNLIKELY(not get_msgpack_string(key) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_msgpack_internal())) { return false; } key.clear(); } return sax->end_object(); } //////////// // UBJSON // //////////// /*! @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether a valid UBJSON value was passed to the SAX parser */ bool parse_ubjson_internal(const bool get_char = true) { return get_ubjson_value(get_char ? get_ignore_noop() : current); } /*! @brief reads a UBJSON string This function is either called after reading the 'S' byte explicitly indicating a string, or in case of an object key where the 'S' byte can be left out. @param[out] result created string @param[in] get_char whether a new character should be retrieved from the input (true, default) or whether the last read character should be considered instead @return whether string creation completed */ bool get_ubjson_string(string_t& result, const bool get_char = true) { if (get_char) { get(); // TODO(niels): may we ignore N here? } if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value"))) { return false; } switch (current) { case 'U': { std::uint8_t len; return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result); } case 'i': { std::int8_t len; return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result); } case 'I': { std::int16_t len; return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result); } case 'l': { std::int32_t len; return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result); } case 'L': { std::int64_t len; return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result); } default: auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); } } /*! @param[out] result determined size @return whether size determination completed */ bool get_ubjson_size_value(std::size_t& result) { switch (get_ignore_noop()) { case 'U': { std::uint8_t number; if (JSON_HEDLEY_UNLIKELY(not get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'i': { std::int8_t number; if (JSON_HEDLEY_UNLIKELY(not get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'I': { std::int16_t number; if (JSON_HEDLEY_UNLIKELY(not get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'l': { std::int32_t number; if (JSON_HEDLEY_UNLIKELY(not get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } case 'L': { std::int64_t number; if (JSON_HEDLEY_UNLIKELY(not get_number(input_format_t::ubjson, number))) { return false; } result = static_cast(number); return true; } default: { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); } } } /*! @brief determine the type and size for a container In the optimized UBJSON format, a type and a size can be provided to allow for a more compact representation. @param[out] result pair of the size and the type @return whether pair creation completed */ bool get_ubjson_size_type(std::pair& result) { result.first = string_t::npos; // size result.second = 0; // type get_ignore_noop(); if (current == '$') { result.second = get(); // must not ignore 'N', because 'N' maybe the type if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "type"))) { return false; } get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value"))) { return false; } auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); } return get_ubjson_size_value(result.first); } if (current == '#') { return get_ubjson_size_value(result.first); } return true; } /*! @param prefix the previously read or set type prefix @return whether value creation completed */ bool get_ubjson_value(const int prefix) { switch (prefix) { case std::char_traits::eof(): // EOF return unexpect_eof(input_format_t::ubjson, "value"); case 'T': // true return sax->boolean(true); case 'F': // false return sax->boolean(false); case 'Z': // null return sax->null(); case 'U': { std::uint8_t number; return get_number(input_format_t::ubjson, number) and sax->number_unsigned(number); } case 'i': { std::int8_t number; return get_number(input_format_t::ubjson, number) and sax->number_integer(number); } case 'I': { std::int16_t number; return get_number(input_format_t::ubjson, number) and sax->number_integer(number); } case 'l': { std::int32_t number; return get_number(input_format_t::ubjson, number) and sax->number_integer(number); } case 'L': { std::int64_t number; return get_number(input_format_t::ubjson, number) and sax->number_integer(number); } case 'd': { float number; return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast(number), ""); } case 'D': { double number; return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast(number), ""); } case 'C': // char { get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "char"))) { return false; } if (JSON_HEDLEY_UNLIKELY(current > 127)) { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); } string_t s(1, static_cast(current)); return sax->string(s); } case 'S': // string { string_t s; return get_ubjson_string(s) and sax->string(s); } case '[': // array return get_ubjson_array(); case '{': // object return get_ubjson_object(); default: // anything else { auto last_token = get_token_string(); return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); } } } /*! @return whether array creation completed */ bool get_ubjson_array() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_type(size_and_type))) { return false; } if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(not sax->start_array(size_and_type.first))) { return false; } if (size_and_type.second != 0) { if (size_and_type.second != 'N') { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(not get_ubjson_value(size_and_type.second))) { return false; } } } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(not parse_ubjson_internal())) { return false; } } } } else { if (JSON_HEDLEY_UNLIKELY(not sax->start_array(std::size_t(-1)))) { return false; } while (current != ']') { if (JSON_HEDLEY_UNLIKELY(not parse_ubjson_internal(false))) { return false; } get_ignore_noop(); } } return sax->end_array(); } /*! @return whether object creation completed */ bool get_ubjson_object() { std::pair size_and_type; if (JSON_HEDLEY_UNLIKELY(not get_ubjson_size_type(size_and_type))) { return false; } string_t key; if (size_and_type.first != string_t::npos) { if (JSON_HEDLEY_UNLIKELY(not sax->start_object(size_and_type.first))) { return false; } if (size_and_type.second != 0) { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(not get_ubjson_string(key) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not get_ubjson_value(size_and_type.second))) { return false; } key.clear(); } } else { for (std::size_t i = 0; i < size_and_type.first; ++i) { if (JSON_HEDLEY_UNLIKELY(not get_ubjson_string(key) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_ubjson_internal())) { return false; } key.clear(); } } } else { if (JSON_HEDLEY_UNLIKELY(not sax->start_object(std::size_t(-1)))) { return false; } while (current != '}') { if (JSON_HEDLEY_UNLIKELY(not get_ubjson_string(key, false) or not sax->key(key))) { return false; } if (JSON_HEDLEY_UNLIKELY(not parse_ubjson_internal())) { return false; } get_ignore_noop(); key.clear(); } } return sax->end_object(); } /////////////////////// // Utility functions // /////////////////////// /*! @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input */ int get() { ++chars_read; return current = ia->get_character(); } /*! @return character read from the input after ignoring all 'N' entries */ int get_ignore_noop() { do { get(); } while (current == 'N'); return current; } /* @brief read a number from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[out] result number of type @a NumberType @return whether conversion completed @note This function needs to respect the system's endianess, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ template bool get_number(const input_format_t format, NumberType& result) { // step 1: read input into array with system's byte order std::array vec; for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(format, "number"))) { return false; } // reverse byte order prior to conversion if necessary if (is_little_endian != InputIsLittleEndian) { vec[sizeof(NumberType) - i - 1] = static_cast(current); } else { vec[i] = static_cast(current); // LCOV_EXCL_LINE } } // step 2: convert array into number of type T and return std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } /*! @brief create a string by reading characters from the input @tparam NumberType the type of the number @param[in] format the current format (for diagnostics) @param[in] len number of characters to read @param[out] result string created by reading @a len bytes @return whether string creation completed @note We can not reserve @a len bytes for the result, because @a len may be too large. Usually, @ref unexpect_eof() detects the end of the input before we run out of string memory. */ template bool get_string(const input_format_t format, const NumberType len, string_t& result) { bool success = true; std::generate_n(std::back_inserter(result), len, [this, &success, &format]() { get(); if (JSON_HEDLEY_UNLIKELY(not unexpect_eof(format, "string"))) { success = false; } return static_cast(current); }); return success; } /*! @param[in] format the current format (for diagnostics) @param[in] context further context information (for diagnostics) @return whether the last read character is not EOF */ JSON_HEDLEY_NON_NULL(3) bool unexpect_eof(const input_format_t format, const char* context) const { if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) { return sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); } return true; } /*! @return a string representation of the last read byte */ std::string get_token_string() const { std::array cr{{}}; (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); return std::string{cr.data()}; } /*! @param[in] format the current format @param[in] detail a detailed error message @param[in] context further context information @return a message string to use in the parse_error exceptions */ std::string exception_message(const input_format_t format, const std::string& detail, const std::string& context) const { std::string error_msg = "syntax error while parsing "; switch (format) { case input_format_t::cbor: error_msg += "CBOR"; break; case input_format_t::msgpack: error_msg += "MessagePack"; break; case input_format_t::ubjson: error_msg += "UBJSON"; break; case input_format_t::bson: error_msg += "BSON"; break; default: // LCOV_EXCL_LINE assert(false); // LCOV_EXCL_LINE } return error_msg + " " + context + ": " + detail; } private: /// input adapter input_adapter_t ia = nullptr; /// the current character int current = std::char_traits::eof(); /// the number of characters read std::size_t chars_read = 0; /// whether we can assume little endianess const bool is_little_endian = little_endianess(); /// the SAX parser json_sax_t* sax = nullptr; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/input_adapters.hpp ================================================ #pragma once #include // array #include // assert #include // size_t #include //FILE * #include // strlen #include // istream #include // begin, end, iterator_traits, random_access_iterator_tag, distance, next #include // shared_ptr, make_shared, addressof #include // accumulate #include // string, char_traits #include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include // pair, declval #include #include namespace nlohmann { namespace detail { /// the supported input formats enum class input_format_t { json, cbor, msgpack, ubjson, bson }; //////////////////// // input adapters // //////////////////// /*! @brief abstract input adapter interface Produces a stream of std::char_traits::int_type characters from a std::istream, a buffer, or some other input type. Accepts the return of exactly one non-EOF character for future input. The int_type characters returned consist of all valid char values as positive values (typically unsigned char), plus an EOF value outside that range, specified by the value of the function std::char_traits::eof(). This value is typically -1, but could be any arbitrary value which is not a valid char value. */ struct input_adapter_protocol { /// get a character [0,255] or std::char_traits::eof(). virtual std::char_traits::int_type get_character() = 0; virtual ~input_adapter_protocol() = default; }; /// a type to simplify interfaces using input_adapter_t = std::shared_ptr; /*! Input adapter for stdio file access. This adapter read only 1 byte and do not use any buffer. This adapter is a very low level adapter. */ class file_input_adapter : public input_adapter_protocol { public: JSON_HEDLEY_NON_NULL(2) explicit file_input_adapter(std::FILE* f) noexcept : m_file(f) {} // make class move-only file_input_adapter(const file_input_adapter&) = delete; file_input_adapter(file_input_adapter&&) = default; file_input_adapter& operator=(const file_input_adapter&) = delete; file_input_adapter& operator=(file_input_adapter&&) = default; ~file_input_adapter() override = default; std::char_traits::int_type get_character() noexcept override { return std::fgetc(m_file); } private: /// the file pointer to read from std::FILE* m_file; }; /*! Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at beginning of input. Does not support changing the underlying std::streambuf in mid-input. Maintains underlying std::istream and std::streambuf to support subsequent use of standard std::istream operations to process any input characters following those used in parsing the JSON input. Clears the std::istream flags; any input errors (e.g., EOF) will be detected by the first subsequent call for input from the std::istream. */ class input_stream_adapter : public input_adapter_protocol { public: ~input_stream_adapter() override { // clear stream flags; we use underlying streambuf I/O, do not // maintain ifstream flags, except eof is.clear(is.rdstate() & std::ios::eofbit); } explicit input_stream_adapter(std::istream& i) : is(i), sb(*i.rdbuf()) {} // delete because of pointer members input_stream_adapter(const input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter(input_stream_adapter&&) = delete; input_stream_adapter& operator=(input_stream_adapter&&) = delete; // std::istream/std::streambuf use std::char_traits::to_int_type, to // ensure that std::char_traits::eof() and the character 0xFF do not // end up as the same value, eg. 0xFFFFFFFF. std::char_traits::int_type get_character() override { auto res = sb.sbumpc(); // set eof manually, as we don't use the istream interface. if (res == EOF) { is.clear(is.rdstate() | std::ios::eofbit); } return res; } private: /// the associated input stream std::istream& is; std::streambuf& sb; }; /// input adapter for buffer input class input_buffer_adapter : public input_adapter_protocol { public: input_buffer_adapter(const char* b, const std::size_t l) noexcept : cursor(b), limit(b == nullptr ? nullptr : (b + l)) {} // delete because of pointer members input_buffer_adapter(const input_buffer_adapter&) = delete; input_buffer_adapter& operator=(input_buffer_adapter&) = delete; input_buffer_adapter(input_buffer_adapter&&) = delete; input_buffer_adapter& operator=(input_buffer_adapter&&) = delete; ~input_buffer_adapter() override = default; std::char_traits::int_type get_character() noexcept override { if (JSON_HEDLEY_LIKELY(cursor < limit)) { assert(cursor != nullptr and limit != nullptr); return std::char_traits::to_int_type(*(cursor++)); } return std::char_traits::eof(); } private: /// pointer to the current character const char* cursor; /// pointer past the last character const char* const limit; }; template struct wide_string_input_helper { // UTF-32 static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (current_wchar == str.size()) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = static_cast(str[current_wchar++]); // UTF-32 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((wc >> 6u) & 0x1Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | (wc & 0x3Fu)); utf8_bytes_filled = 2; } else if (wc <= 0xFFFF) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((wc >> 12u) & 0x0Fu)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((wc >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (wc & 0x3Fu)); utf8_bytes_filled = 3; } else if (wc <= 0x10FFFF) { utf8_bytes[0] = static_cast::int_type>(0xF0u | ((wc >> 18u) & 0x07u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((wc >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((wc >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (wc & 0x3Fu)); utf8_bytes_filled = 4; } else { // unknown character utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } }; template struct wide_string_input_helper { // UTF-16 static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled) { utf8_bytes_index = 0; if (current_wchar == str.size()) { utf8_bytes[0] = std::char_traits::eof(); utf8_bytes_filled = 1; } else { // get the current character const auto wc = static_cast(str[current_wchar++]); // UTF-16 to UTF-8 encoding if (wc < 0x80) { utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } else if (wc <= 0x7FF) { utf8_bytes[0] = static_cast::int_type>(0xC0u | ((wc >> 6u))); utf8_bytes[1] = static_cast::int_type>(0x80u | (wc & 0x3Fu)); utf8_bytes_filled = 2; } else if (0xD800 > wc or wc >= 0xE000) { utf8_bytes[0] = static_cast::int_type>(0xE0u | ((wc >> 12u))); utf8_bytes[1] = static_cast::int_type>(0x80u | ((wc >> 6u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | (wc & 0x3Fu)); utf8_bytes_filled = 3; } else { if (current_wchar < str.size()) { const auto wc2 = static_cast(str[current_wchar++]); const auto charcode = 0x10000u + (((wc & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); utf8_bytes_filled = 4; } else { // unknown character ++current_wchar; utf8_bytes[0] = static_cast::int_type>(wc); utf8_bytes_filled = 1; } } } } }; template class wide_string_input_adapter : public input_adapter_protocol { public: explicit wide_string_input_adapter(const WideStringType& w) noexcept : str(w) {} std::char_traits::int_type get_character() noexcept override { // check if buffer needs to be filled if (utf8_bytes_index == utf8_bytes_filled) { fill_buffer(); assert(utf8_bytes_filled > 0); assert(utf8_bytes_index == 0); } // use buffer assert(utf8_bytes_filled > 0); assert(utf8_bytes_index < utf8_bytes_filled); return utf8_bytes[utf8_bytes_index++]; } private: template void fill_buffer() { wide_string_input_helper::fill_buffer(str, current_wchar, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); } /// the wstring to process const WideStringType& str; /// index of the current wchar in str std::size_t current_wchar = 0; /// a buffer for UTF-8 bytes std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; /// index to the utf8_codes array for the next valid byte std::size_t utf8_bytes_index = 0; /// number of valid bytes in the utf8_codes array std::size_t utf8_bytes_filled = 0; }; class input_adapter { public: // native support JSON_HEDLEY_NON_NULL(2) input_adapter(std::FILE* file) : ia(std::make_shared(file)) {} /// input adapter for input stream input_adapter(std::istream& i) : ia(std::make_shared(i)) {} /// input adapter for input stream input_adapter(std::istream&& i) : ia(std::make_shared(i)) {} input_adapter(const std::wstring& ws) : ia(std::make_shared>(ws)) {} input_adapter(const std::u16string& ws) : ia(std::make_shared>(ws)) {} input_adapter(const std::u32string& ws) : ia(std::make_shared>(ws)) {} /// input adapter for buffer template::value and std::is_integral::type>::value and sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> input_adapter(CharT b, std::size_t l) : ia(std::make_shared(reinterpret_cast(b), l)) {} // derived support /// input adapter for string literal template::value and std::is_integral::type>::value and sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> input_adapter(CharT b) : input_adapter(reinterpret_cast(b), std::strlen(reinterpret_cast(b))) {} /// input adapter for iterator range with contiguous storage template::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0> input_adapter(IteratorType first, IteratorType last) { #ifndef NDEBUG // assertion to check that the iterator range is indeed contiguous, // see http://stackoverflow.com/a/35008842/266378 for more discussion const auto is_contiguous = std::accumulate( first, last, std::pair(true, 0), [&first](std::pair res, decltype(*first) val) { res.first &= (val == *(std::next(std::addressof(*first), res.second++))); return res; }).first; assert(is_contiguous); #endif // assertion to check that each element is 1 byte long static_assert( sizeof(typename iterator_traits::value_type) == 1, "each element in the iterator range must have the size of 1 byte"); const auto len = static_cast(std::distance(first, last)); if (JSON_HEDLEY_LIKELY(len > 0)) { // there is at least one element: use the address of first ia = std::make_shared(reinterpret_cast(&(*first)), len); } else { // the address of first cannot be used: use nullptr ia = std::make_shared(nullptr, len); } } /// input adapter for array template input_adapter(T (&array)[N]) : input_adapter(std::begin(array), std::end(array)) {} /// input adapter for contiguous container template::value and std::is_base_of()))>::iterator_category>::value, int>::type = 0> input_adapter(const ContiguousContainer& c) : input_adapter(std::begin(c), std::end(c)) {} operator input_adapter_t() { return ia; } private: /// the actual adapter input_adapter_t ia = nullptr; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/json_sax.hpp ================================================ #pragma once #include // assert #include #include // string #include // move #include // vector #include #include namespace nlohmann { /*! @brief SAX interface This class describes the SAX interface used by @ref nlohmann::json::sax_parse. Each function is called in different situations while the input is parsed. The boolean return value informs the parser whether to continue processing the input. */ template struct json_sax { /// type for (signed) integers using number_integer_t = typename BasicJsonType::number_integer_t; /// type for unsigned integers using number_unsigned_t = typename BasicJsonType::number_unsigned_t; /// type for floating-point numbers using number_float_t = typename BasicJsonType::number_float_t; /// type for strings using string_t = typename BasicJsonType::string_t; /*! @brief a null value was read @return whether parsing should proceed */ virtual bool null() = 0; /*! @brief a boolean value was read @param[in] val boolean value @return whether parsing should proceed */ virtual bool boolean(bool val) = 0; /*! @brief an integer number was read @param[in] val integer value @return whether parsing should proceed */ virtual bool number_integer(number_integer_t val) = 0; /*! @brief an unsigned integer number was read @param[in] val unsigned integer value @return whether parsing should proceed */ virtual bool number_unsigned(number_unsigned_t val) = 0; /*! @brief an floating-point number was read @param[in] val floating-point value @param[in] s raw token value @return whether parsing should proceed */ virtual bool number_float(number_float_t val, const string_t& s) = 0; /*! @brief a string was read @param[in] val string value @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool string(string_t& val) = 0; /*! @brief the beginning of an object was read @param[in] elements number of object elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_object(std::size_t elements) = 0; /*! @brief an object key was read @param[in] val object key @return whether parsing should proceed @note It is safe to move the passed string. */ virtual bool key(string_t& val) = 0; /*! @brief the end of an object was read @return whether parsing should proceed */ virtual bool end_object() = 0; /*! @brief the beginning of an array was read @param[in] elements number of array elements or -1 if unknown @return whether parsing should proceed @note binary formats may report the number of elements */ virtual bool start_array(std::size_t elements) = 0; /*! @brief the end of an array was read @return whether parsing should proceed */ virtual bool end_array() = 0; /*! @brief a parse error occurred @param[in] position the position in the input where the error occurs @param[in] last_token the last read token @param[in] ex an exception object describing the error @return whether parsing should proceed (must return false) */ virtual bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex) = 0; virtual ~json_sax() = default; }; namespace detail { /*! @brief SAX implementation to create a JSON value from SAX events This class implements the @ref json_sax interface and processes the SAX events to create a JSON value which makes it basically a DOM parser. The structure or hierarchy of the JSON value is managed by the stack `ref_stack` which contains a pointer to the respective array or object for each recursion depth. After successful parsing, the value that is passed by reference to the constructor contains the parsed value. @tparam BasicJsonType the JSON type */ template class json_sax_dom_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; /*! @param[in, out] r reference to a JSON value that is manipulated while parsing @param[in] allow_exceptions_ whether parse errors yield exceptions */ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) : root(r), allow_exceptions(allow_exceptions_) {} // make class move-only json_sax_dom_parser(const json_sax_dom_parser&) = delete; json_sax_dom_parser(json_sax_dom_parser&&) = default; json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; ~json_sax_dom_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool start_object(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { // add null at given key and store the reference for later object_element = &(ref_stack.back()->m_value.object->operator[](val)); return true; } bool end_object() { ref_stack.pop_back(); return true; } bool start_array(std::size_t len) { ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { ref_stack.pop_back(); return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& ex) { errored = true; if (allow_exceptions) { // determine the proper exception type from the id switch ((ex.id / 100) % 100) { case 1: JSON_THROW(*static_cast(&ex)); case 4: JSON_THROW(*static_cast(&ex)); // LCOV_EXCL_START case 2: JSON_THROW(*static_cast(&ex)); case 3: JSON_THROW(*static_cast(&ex)); case 5: JSON_THROW(*static_cast(&ex)); default: assert(false); // LCOV_EXCL_STOP } } return false; } constexpr bool is_errored() const { return errored; } private: /*! @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements */ template JSON_HEDLEY_RETURNS_NON_NULL BasicJsonType* handle_value(Value&& v) { if (ref_stack.empty()) { root = BasicJsonType(std::forward(v)); return &root; } assert(ref_stack.back()->is_array() or ref_stack.back()->is_object()); if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->emplace_back(std::forward(v)); return &(ref_stack.back()->m_value.array->back()); } assert(ref_stack.back()->is_object()); assert(object_element); *object_element = BasicJsonType(std::forward(v)); return object_element; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; template class json_sax_dom_callback_parser { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using parser_callback_t = typename BasicJsonType::parser_callback_t; using parse_event_t = typename BasicJsonType::parse_event_t; json_sax_dom_callback_parser(BasicJsonType& r, const parser_callback_t cb, const bool allow_exceptions_ = true) : root(r), callback(cb), allow_exceptions(allow_exceptions_) { keep_stack.push_back(true); } // make class move-only json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; ~json_sax_dom_callback_parser() = default; bool null() { handle_value(nullptr); return true; } bool boolean(bool val) { handle_value(val); return true; } bool number_integer(number_integer_t val) { handle_value(val); return true; } bool number_unsigned(number_unsigned_t val) { handle_value(val); return true; } bool number_float(number_float_t val, const string_t& /*unused*/) { handle_value(val); return true; } bool string(string_t& val) { handle_value(val); return true; } bool start_object(std::size_t len) { // check callback for object start const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::object, true); ref_stack.push_back(val.second); // check object limit if (ref_stack.back() and JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); } return true; } bool key(string_t& val) { BasicJsonType k = BasicJsonType(val); // check callback for key const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); key_keep_stack.push_back(keep); // add discarded value at given key and store the reference for later if (keep and ref_stack.back()) { object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); } return true; } bool end_object() { if (ref_stack.back() and not callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) { // discard object *ref_stack.back() = discarded; } assert(not ref_stack.empty()); assert(not keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); if (not ref_stack.empty() and ref_stack.back() and ref_stack.back()->is_object()) { // remove discarded value for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { if (it->is_discarded()) { ref_stack.back()->erase(it); break; } } } return true; } bool start_array(std::size_t len) { const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); keep_stack.push_back(keep); auto val = handle_value(BasicJsonType::value_t::array, true); ref_stack.push_back(val.second); // check array limit if (ref_stack.back() and JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size())) { JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); } return true; } bool end_array() { bool keep = true; if (ref_stack.back()) { keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); if (not keep) { // discard array *ref_stack.back() = discarded; } } assert(not ref_stack.empty()); assert(not keep_stack.empty()); ref_stack.pop_back(); keep_stack.pop_back(); // remove discarded value if (not keep and not ref_stack.empty() and ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->pop_back(); } return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& ex) { errored = true; if (allow_exceptions) { // determine the proper exception type from the id switch ((ex.id / 100) % 100) { case 1: JSON_THROW(*static_cast(&ex)); case 4: JSON_THROW(*static_cast(&ex)); // LCOV_EXCL_START case 2: JSON_THROW(*static_cast(&ex)); case 3: JSON_THROW(*static_cast(&ex)); case 5: JSON_THROW(*static_cast(&ex)); default: assert(false); // LCOV_EXCL_STOP } } return false; } constexpr bool is_errored() const { return errored; } private: /*! @param[in] v value to add to the JSON value we build during parsing @param[in] skip_callback whether we should skip calling the callback function; this is required after start_array() and start_object() SAX events, because otherwise we would call the callback function with an empty array or object, respectively. @invariant If the ref stack is empty, then the passed value will be the new root. @invariant If the ref stack contains a value, then it is an array or an object to which we can add elements @return pair of boolean (whether value should be kept) and pointer (to the passed value in the ref_stack hierarchy; nullptr if not kept) */ template std::pair handle_value(Value&& v, const bool skip_callback = false) { assert(not keep_stack.empty()); // do not handle this value if we know it would be added to a discarded // container if (not keep_stack.back()) { return {false, nullptr}; } // create value auto value = BasicJsonType(std::forward(v)); // check callback const bool keep = skip_callback or callback(static_cast(ref_stack.size()), parse_event_t::value, value); // do not handle this value if we just learnt it shall be discarded if (not keep) { return {false, nullptr}; } if (ref_stack.empty()) { root = std::move(value); return {true, &root}; } // skip this value if we already decided to skip the parent // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) if (not ref_stack.back()) { return {false, nullptr}; } // we now only expect arrays and objects assert(ref_stack.back()->is_array() or ref_stack.back()->is_object()); // array if (ref_stack.back()->is_array()) { ref_stack.back()->m_value.array->push_back(std::move(value)); return {true, &(ref_stack.back()->m_value.array->back())}; } // object assert(ref_stack.back()->is_object()); // check if we should store an element for the current key assert(not key_keep_stack.empty()); const bool store_element = key_keep_stack.back(); key_keep_stack.pop_back(); if (not store_element) { return {false, nullptr}; } assert(object_element); *object_element = std::move(value); return {true, object_element}; } /// the parsed JSON value BasicJsonType& root; /// stack to model hierarchy of values std::vector ref_stack {}; /// stack to manage which values to keep std::vector keep_stack {}; /// stack to manage which object keys to keep std::vector key_keep_stack {}; /// helper to hold the reference for the next object element BasicJsonType* object_element = nullptr; /// whether a syntax error occurred bool errored = false; /// callback function const parser_callback_t callback = nullptr; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; /// a discarded value for the callback BasicJsonType discarded = BasicJsonType::value_t::discarded; }; template class json_sax_acceptor { public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; bool null() { return true; } bool boolean(bool /*unused*/) { return true; } bool number_integer(number_integer_t /*unused*/) { return true; } bool number_unsigned(number_unsigned_t /*unused*/) { return true; } bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) { return true; } bool string(string_t& /*unused*/) { return true; } bool start_object(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool key(string_t& /*unused*/) { return true; } bool end_object() { return true; } bool start_array(std::size_t /*unused*/ = std::size_t(-1)) { return true; } bool end_array() { return true; } bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) { return false; } }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/lexer.hpp ================================================ #pragma once #include // array #include // localeconv #include // size_t #include // snprintf #include // strtof, strtod, strtold, strtoll, strtoull #include // initializer_list #include // char_traits, string #include // move #include // vector #include #include #include namespace nlohmann { namespace detail { /////////// // lexer // /////////// /*! @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. */ template class lexer { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; public: /// token types for the parser enum class token_type { uninitialized, ///< indicating the scanner is uninitialized literal_true, ///< the `true` literal literal_false, ///< the `false` literal literal_null, ///< the `null` literal value_string, ///< a string -- use get_string() for actual value value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value value_integer, ///< a signed integer -- use get_number_integer() for actual value value_float, ///< an floating point number -- use get_number_float() for actual value begin_array, ///< the character for array begin `[` begin_object, ///< the character for object begin `{` end_array, ///< the character for array end `]` end_object, ///< the character for object end `}` name_separator, ///< the name separator `:` value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error end_of_input, ///< indicating the end of the input buffer literal_or_value ///< a literal or the begin of a value (only for diagnostics) }; /// return name of values of type token_type (only used for errors) JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_CONST static const char* token_type_name(const token_type t) noexcept { switch (t) { case token_type::uninitialized: return ""; case token_type::literal_true: return "true literal"; case token_type::literal_false: return "false literal"; case token_type::literal_null: return "null literal"; case token_type::value_string: return "string literal"; case lexer::token_type::value_unsigned: case lexer::token_type::value_integer: case lexer::token_type::value_float: return "number literal"; case token_type::begin_array: return "'['"; case token_type::begin_object: return "'{'"; case token_type::end_array: return "']'"; case token_type::end_object: return "'}'"; case token_type::name_separator: return "':'"; case token_type::value_separator: return "','"; case token_type::parse_error: return ""; case token_type::end_of_input: return "end of input"; case token_type::literal_or_value: return "'[', '{', or a literal"; // LCOV_EXCL_START default: // catch non-enum values return "unknown token"; // LCOV_EXCL_STOP } } explicit lexer(detail::input_adapter_t&& adapter) : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} // delete because of pointer members lexer(const lexer&) = delete; lexer(lexer&&) = delete; lexer& operator=(lexer&) = delete; lexer& operator=(lexer&&) = delete; ~lexer() = default; private: ///////////////////// // locales ///////////////////// /// return the locale-dependent decimal point JSON_HEDLEY_PURE static char get_decimal_point() noexcept { const auto loc = localeconv(); assert(loc != nullptr); return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// // scan functions ///////////////////// /*! @brief get codepoint from 4 hex characters following `\u` For input "\u c1 c2 c3 c4" the codepoint is: (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The conversion is done by subtracting the offset (0x30, 0x37, and 0x57) between the ASCII value of the character and the desired integer value. @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or non-hex character) */ int get_codepoint() { // this function only makes sense after reading `\u` assert(current == 'u'); int codepoint = 0; const auto factors = { 12u, 8u, 4u, 0u }; for (const auto factor : factors) { get(); if (current >= '0' and current <= '9') { codepoint += static_cast((static_cast(current) - 0x30u) << factor); } else if (current >= 'A' and current <= 'F') { codepoint += static_cast((static_cast(current) - 0x37u) << factor); } else if (current >= 'a' and current <= 'f') { codepoint += static_cast((static_cast(current) - 0x57u) << factor); } else { return -1; } } assert(0x0000 <= codepoint and codepoint <= 0xFFFF); return codepoint; } /*! @brief check if the next byte(s) are inside a given range Adds the current byte and, for each passed range, reads a new byte and checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, 1, 2, or 3 pairs. This precondition is enforced by an assertion. @return true if and only if no range violation was detected */ bool next_byte_in_range(std::initializer_list ranges) { assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); add(current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current and current <= *(++range))) { add(current); } else { error_message = "invalid string: ill-formed UTF-8 byte"; return false; } } return true; } /*! @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While scanning, bytes are escaped and copied into buffer token_buffer. Then the function returns successfully, token_buffer is *not* null-terminated (as it may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ token_type scan_string() { // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote assert(current == '\"'); while (true) { // get next character switch (get()) { // end of file while parsing string case std::char_traits::eof(): { error_message = "invalid string: missing closing quote"; return token_type::parse_error; } // closing quote case '\"': { return token_type::value_string; } // escapes case '\\': { switch (get()) { // quotation mark case '\"': add('\"'); break; // reverse solidus case '\\': add('\\'); break; // solidus case '/': add('/'); break; // backspace case 'b': add('\b'); break; // form feed case 'f': add('\f'); break; // line feed case 'n': add('\n'); break; // carriage return case 'r': add('\r'); break; // tab case 't': add('\t'); break; // unicode escapes case 'u': { const int codepoint1 = get_codepoint(); int codepoint = codepoint1; // start with codepoint1 if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if code point is a high surrogate if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) { // expect next \uxxxx entry if (JSON_HEDLEY_LIKELY(get() == '\\' and get() == 'u')) { const int codepoint2 = get_codepoint(); if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) { error_message = "invalid string: '\\u' must be followed by 4 hex digits"; return token_type::parse_error; } // check if codepoint2 is a low surrogate if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { // overwrite codepoint codepoint = static_cast( // high surrogate occupies the most significant 22 bits (static_cast(codepoint1) << 10u) // low surrogate occupies the least significant 15 bits + static_cast(codepoint2) // there is still the 0xD800, 0xDC00 and 0x10000 noise // in the result so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00u); } else { error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) { error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } } // result of the above calculation yields a proper codepoint assert(0x00 <= codepoint and codepoint <= 0x10FFFF); // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(codepoint); } else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } else { // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); } break; } // other characters after escape default: error_message = "invalid string: forbidden character after backslash"; return token_type::parse_error; } break; } // invalid control characters case 0x00: { error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; return token_type::parse_error; } case 0x01: { error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; return token_type::parse_error; } case 0x02: { error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; return token_type::parse_error; } case 0x03: { error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; return token_type::parse_error; } case 0x04: { error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; return token_type::parse_error; } case 0x05: { error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; return token_type::parse_error; } case 0x06: { error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; return token_type::parse_error; } case 0x07: { error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; return token_type::parse_error; } case 0x08: { error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; return token_type::parse_error; } case 0x09: { error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; return token_type::parse_error; } case 0x0A: { error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; return token_type::parse_error; } case 0x0B: { error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; return token_type::parse_error; } case 0x0C: { error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; return token_type::parse_error; } case 0x0D: { error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; return token_type::parse_error; } case 0x0E: { error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; return token_type::parse_error; } case 0x0F: { error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; return token_type::parse_error; } case 0x10: { error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; return token_type::parse_error; } case 0x11: { error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; return token_type::parse_error; } case 0x12: { error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; return token_type::parse_error; } case 0x13: { error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; return token_type::parse_error; } case 0x14: { error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; return token_type::parse_error; } case 0x15: { error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; return token_type::parse_error; } case 0x16: { error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; return token_type::parse_error; } case 0x17: { error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; return token_type::parse_error; } case 0x18: { error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; return token_type::parse_error; } case 0x19: { error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; return token_type::parse_error; } case 0x1A: { error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; return token_type::parse_error; } case 0x1B: { error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; return token_type::parse_error; } case 0x1C: { error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; return token_type::parse_error; } case 0x1D: { error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; return token_type::parse_error; } case 0x1E: { error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; return token_type::parse_error; } case 0x1F: { error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; return token_type::parse_error; } // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) case 0x20: case 0x21: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5D: case 0x5E: case 0x5F: case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: { if (JSON_HEDLEY_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { return token_type::parse_error; } break; } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xEE: case 0xEF: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF case 0xF1: case 0xF2: case 0xF3: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { if (JSON_HEDLEY_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } break; } // remaining bytes (80..C1 and F5..FF) are ill-formed default: { error_message = "invalid string: ill-formed UTF-8 byte"; return token_type::parse_error; } } } } JSON_HEDLEY_NON_NULL(2) static void strtof(float& f, const char* str, char** endptr) noexcept { f = std::strtof(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(double& f, const char* str, char** endptr) noexcept { f = std::strtod(str, endptr); } JSON_HEDLEY_NON_NULL(2) static void strtof(long double& f, const char* str, char** endptr) noexcept { f = std::strtold(str, endptr); } /*! @brief scan a number literal This function scans a string according to Sect. 6 of RFC 7159. The function is realized with a deterministic finite state machine derived from the grammar described in RFC 7159. Starting in state "init", the input is read and used to determined the next state. Only state "done" accepts the number. State "error" is a trap state to model errors. In the table below, "anything" means any character but the ones listed before. state | 0 | 1-9 | e E | + | - | . | anything ---------|----------|----------|----------|---------|---------|----------|----------- init | zero | any1 | [error] | [error] | minus | [error] | [error] minus | zero | any1 | [error] | [error] | [error] | [error] | [error] zero | done | done | exponent | done | done | decimal1 | done any1 | any1 | any1 | exponent | done | done | decimal1 | done decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] decimal2 | decimal2 | decimal2 | exponent | done | done | done | done exponent | any2 | any2 | [error] | sign | sign | [error] | [error] sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] any2 | any2 | any2 | done | done | done | done | done The state machine is realized with one label per state (prefixed with "scan_number_") and `goto` statements between them. The state machine contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @return token_type::value_unsigned, token_type::value_integer, or token_type::value_float if number could be successfully scanned, token_type::parse_error otherwise @note The scanner is independent of the current locale. Internally, the locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ token_type scan_number() // lgtm [cpp/use-of-goto] { // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be // changed if minus sign, decimal point or exponent is read token_type number_type = token_type::value_unsigned; // state (init): we just found out we need to scan a number switch (current) { case '-': { add(current); goto scan_number_minus; } case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } // all other characters are rejected outside scan_number() default: // LCOV_EXCL_LINE assert(false); // LCOV_EXCL_LINE } scan_number_minus: // state: we just parsed a leading minus sign number_type = token_type::value_integer; switch (get()) { case '0': { add(current); goto scan_number_zero; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } default: { error_message = "invalid number; expected digit after '-'"; return token_type::parse_error; } } scan_number_zero: // state: we just parse a zero (maybe with a leading minus sign) switch (get()) { case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_any1: // state: we just parsed a number 0-9 (maybe with a leading minus sign) switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any1; } case '.': { add(decimal_point_char); goto scan_number_decimal1; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_decimal1: // state: we just parsed a decimal point number_type = token_type::value_float; switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } default: { error_message = "invalid number; expected digit after '.'"; return token_type::parse_error; } } scan_number_decimal2: // we just parsed at least one number after a decimal point switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_decimal2; } case 'e': case 'E': { add(current); goto scan_number_exponent; } default: goto scan_number_done; } scan_number_exponent: // we just parsed an exponent number_type = token_type::value_float; switch (get()) { case '+': case '-': { add(current); goto scan_number_sign; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected '+', '-', or digit after exponent"; return token_type::parse_error; } } scan_number_sign: // we just parsed an exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: { error_message = "invalid number; expected digit after exponent sign"; return token_type::parse_error; } } scan_number_any2: // we just parsed a number after the exponent or exponent sign switch (get()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { add(current); goto scan_number_any2; } default: goto scan_number_done; } scan_number_done: // unget the character after the number (we only read it to know that // we are done scanning a number) unget(); char* endptr = nullptr; errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { const auto x = std::strtoull(token_buffer.data(), &endptr, 10); // we checked the number format before assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_unsigned = static_cast(x); if (value_unsigned == x) { return token_type::value_unsigned; } } } else if (number_type == token_type::value_integer) { const auto x = std::strtoll(token_buffer.data(), &endptr, 10); // we checked the number format before assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { value_integer = static_cast(x); if (value_integer == x) { return token_type::value_integer; } } } // this code is reached if we parse a floating-point number or if an // integer conversion above failed strtof(value_float, token_buffer.data(), &endptr); // we checked the number format before assert(endptr == token_buffer.data() + token_buffer.size()); return token_type::value_float; } /*! @param[in] literal_text the literal text to expect @param[in] length the length of the passed literal text @param[in] return_type the token type to return on success */ JSON_HEDLEY_NON_NULL(2) token_type scan_literal(const char* literal_text, const std::size_t length, token_type return_type) { assert(current == literal_text[0]); for (std::size_t i = 1; i < length; ++i) { if (JSON_HEDLEY_UNLIKELY(get() != literal_text[i])) { error_message = "invalid literal"; return token_type::parse_error; } } return return_type; } ///////////////////// // input management ///////////////////// /// reset token_buffer; current character is beginning of token void reset() noexcept { token_buffer.clear(); token_string.clear(); token_string.push_back(std::char_traits::to_char_type(current)); } /* @brief get next character from the input This function provides the interface to the used input adapter. It does not throw in case the input reached EOF, but returns a `std::char_traits::eof()` in that case. Stores the scanned characters for use in error messages. @return character read from the input */ std::char_traits::int_type get() { ++position.chars_read_total; ++position.chars_read_current_line; if (next_unget) { // just reset the next_unget variable and work with current next_unget = false; } else { current = ia->get_character(); } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { token_string.push_back(std::char_traits::to_char_type(current)); } if (current == '\n') { ++position.lines_read; position.chars_read_current_line = 0; } return current; } /*! @brief unget current character (read it again on next get) We implement unget by setting variable next_unget to true. The input is not changed - we just simulate ungetting by modifying chars_read_total, chars_read_current_line, and token_string. The next call to get() will behave as if the unget character is read again. */ void unget() { next_unget = true; --position.chars_read_total; // in case we "unget" a newline, we have to also decrement the lines_read if (position.chars_read_current_line == 0) { if (position.lines_read > 0) { --position.lines_read; } } else { --position.chars_read_current_line; } if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) { assert(not token_string.empty()); token_string.pop_back(); } } /// add a character to token_buffer void add(int c) { token_buffer.push_back(std::char_traits::to_char_type(c)); } public: ///////////////////// // value getters ///////////////////// /// return integer value constexpr number_integer_t get_number_integer() const noexcept { return value_integer; } /// return unsigned integer value constexpr number_unsigned_t get_number_unsigned() const noexcept { return value_unsigned; } /// return floating-point value constexpr number_float_t get_number_float() const noexcept { return value_float; } /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { return token_buffer; } ///////////////////// // diagnostics ///////////////////// /// return position of last read token constexpr position_t get_position() const noexcept { return position; } /// return the last read token (for errors only). Will never contain EOF /// (an arbitrary value that is not a valid char value, often -1), because /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const { // escape control characters std::string result; for (const auto c : token_string) { if ('\x00' <= c and c <= '\x1F') { // escape control characters std::array cs{{}}; (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); result += cs.data(); } else { // add character as is result.push_back(c); } } return result; } /// return syntax error message JSON_HEDLEY_RETURNS_NON_NULL constexpr const char* get_error_message() const noexcept { return error_message; } ///////////////////// // actual scanner ///////////////////// /*! @brief skip the UTF-8 byte order mark @return true iff there is no BOM or the correct BOM has been skipped */ bool skip_bom() { if (get() == 0xEF) { // check if we completely parse the BOM return get() == 0xBB and get() == 0xBF; } // the first character is not the beginning of the BOM; unget it to // process is later unget(); return true; } token_type scan() { // initially, skip the BOM if (position.chars_read_total == 0 and not skip_bom()) { error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; return token_type::parse_error; } // read next character and ignore whitespace do { get(); } while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); switch (current) { // structural characters case '[': return token_type::begin_array; case ']': return token_type::end_array; case '{': return token_type::begin_object; case '}': return token_type::end_object; case ':': return token_type::name_separator; case ',': return token_type::value_separator; // literals case 't': return scan_literal("true", 4, token_type::literal_true); case 'f': return scan_literal("false", 5, token_type::literal_false); case 'n': return scan_literal("null", 4, token_type::literal_null); // string case '\"': return scan_string(); // number case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return scan_number(); // end of input (the null byte is needed when parsing from // string literals) case '\0': case std::char_traits::eof(): return token_type::end_of_input; // error default: error_message = "invalid literal"; return token_type::parse_error; } } private: /// input adapter detail::input_adapter_t ia = nullptr; /// the current character std::char_traits::int_type current = std::char_traits::eof(); /// whether the next get() call should just return current bool next_unget = false; /// the start position of the current token position_t position {}; /// raw input token string (for error messages) std::vector token_string {}; /// buffer for variable-length tokens (numbers, strings) string_t token_buffer {}; /// a description of occurred lexer errors const char* error_message = ""; // number values number_integer_t value_integer = 0; number_unsigned_t value_unsigned = 0; number_float_t value_float = 0; /// the decimal point const char decimal_point_char = '.'; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/parser.hpp ================================================ #pragma once #include // assert #include // isfinite #include // uint8_t #include // function #include // string #include // move #include // vector #include #include #include #include #include #include #include namespace nlohmann { namespace detail { //////////// // parser // //////////// /*! @brief syntax analysis This class implements a recursive decent parser. */ template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: enum class parse_event_t : uint8_t { /// the parser read `{` and started to process a JSON object object_start, /// the parser read `}` and finished processing a JSON object object_end, /// the parser read `[` and started to process a JSON array array_start, /// the parser read `]` and finished processing a JSON array array_end, /// the parser read a key of a value in an object key, /// the parser finished reading a JSON value value }; using parser_callback_t = std::function; /// a parser reading from an input adapter explicit parser(detail::input_adapter_t&& adapter, const parser_callback_t cb = nullptr, const bool allow_exceptions_ = true) : callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_) { // read first token get_token(); } /*! @brief public parser interface @param[in] strict whether to expect the last token to be EOF @param[in,out] result parsed JSON value @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails */ void parse(const bool strict, BasicJsonType& result) { if (callback) { json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict and (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } // set top-level value to null if it was discarded by the callback // function if (result.is_discarded()) { result = nullptr; } } else { json_sax_dom_parser sdp(result, allow_exceptions); sax_parse_internal(&sdp); result.assert_invariant(); // in strict mode, input must be completely read if (strict and (get_token() != token_type::end_of_input)) { sdp.parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } // in case of an error, return discarded value if (sdp.is_errored()) { result = value_t::discarded; return; } } } /*! @brief public accept interface @param[in] strict whether to expect the last token to be EOF @return whether the input is a proper JSON text */ bool accept(const bool strict = true) { json_sax_acceptor sax_acceptor; return sax_parse(&sax_acceptor, strict); } template JSON_HEDLEY_NON_NULL(2) bool sax_parse(SAX* sax, const bool strict = true) { (void)detail::is_sax_static_asserts {}; const bool result = sax_parse_internal(sax); // strict mode: next byte must be EOF if (result and strict and (get_token() != token_type::end_of_input)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"))); } return result; } private: template JSON_HEDLEY_NON_NULL(2) bool sax_parse_internal(SAX* sax) { // stack to remember the hierarchy of structured values we are parsing // true = array; false = object std::vector states; // value to avoid a goto (see comment where set to true) bool skip_to_state_evaluation = false; while (true) { if (not skip_to_state_evaluation) { // invariant: get_token() was called before each iteration switch (last_token) { case token_type::begin_object: { if (JSON_HEDLEY_UNLIKELY(not sax->start_object(std::size_t(-1)))) { return false; } // closing } -> we are done if (get_token() == token_type::end_object) { if (JSON_HEDLEY_UNLIKELY(not sax->end_object())) { return false; } break; } // parse key if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(not sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // remember we are now inside an object states.push_back(false); // parse values get_token(); continue; } case token_type::begin_array: { if (JSON_HEDLEY_UNLIKELY(not sax->start_array(std::size_t(-1)))) { return false; } // closing ] -> we are done if (get_token() == token_type::end_array) { if (JSON_HEDLEY_UNLIKELY(not sax->end_array())) { return false; } break; } // remember we are now inside an array states.push_back(true); // parse values (no need to call get_token) continue; } case token_type::value_float: { const auto res = m_lexer.get_number_float(); if (JSON_HEDLEY_UNLIKELY(not std::isfinite(res))) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); } if (JSON_HEDLEY_UNLIKELY(not sax->number_float(res, m_lexer.get_string()))) { return false; } break; } case token_type::literal_false: { if (JSON_HEDLEY_UNLIKELY(not sax->boolean(false))) { return false; } break; } case token_type::literal_null: { if (JSON_HEDLEY_UNLIKELY(not sax->null())) { return false; } break; } case token_type::literal_true: { if (JSON_HEDLEY_UNLIKELY(not sax->boolean(true))) { return false; } break; } case token_type::value_integer: { if (JSON_HEDLEY_UNLIKELY(not sax->number_integer(m_lexer.get_number_integer()))) { return false; } break; } case token_type::value_string: { if (JSON_HEDLEY_UNLIKELY(not sax->string(m_lexer.get_string()))) { return false; } break; } case token_type::value_unsigned: { if (JSON_HEDLEY_UNLIKELY(not sax->number_unsigned(m_lexer.get_number_unsigned()))) { return false; } break; } case token_type::parse_error: { // using "uninitialized" to avoid "expected" message return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"))); } default: // the last token was unexpected { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"))); } } } else { skip_to_state_evaluation = false; } // we reached this line after we successfully parsed a value if (states.empty()) { // empty stack: we reached the end of the hierarchy: done return true; } if (states.back()) // array { // comma -> next value if (get_token() == token_type::value_separator) { // parse a new value get_token(); continue; } // closing ] if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) { if (JSON_HEDLEY_UNLIKELY(not sax->end_array())) { return false; } // We are done with this array. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. assert(not states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"))); } else // object { // comma -> next value if (get_token() == token_type::value_separator) { // parse key if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"))); } if (JSON_HEDLEY_UNLIKELY(not sax->key(m_lexer.get_string()))) { return false; } // parse separator (:) if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) { return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"))); } // parse values get_token(); continue; } // closing } if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) { if (JSON_HEDLEY_UNLIKELY(not sax->end_object())) { return false; } // We are done with this object. Before we can parse a // new value, we need to evaluate the new state first. // By setting skip_to_state_evaluation to false, we // are effectively jumping to the beginning of this if. assert(not states.empty()); states.pop_back(); skip_to_state_evaluation = true; continue; } return sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"))); } } } /// get next token from lexer token_type get_token() { return last_token = m_lexer.scan(); } std::string exception_message(const token_type expected, const std::string& context) { std::string error_msg = "syntax error "; if (not context.empty()) { error_msg += "while parsing " + context + " "; } error_msg += "- "; if (last_token == token_type::parse_error) { error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + m_lexer.get_token_string() + "'"; } else { error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); } if (expected != token_type::uninitialized) { error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); } return error_msg; } private: /// callback function const parser_callback_t callback = nullptr; /// the type of the last read token token_type last_token = token_type::uninitialized; /// the lexer lexer_t m_lexer; /// whether to throw exceptions in case of errors const bool allow_exceptions = true; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/input/position_t.hpp ================================================ #pragma once #include // size_t namespace nlohmann { namespace detail { /// struct to capture the start position of the current token struct position_t { /// the total number of characters read std::size_t chars_read_total = 0; /// the number of characters read in the current line std::size_t chars_read_current_line = 0; /// the number of lines read std::size_t lines_read = 0; /// conversion to size_t to preserve SAX interface constexpr operator size_t() const { return chars_read_total; } }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/iterators/internal_iterator.hpp ================================================ #pragma once #include namespace nlohmann { namespace detail { /*! @brief an iterator value @note This structure could easily be a union, but MSVC currently does not allow unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. */ template struct internal_iterator { /// iterator for JSON objects typename BasicJsonType::object_t::iterator object_iterator {}; /// iterator for JSON arrays typename BasicJsonType::array_t::iterator array_iterator {}; /// generic iterator for all other types primitive_iterator_t primitive_iterator {}; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/iterators/iter_impl.hpp ================================================ #pragma once #include // not #include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next #include // conditional, is_const, remove_const #include #include #include #include #include #include #include namespace nlohmann { namespace detail { // forward declare, to be able to friend it later on template class iteration_proxy; template class iteration_proxy_value; /*! @brief a template for a bidirectional iterator for the @ref basic_json class This class implements a both iterators (iterator and const_iterator) for the @ref basic_json class. @note An iterator is called *initialized* when a pointer to a JSON value has been set (e.g., by a constructor or a copy assignment). If the iterator is default-constructed, it is *uninitialized* and most methods are undefined. **The library uses assertions to detect calls on uninitialized iterators.** @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). @since version 1.0.0, simplified in version 2.0.9, change to bidirectional iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) */ template class iter_impl { /// allow basic_json to access private members friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; friend BasicJsonType; friend iteration_proxy; friend iteration_proxy_value; using object_t = typename BasicJsonType::object_t; using array_t = typename BasicJsonType::array_t; // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); public: /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. /// The C++ Standard has never required user-defined iterators to derive from std::iterator. /// A user-defined iterator should provide publicly accessible typedefs named /// iterator_category, value_type, difference_type, pointer, and reference. /// Note that value_type is required to be non-const, even for constant iterators. using iterator_category = std::bidirectional_iterator_tag; /// the type of the values when the iterator is dereferenced using value_type = typename BasicJsonType::value_type; /// a type to represent differences between iterators using difference_type = typename BasicJsonType::difference_type; /// defines a pointer to the type iterated over (value_type) using pointer = typename std::conditional::value, typename BasicJsonType::const_pointer, typename BasicJsonType::pointer>::type; /// defines a reference to the type iterated over (value_type) using reference = typename std::conditional::value, typename BasicJsonType::const_reference, typename BasicJsonType::reference>::type; /// default constructor iter_impl() = default; /*! @brief constructor for a given JSON instance @param[in] object pointer to a JSON object for this iterator @pre object != nullptr @post The iterator is initialized; i.e. `m_object != nullptr`. */ explicit iter_impl(pointer object) noexcept : m_object(object) { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } case value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } default: { m_it.primitive_iterator = primitive_iterator_t(); break; } } } /*! @note The conventional copy constructor and copy assignment are implicitly defined. Combined with the following converting constructor and assignment, they support: (1) copy from iterator to iterator, (2) copy from const iterator to const iterator, and (3) conversion from iterator to const iterator. However conversion from const iterator to iterator is not defined. */ /*! @brief const copy constructor @param[in] other const iterator to copy from @note This copy constructor had to be defined explicitly to circumvent a bug occurring on msvc v19.0 compiler (VS 2015) debug build. For more information refer to: https://github.com/nlohmann/json/issues/1608 */ iter_impl(const iter_impl& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } /*! @brief converting constructor @param[in] other non-const iterator to copy from @note It is not checked whether @a other is initialized. */ iter_impl(const iter_impl::type>& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} /*! @brief converting assignment @param[in] other non-const iterator to copy from @return const/non-const iterator @note It is not checked whether @a other is initialized. */ iter_impl& operator=(const iter_impl::type>& other) noexcept { m_object = other.m_object; m_it = other.m_it; return *this; } private: /*! @brief set the iterator to the first value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_begin() noexcept { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->begin(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->begin(); break; } case value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); break; } default: { m_it.primitive_iterator.set_begin(); break; } } } /*! @brief set the iterator past the last value @pre The iterator is initialized; i.e. `m_object != nullptr`. */ void set_end() noexcept { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { m_it.object_iterator = m_object->m_value.object->end(); break; } case value_t::array: { m_it.array_iterator = m_object->m_value.array->end(); break; } default: { m_it.primitive_iterator.set_end(); break; } } } public: /*! @brief return a reference to the value pointed to by the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator*() const { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { assert(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } case value_t::array: { assert(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief dereference the iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ pointer operator->() const { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { assert(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } case value_t::array: { assert(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) { return m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief post-increment (it++) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator++(int) { auto result = *this; ++(*this); return result; } /*! @brief pre-increment (++it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator++() { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, 1); break; } case value_t::array: { std::advance(m_it.array_iterator, 1); break; } default: { ++m_it.primitive_iterator; break; } } return *this; } /*! @brief post-decrement (it--) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl const operator--(int) { auto result = *this; --(*this); return result; } /*! @brief pre-decrement (--it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator--() { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: { std::advance(m_it.object_iterator, -1); break; } case value_t::array: { std::advance(m_it.array_iterator, -1); break; } default: { --m_it.primitive_iterator; break; } } return *this; } /*! @brief comparison: equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator==(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: return (m_it.object_iterator == other.m_it.object_iterator); case value_t::array: return (m_it.array_iterator == other.m_it.array_iterator); default: return (m_it.primitive_iterator == other.m_it.primitive_iterator); } } /*! @brief comparison: not equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator!=(const iter_impl& other) const { return not operator==(other); } /*! @brief comparison: smaller @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) { JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); } assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); case value_t::array: return (m_it.array_iterator < other.m_it.array_iterator); default: return (m_it.primitive_iterator < other.m_it.primitive_iterator); } } /*! @brief comparison: less than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator<=(const iter_impl& other) const { return not other.operator < (*this); } /*! @brief comparison: greater than @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>(const iter_impl& other) const { return not operator<=(other); } /*! @brief comparison: greater than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ bool operator>=(const iter_impl& other) const { return not operator<(other); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator+=(difference_type i) { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: { std::advance(m_it.array_iterator, i); break; } default: { m_it.primitive_iterator += i; break; } } return *this; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl& operator-=(difference_type i) { return operator+=(-i); } /*! @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator+(difference_type i) const { auto result = *this; result += i; return result; } /*! @brief addition of distance and iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ friend iter_impl operator+(difference_type i, const iter_impl& it) { auto result = it; result += i; return result; } /*! @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ iter_impl operator-(difference_type i) const { auto result = *this; result -= i; return result; } /*! @brief return difference @pre The iterator is initialized; i.e. `m_object != nullptr`. */ difference_type operator-(const iter_impl& other) const { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); case value_t::array: return m_it.array_iterator - other.m_it.array_iterator; default: return m_it.primitive_iterator - other.m_it.primitive_iterator; } } /*! @brief access to successor @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference operator[](difference_type n) const { assert(m_object != nullptr); switch (m_object->m_type) { case value_t::object: JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); case value_t::array: return *std::next(m_it.array_iterator, n); case value_t::null: JSON_THROW(invalid_iterator::create(214, "cannot get value")); default: { if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) { return *m_object; } JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } /*! @brief return the key of an object iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ const typename object_t::key_type& key() const { assert(m_object != nullptr); if (JSON_HEDLEY_LIKELY(m_object->is_object())) { return m_it.object_iterator->first; } JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); } /*! @brief return the value of an iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ reference value() const { return operator*(); } private: /// associated JSON instance pointer m_object = nullptr; /// the actual iterator of the associated instance internal_iterator::type> m_it {}; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/iterators/iteration_proxy.hpp ================================================ #pragma once #include // size_t #include // input_iterator_tag #include // string, to_string #include // tuple_size, get, tuple_element #include #include namespace nlohmann { namespace detail { template void int_to_string( string_type& target, std::size_t value ) { target = std::to_string(value); } template class iteration_proxy_value { public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; using pointer = value_type * ; using reference = value_type & ; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator IteratorType anchor; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index mutable std::size_t array_index_last = 0; /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) const string_type empty_str = ""; public: explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} /// dereference operator (needed for range-based for) iteration_proxy_value& operator*() { return *this; } /// increment operator (needed for range-based for) iteration_proxy_value& operator++() { ++anchor; ++array_index; return *this; } /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { return anchor == o.anchor; } /// inequality operator (needed for range-based for) bool operator!=(const iteration_proxy_value& o) const { return anchor != o.anchor; } /// return key of the iterator const string_type& key() const { assert(anchor.m_object != nullptr); switch (anchor.m_object->type()) { // use integer array index as key case value_t::array: { if (array_index != array_index_last) { int_to_string( array_index_str, array_index ); array_index_last = array_index; } return array_index_str; } // use key from the object case value_t::object: return anchor.key(); // use an empty key for all primitive types default: return empty_str; } } /// return value of the iterator typename IteratorType::reference value() const { return anchor.value(); } }; /// proxy class for the items() function template class iteration_proxy { private: /// the container to iterate typename IteratorType::reference container; public: /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept : container(cont) {} /// return iterator begin (needed for range-based for) iteration_proxy_value begin() noexcept { return iteration_proxy_value(container.begin()); } /// return iterator end (needed for range-based for) iteration_proxy_value end() noexcept { return iteration_proxy_value(container.end()); } }; // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) { return i.key(); } // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 template = 0> auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) { return i.value(); } } // namespace detail } // namespace nlohmann // The Addition to the STD Namespace is required to add // Structured Bindings Support to the iteration_proxy_value class // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 namespace std { #if defined(__clang__) // Fix: https://github.com/nlohmann/json/issues/1401 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmismatched-tags" #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> : public std::integral_constant {}; template class tuple_element> { public: using type = decltype( get(std::declval < ::nlohmann::detail::iteration_proxy_value> ())); }; #if defined(__clang__) #pragma clang diagnostic pop #endif } // namespace std ================================================ FILE: thirdparty/nlohmann/detail/iterators/iterator_traits.hpp ================================================ #pragma once #include // random_access_iterator_tag #include #include namespace nlohmann { namespace detail { template struct iterator_types {}; template struct iterator_types < It, void_t> { using difference_type = typename It::difference_type; using value_type = typename It::value_type; using pointer = typename It::pointer; using reference = typename It::reference; using iterator_category = typename It::iterator_category; }; // This is required as some compilers implement std::iterator_traits in a way that // doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. template struct iterator_traits { }; template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> : iterator_types { }; template struct iterator_traits::value>> { using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = ptrdiff_t; using pointer = T*; using reference = T&; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/iterators/json_reverse_iterator.hpp ================================================ #pragma once #include // ptrdiff_t #include // reverse_iterator #include // declval namespace nlohmann { namespace detail { ////////////////////// // reverse_iterator // ////////////////////// /*! @brief a template for a reverse iterator class @tparam Base the base iterator type to reverse. Valid types are @ref iterator (to create @ref reverse_iterator) and @ref const_iterator (to create @ref const_reverse_iterator). @requirement The class satisfies the following concept requirements: - [BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): The iterator that can be moved can be moved in both directions (i.e. incremented and decremented). - [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator { public: using difference_type = std::ptrdiff_t; /// shortcut to the reverse iterator adapter using base_iterator = std::reverse_iterator; /// the reference type for the pointed-to element using reference = typename Base::reference; /// create reverse iterator from iterator explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept : base_iterator(it) {} /// create reverse iterator from base class explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} /// post-increment (it++) json_reverse_iterator const operator++(int) { return static_cast(base_iterator::operator++(1)); } /// pre-increment (++it) json_reverse_iterator& operator++() { return static_cast(base_iterator::operator++()); } /// post-decrement (it--) json_reverse_iterator const operator--(int) { return static_cast(base_iterator::operator--(1)); } /// pre-decrement (--it) json_reverse_iterator& operator--() { return static_cast(base_iterator::operator--()); } /// add to iterator json_reverse_iterator& operator+=(difference_type i) { return static_cast(base_iterator::operator+=(i)); } /// add to iterator json_reverse_iterator operator+(difference_type i) const { return static_cast(base_iterator::operator+(i)); } /// subtract from iterator json_reverse_iterator operator-(difference_type i) const { return static_cast(base_iterator::operator-(i)); } /// return difference difference_type operator-(const json_reverse_iterator& other) const { return base_iterator(*this) - base_iterator(other); } /// access to successor reference operator[](difference_type n) const { return *(this->operator+(n)); } /// return the key of an object iterator auto key() const -> decltype(std::declval().key()) { auto it = --this->base(); return it.key(); } /// return the value of an iterator reference value() const { auto it = --this->base(); return it.operator * (); } }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/iterators/primitive_iterator.hpp ================================================ #pragma once #include // ptrdiff_t #include // numeric_limits namespace nlohmann { namespace detail { /* @brief an iterator for primitive JSON types This class models an iterator for primitive JSON types (boolean, number, string). It's only purpose is to allow the iterator/const_iterator classes to "iterate" over primitive values. Internally, the iterator is modeled by a `difference_type` variable. Value begin_value (`0`) models the begin, end_value (`1`) models past the end. */ class primitive_iterator_t { private: using difference_type = std::ptrdiff_t; static constexpr difference_type begin_value = 0; static constexpr difference_type end_value = begin_value + 1; /// iterator as signed integer type difference_type m_it = (std::numeric_limits::min)(); public: constexpr difference_type get_value() const noexcept { return m_it; } /// set iterator to a defined beginning void set_begin() noexcept { m_it = begin_value; } /// set iterator to a defined past the end void set_end() noexcept { m_it = end_value; } /// return whether the iterator can be dereferenced constexpr bool is_begin() const noexcept { return m_it == begin_value; } /// return whether the iterator is at end constexpr bool is_end() const noexcept { return m_it == end_value; } friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it == rhs.m_it; } friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it < rhs.m_it; } primitive_iterator_t operator+(difference_type n) noexcept { auto result = *this; result += n; return result; } friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { return lhs.m_it - rhs.m_it; } primitive_iterator_t& operator++() noexcept { ++m_it; return *this; } primitive_iterator_t const operator++(int) noexcept { auto result = *this; ++m_it; return result; } primitive_iterator_t& operator--() noexcept { --m_it; return *this; } primitive_iterator_t const operator--(int) noexcept { auto result = *this; --m_it; return result; } primitive_iterator_t& operator+=(difference_type n) noexcept { m_it += n; return *this; } primitive_iterator_t& operator-=(difference_type n) noexcept { m_it -= n; return *this; } }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/json_pointer.hpp ================================================ #pragma once #include // all_of #include // assert #include // isdigit #include // accumulate #include // string #include // move #include // vector #include #include #include namespace nlohmann { template class json_pointer { // allow basic_json to access private members NLOHMANN_BASIC_JSON_TPL_DECLARATION friend class basic_json; public: /*! @brief create JSON pointer Create a JSON pointer according to the syntax described in [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). @param[in] s string representing the JSON pointer; if omitted, the empty string is assumed which references the whole JSON value @throw parse_error.107 if the given JSON pointer @a s is nonempty and does not begin with a slash (`/`); see example below @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is not followed by `0` (representing `~`) or `1` (representing `/`); see example below @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @since version 2.0.0 */ explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} /*! @brief return a string representation of the JSON pointer @invariant For each JSON pointer `ptr`, it holds: @code {.cpp} ptr == json_pointer(ptr.to_string()); @endcode @return a string representation of the JSON pointer @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} @since version 2.0.0 */ std::string to_string() const { return std::accumulate(reference_tokens.begin(), reference_tokens.end(), std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + escape(b); }); } /// @copydoc to_string() operator std::string() const { return to_string(); } /*! @brief append another JSON pointer at the end of this JSON pointer @param[in] ptr JSON pointer to append @return JSON pointer with @a ptr appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(const json_pointer& ptr) { reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); return *this; } /*! @brief append an unescaped reference token at the end of this JSON pointer @param[in] token reference token to append @return JSON pointer with @a token appended without escaping @a token @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::size_t) to append an array index @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::string token) { push_back(std::move(token)); return *this; } /*! @brief append an array index at the end of this JSON pointer @param[in] array_index array index to append @return JSON pointer with @a array_index appended @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} @complexity Amortized constant. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @sa @ref operator/=(std::string) to append a reference token @sa @ref operator/(const json_pointer&, std::string) for a binary operator @since version 3.6.0 */ json_pointer& operator/=(std::size_t array_index) { return *this /= std::to_string(array_index); } /*! @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer @param[in] lhs JSON pointer @param[in] rhs JSON pointer @return a new JSON pointer with @a rhs appended to @a lhs @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a lhs and @a rhs. @sa @ref operator/=(const json_pointer&) to append a JSON pointer @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& lhs, const json_pointer& rhs) { return json_pointer(lhs) /= rhs; } /*! @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] token reference token @return a new JSON pointer with unescaped @a token appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::string) to append a reference token @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::string token) { return json_pointer(ptr) /= std::move(token); } /*! @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer @param[in] ptr JSON pointer @param[in] array_index array index @return a new JSON pointer with @a array_index appended to @a ptr @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} @complexity Linear in the length of @a ptr. @sa @ref operator/=(std::size_t) to append an array index @since version 3.6.0 */ friend json_pointer operator/(const json_pointer& ptr, std::size_t array_index) { return json_pointer(ptr) /= array_index; } /*! @brief returns the parent of this JSON pointer @return parent of this JSON pointer; in case this JSON pointer is the root, the root itself is returned @complexity Linear in the length of the JSON pointer. @liveexample{The example shows the result of `parent_pointer` for different JSON Pointers.,json_pointer__parent_pointer} @since version 3.6.0 */ json_pointer parent_pointer() const { if (empty()) { return *this; } json_pointer res = *this; res.pop_back(); return res; } /*! @brief remove last reference token @pre not `empty()` @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ void pop_back() { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } reference_tokens.pop_back(); } /*! @brief return last reference token @pre not `empty()` @return last reference token @liveexample{The example shows the usage of `back`.,json_pointer__back} @complexity Constant. @throw out_of_range.405 if JSON pointer has no parent @since version 3.6.0 */ const std::string& back() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } return reference_tokens.back(); } /*! @brief append an unescaped token at the end of the reference pointer @param[in] token token to add @complexity Amortized constant. @liveexample{The example shows the result of `push_back` for different JSON Pointers.,json_pointer__push_back} @since version 3.6.0 */ void push_back(const std::string& token) { reference_tokens.push_back(token); } /// @copydoc push_back(const std::string&) void push_back(std::string&& token) { reference_tokens.push_back(std::move(token)); } /*! @brief return whether pointer points to the root document @return true iff the JSON pointer points to the root document @complexity Constant. @exceptionsafety No-throw guarantee: this function never throws exceptions. @liveexample{The example shows the result of `empty` for different JSON Pointers.,json_pointer__empty} @since version 3.6.0 */ bool empty() const noexcept { return reference_tokens.empty(); } private: /*! @param[in] s reference token to be converted into an array index @return integer representation of @a s @throw out_of_range.404 if string @a s could not be converted to an integer */ static int array_index(const std::string& s) { std::size_t processed_chars = 0; const int res = std::stoi(s, &processed_chars); // check if the string was completely read if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) { JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); } return res; } json_pointer top() const { if (JSON_HEDLEY_UNLIKELY(empty())) { JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); } json_pointer result = *this; result.reference_tokens = {reference_tokens[0]}; return result; } /*! @brief create and return a reference to the pointed to value @complexity Linear in the number of reference tokens. @throw parse_error.109 if array index is not a number @throw type_error.313 if value cannot be unflattened */ BasicJsonType& get_and_create(BasicJsonType& j) const { using size_type = typename BasicJsonType::size_type; auto result = &j; // in case no reference tokens exist, return a reference to the JSON value // j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->type()) { case detail::value_t::null: { if (reference_token == "0") { // start a new array if reference token is 0 result = &result->operator[](0); } else { // start a new object otherwise result = &result->operator[](reference_token); } break; } case detail::value_t::object: { // create an entry in the object result = &result->operator[](reference_token); break; } case detail::value_t::array: { // create an entry in the array JSON_TRY { result = &result->operator[](static_cast(array_index(reference_token))); } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } break; } /* The following code is only reached if there exists a reference token _and_ the current value is primitive. In this case, we have an error situation, because primitive values may only occur as single value; that is, with an empty list of reference tokens. */ default: JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); } } return *result; } /*! @brief return a reference to the pointed to value @note This version does not throw if a value is not present, but tries to create nested values instead. For instance, calling this function with pointer `"/this/that"` on a null value is equivalent to calling `operator[]("this").operator[]("that")` on that value, effectively changing the null value to an object. @param[in] ptr a JSON value @return reference to the JSON value pointed to by the JSON pointer @complexity Linear in the length of the JSON pointer. @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_unchecked(BasicJsonType* ptr) const { using size_type = typename BasicJsonType::size_type; for (const auto& reference_token : reference_tokens) { // convert null values to arrays or objects before continuing if (ptr->is_null()) { // check if reference token is a number const bool nums = std::all_of(reference_token.begin(), reference_token.end(), [](const unsigned char x) { return std::isdigit(x); }); // change value to array for numbers or "-" or to object otherwise *ptr = (nums or reference_token == "-") ? detail::value_t::array : detail::value_t::object; } switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); } if (reference_token == "-") { // explicitly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { // convert array index to number; unchecked access JSON_TRY { ptr = &ptr->operator[]( static_cast(array_index(reference_token))); } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ BasicJsonType& get_checked(BasicJsonType* ptr) const { using size_type = typename BasicJsonType::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); } // note: at performs range check JSON_TRY { ptr = &ptr->at(static_cast(array_index(reference_token))); } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @brief return a const reference to the pointed to value @param[in] ptr a JSON value @return const reference to the JSON value pointed to by the JSON pointer @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const { using size_type = typename BasicJsonType::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); } // use unchecked array access JSON_TRY { ptr = &ptr->operator[]( static_cast(array_index(reference_token))); } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number @throw out_of_range.402 if the array index '-' is used @throw out_of_range.404 if the JSON pointer can not be resolved */ const BasicJsonType& get_checked(const BasicJsonType* ptr) const { using size_type = typename BasicJsonType::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); } // note: at performs range check JSON_TRY { ptr = &ptr->at(static_cast(array_index(reference_token))); } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } break; } default: JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); } } return *ptr; } /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number */ bool contains(const BasicJsonType* ptr) const { using size_type = typename BasicJsonType::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->type()) { case detail::value_t::object: { if (not ptr->contains(reference_token)) { // we did not find the key in the object return false; } ptr = &ptr->operator[](reference_token); break; } case detail::value_t::array: { if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) { // "-" always fails the range check return false; } // error condition (cf. RFC 6901, Sect. 4) if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { JSON_THROW(detail::parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); } JSON_TRY { const auto idx = static_cast(array_index(reference_token)); if (idx >= ptr->size()) { // index out of range return false; } ptr = &ptr->operator[](idx); break; } JSON_CATCH(std::invalid_argument&) { JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); } break; } default: { // we do not expect primitive values if there is still a // reference token to process return false; } } } // no reference token left means we found a primitive value return true; } /*! @brief split the string input to reference tokens @note This function is only called by the json_pointer constructor. All exceptions below are documented there. @throw parse_error.107 if the pointer is not empty or begins with '/' @throw parse_error.108 if character '~' is not followed by '0' or '1' */ static std::vector split(const std::string& reference_string) { std::vector result; // special case: empty reference string -> no reference tokens if (reference_string.empty()) { return result; } // check if nonempty reference string begins with slash if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) { JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'")); } // extract the reference tokens: // - slash: position of the last read slash (or end of string) // - start: position after the previous slash for ( // search for the first slash after the first character std::size_t slash = reference_string.find_first_of('/', 1), // set the beginning of the first reference token start = 1; // we can stop if start == 0 (if slash == std::string::npos) start != 0; // set the beginning of the next reference token // (will eventually be 0 if slash == std::string::npos) start = (slash == std::string::npos) ? 0 : slash + 1, // find next slash slash = reference_string.find_first_of('/', start)) { // use the text between the beginning of the reference token // (start) and the last slash (slash). auto reference_token = reference_string.substr(start, slash - start); // check reference tokens are properly escaped for (std::size_t pos = reference_token.find_first_of('~'); pos != std::string::npos; pos = reference_token.find_first_of('~', pos + 1)) { assert(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 or (reference_token[pos + 1] != '0' and reference_token[pos + 1] != '1'))) { JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); } } // finally, store the reference token unescape(reference_token); result.push_back(reference_token); } return result; } /*! @brief replace all occurrences of a substring by another string @param[in,out] s the string to manipulate; changed so that all occurrences of @a f are replaced with @a t @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f @pre The search string @a f must not be empty. **This precondition is enforced with an assertion.** @since version 2.0.0 */ static void replace_substring(std::string& s, const std::string& f, const std::string& t) { assert(not f.empty()); for (auto pos = s.find(f); // find first occurrence of f pos != std::string::npos; // make sure f was found s.replace(pos, f.size(), t), // replace with t, and pos = s.find(f, pos + t.size())) // find next occurrence of f {} } /// escape "~" to "~0" and "/" to "~1" static std::string escape(std::string s) { replace_substring(s, "~", "~0"); replace_substring(s, "/", "~1"); return s; } /// unescape "~1" to tilde and "~0" to slash (order is important!) static void unescape(std::string& s) { replace_substring(s, "~1", "/"); replace_substring(s, "~0", "~"); } /*! @param[in] reference_string the reference string to the current value @param[in] value the value to consider @param[in,out] result the result object to insert values to @note Empty objects or arrays are flattened to `null`. */ static void flatten(const std::string& reference_string, const BasicJsonType& value, BasicJsonType& result) { switch (value.type()) { case detail::value_t::array: { if (value.m_value.array->empty()) { // flatten empty array as null result[reference_string] = nullptr; } else { // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_value.array->size(); ++i) { flatten(reference_string + "/" + std::to_string(i), value.m_value.array->operator[](i), result); } } break; } case detail::value_t::object: { if (value.m_value.object->empty()) { // flatten empty object as null result[reference_string] = nullptr; } else { // iterate object and use keys as reference string for (const auto& element : *value.m_value.object) { flatten(reference_string + "/" + escape(element.first), element.second, result); } } break; } default: { // add primitive value with its reference string result[reference_string] = value; break; } } } /*! @param[in] value flattened JSON @return unflattened JSON @throw parse_error.109 if array index is not a number @throw type_error.314 if value is not an object @throw type_error.315 if object values are not primitive @throw type_error.313 if value cannot be unflattened */ static BasicJsonType unflatten(const BasicJsonType& value) { if (JSON_HEDLEY_UNLIKELY(not value.is_object())) { JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); } BasicJsonType result; // iterate the JSON object values for (const auto& element : *value.m_value.object) { if (JSON_HEDLEY_UNLIKELY(not element.second.is_primitive())) { JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); } // assign value to reference pointed to by JSON pointer; Note that if // the JSON pointer is "" (i.e., points to the whole value), function // get_and_create returns a reference to result itself. An assignment // will then create a primitive value. json_pointer(element.first).get_and_create(result) = element.second; } return result; } /*! @brief compares two JSON pointers for equality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is equal to @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept { return lhs.reference_tokens == rhs.reference_tokens; } /*! @brief compares two JSON pointers for inequality @param[in] lhs JSON pointer to compare @param[in] rhs JSON pointer to compare @return whether @a lhs is not equal @a rhs @complexity Linear in the length of the JSON pointer @exceptionsafety No-throw guarantee: this function never throws exceptions. */ friend bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept { return not (lhs == rhs); } /// the reference tokens std::vector reference_tokens; }; } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/json_ref.hpp ================================================ #pragma once #include #include #include namespace nlohmann { namespace detail { template class json_ref { public: using value_type = BasicJsonType; json_ref(value_type&& value) : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) {} json_ref(const value_type& value) : value_ref(const_cast(&value)), is_rvalue(false) {} json_ref(std::initializer_list init) : owned_value(init), value_ref(&owned_value), is_rvalue(true) {} template < class... Args, enable_if_t::value, int> = 0 > json_ref(Args && ... args) : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) {} // class should be movable only json_ref(json_ref&&) = default; json_ref(const json_ref&) = delete; json_ref& operator=(const json_ref&) = delete; json_ref& operator=(json_ref&&) = delete; ~json_ref() = default; value_type moved_or_copied() const { if (is_rvalue) { return std::move(*value_ref); } return *value_ref; } value_type const& operator*() const { return *static_cast(value_ref); } value_type const* operator->() const { return static_cast(value_ref); } private: mutable value_type owned_value = nullptr; value_type* value_ref = nullptr; const bool is_rvalue; }; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/macro_scope.hpp ================================================ #pragma once #include // pair #include // This file contains all internal macro definitions // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif #endif // C++ language standard detection #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // disable float-equal warnings on GCC/clang #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif // disable documentation warnings on clang #if defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdocumentation" #endif // allow to disable exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #include #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) #define JSON_INTERNAL_CATCH(exception) if(false) #endif // override exception macros #if defined(JSON_THROW_USER) #undef JSON_THROW #define JSON_THROW JSON_THROW_USER #endif #if defined(JSON_TRY_USER) #undef JSON_TRY #define JSON_TRY JSON_TRY_USER #endif #if defined(JSON_CATCH_USER) #undef JSON_CATCH #define JSON_CATCH JSON_CATCH_USER #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_CATCH_USER #endif #if defined(JSON_INTERNAL_CATCH_USER) #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @since version 3.4.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ { \ return ej_pair.first == e; \ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer> #define NLOHMANN_BASIC_JSON_TPL \ basic_json ================================================ FILE: thirdparty/nlohmann/detail/macro_unscope.hpp ================================================ #pragma once // restore GCC/clang diagnostic settings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif #if defined(__clang__) #pragma GCC diagnostic pop #endif // clean up #undef JSON_INTERNAL_CATCH #undef JSON_CATCH #undef JSON_THROW #undef JSON_TRY #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef NLOHMANN_BASIC_JSON_TPL_DECLARATION #undef NLOHMANN_BASIC_JSON_TPL #include ================================================ FILE: thirdparty/nlohmann/detail/meta/cpp_future.hpp ================================================ #pragma once #include // not #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type namespace nlohmann { namespace detail { // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; template using uncvref_t = typename std::remove_cv::type>::type; // implementation of C++14 index_sequence and affiliates // source: https://stackoverflow.com/a/32223343 template struct index_sequence { using type = index_sequence; using value_type = std::size_t; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; template struct merge_and_renumber; template struct merge_and_renumber, index_sequence> : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; template struct make_index_sequence : merge_and_renumber < typename make_index_sequence < N / 2 >::type, typename make_index_sequence < N - N / 2 >::type > {}; template<> struct make_index_sequence<0> : index_sequence<> {}; template<> struct make_index_sequence<1> : index_sequence<0> {}; template using index_sequence_for = make_index_sequence; // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; // taken from ranges-v3 template struct static_const { static constexpr T value{}; }; template constexpr T static_const::value; } // namespace detail } // namespace nlohmann ================================================ FILE: thirdparty/nlohmann/detail/meta/detected.hpp ================================================ #pragma once #include #include // http://en.cppreference.com/w/cpp/experimental/is_detected namespace nlohmann { namespace detail { struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; nonesuch(nonesuch const&&) = delete; void operator=(nonesuch const&) = delete; void operator=(nonesuch&&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template