Skip to content

Instantly share code, notes, and snippets.

@Jiab77
Last active September 10, 2023 14:26
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jiab77/4d3c1abeb9be4119f33031750cf580d4 to your computer and use it in GitHub Desktop.
Save Jiab77/4d3c1abeb9be4119f33031750cf580d4 to your computer and use it in GitHub Desktop.
An easy way to start the embedded PHP web server with the multi-thread option enabled or simulated when necessary.

Simple multi-threaded PHP web server

An easy way to start the embedded PHP web server with the multi-thread option enabled or simulated when necessary.

Context

In all of my coding projects, I like to follow these principles:

While also trying to avoid this:

Which then imply a lot of research to make sure I would not reinvent the wheel when it is not necessary ๐Ÿ˜…

But for this use case, I was also too much lazy to always having to think about adding PHP_CLI_SERVER_WORKERS=$(nproc) before running php -S and then unlock the recently added multi-threading feature ๐Ÿค˜

I've also tried to improve the lack of connection concurrency in the older PHP versions (< 7.4) but it was pretty hacky and didn't worked very well... Then I've found something interesting on StackOverflow that has solved this issue but needed some minor improvement like simply tuning the sleep value.

Installation

In order to simulate the connection concurrency / multi-threading feature in older PHP versions ONLY, it will be required to install the ucspi-tcp package before running the script simple-php-webserver.sh.

Skip this step if you are currently running or planning to install PHP version >= 7.4.

# For Ubuntu based distrib
sudo apt install ucspi-tcp

# For RPM based distrib
sudo yum install ucspi-tcp

# For modern RPM based distrib
sudo dnf install ucspi-tcp

You can replace by or add the ucspi-tcp-ipv6 package if you want to also handle IPv6 connections for old PHP versions.

And for running the web server without any other dependencies:

# For Ubuntu based distrib
sudo apt install php-cli

# For RPM based distrib
sudo yum install php-cli

# For modern RPM based distrib
sudo dnf install php-cli

It should automatically install few other required dependencies but still stays a very minimal PHP stack installed.

Usage

The script simple-php-webserver.sh will first detect the currently installed PHP version then according to the test result, decide to use the embedded web server or rely on tcpserver throught the proxy-to-php-server.sh script to simulate the multi-threading feature and unlock the multi connections concurrency power that is lacking in older PHP versions.

So in the end, instead of always typing:

# For single file
PHP_CLI_SERVER_WORKERS=$(nproc) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} index.php

# For directory
PHP_CLI_SERVER_WORKERS=$(nproc) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} -t .

I just have to type:

./simple-php-webserver.sh

And I don't have to worry about the lack of connection concurrency anymore ๐Ÿ˜

The magic thing here is the use of nproc to detect how many available CPU cores to decide how many server workers should be created internally in PHP.

This way, I don't have to set an arbitrary value and risk to overload the host.

Improvements

The script simple-php-webserver.sh can be easily improved to let's say better handling the default PHP file to read or support a router.php file for example.

I'll probably try take the time to improve it later ๐Ÿ˜…

Feel free to comment this gist if you want to improve it ๐Ÿ˜‰

Scripts

  • simple-php-webserver.sh:
#!/bin/bash

# Increasing local web server performances as possible
# https://stackoverflow.com/questions/39842170/load-balancing-php-built-in-server/47103758#47103758
# https://www.php.net/manual/en/features.commandline.webserver.php

# Config
LISTEN_INTERFACE="localhost"
LISTEN_PORT=8000
ENTRY_POINT=$1

# Detect server type to use
PHP_SRV_TYPE=$(php -r "if (version_compare(phpversion(), '7.4', '<')) { echo 'tcpserver'; } else { echo 'embedded'; }")

# Run detected server type
if [[ $PHP_SRV_TYPE == 'embedded' ]]; then
    if [[ -d $ENTRY_POINT ]]; then
        PHP_CLI_SERVER_WORKERS=$(nproc) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} -t $ENTRY_POINT
    else
        PHP_CLI_SERVER_WORKERS=$(nproc) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} $ENTRY_POINT
    fi
else
    tcpserver -v -1 0 ${LISTEN_PORT} ./proxy-to-php-server.sh $ENTRY_POINT
fi
  • proxy-to-php-server.sh:
#!/bin/bash -x

# Increasing local web server performances as possible
# https://stackoverflow.com/questions/39842170/load-balancing-php-built-in-server/47103758#47103758

# get a random port -- this could be improved
port=$(shuf -i 2048-65000 -n 1)

# start the PHP server in the background
if [[ -d "$(realpath ${1:?Missing path to serve})" ]]; then
    php -S localhost:"${port}" -t "$(realpath ${1:?Missing path to serve})" &
else
    php -S localhost:"${port}" "$(realpath ${1:?Missing path to serve})" &
fi
pid=$!

# try to adjust delay to improve performances
sleep 0.2

# proxy standard in to nc on that port
nc localhost "${port}"

# kill the server we started
kill "${pid}"

Make them executable with chmod -v +x simple-php-webserver.sh proxy-to-php-server.sh.

@staatzstreich
Copy link

Hey ๐Ÿ‘‹

thanks for sharing๐Ÿ‘
Did not know that before ...
On MacOS an PHP installed with Homebrew it works like so:

# For single file
PHP_CLI_SERVER_WORKERS=$(sysctl -n hw.ncpu) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} index.php

# For directory
PHP_CLI_SERVER_WORKERS=$(sysctl -n hw.ncpu) php -S ${LISTEN_INTERFACE}:${LISTEN_PORT} -t .

@Jiab77
Copy link
Author

Jiab77 commented Feb 5, 2023

Hi @staatzstreich,

Thanks a lot for your comment! I'll add it to this gist ASAP. In the meantime, I've made a little project that does not rely on bash scripts or external dependencies but it does not support older PHP version.

You can find it here if you're interested:

In case you'd like the same but for NodeJS, I've made this other project:

Don't feel obligated to try them, it's purely a suggestion.

Many thanks again for your appreciation of my gist ๐Ÿ™‡โ€โ™‚๏ธ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment