Skip to content

Instantly share code, notes, and snippets.

@ammarfaizi2
Last active December 2, 2022 23:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ammarfaizi2/bab26ac544b4ee23bdc156ad074a563d to your computer and use it in GitHub Desktop.
Save ammarfaizi2/bab26ac544b4ee23bdc156ad074a563d to your computer and use it in GitHub Desktop.
<?php
/*
* @author Ammar Faizi <ammarfaizi2@gnuweeb.org>
*
* A simple HTTP Server. I make this program just to do a challenge
* from @codenoid.
*
* Link: https://t.me/GNUWeebTDD/7822
* Link: https://twitter.com/imrenagi/status/1591659569637634048
*/
$g_clients_state = [];
function create_tcp_server(string $bind_addr, int $bind_port)
{
$tcp_fd = socket_create(AF_INET, SOCK_STREAM, 0);
if (!$tcp_fd)
return false;
if (!socket_bind($tcp_fd, $bind_addr, $bind_port))
goto err;
if (!socket_listen($tcp_fd))
goto err;
if (!socket_set_nonblock($tcp_fd))
goto err;
return $tcp_fd;
err:
socket_close($tcp_fd);
return false;
}
function http_get_client_state($client_fd): ?StdClass
{
global $g_clients_state;
$key = spl_object_hash($client_fd);
if (isset($g_clients_state[$key]))
return $g_clients_state[$key];
return NULL;
}
function http_set_client_state($client_fd): StdClass
{
global $g_clients_state;
$obj = new StdClass;
$obj->addr = NULL;
$obj->port = NULL;
$obj->buf = "";
$obj->fd = $client_fd;
$g_clients_state[spl_object_hash($client_fd)] = $obj;
return $obj;
}
function http_close_client_state($client_fd)
{
global $g_clients_state;
unset($g_clients_state[spl_object_hash($client_fd)]);
}
function http_accept_client($tcp_fd, array &$clients): int
{
$client_fd = socket_accept($tcp_fd);
if ($client_fd === false)
return 1;
$addr = NULL;
$port = NULL;
if (!socket_getpeername($client_fd, $addr, $port)) {
socket_close($client_fd);
return 1;
}
printf("Accepted a new connection from %s:%d\n", $addr, $port);
$client_obj = http_set_client_state($client_fd);
$client_obj->addr = $addr;
$client_obj->port = $port;
$client_obj->fd = $client_fd;
$clients[] = $client_fd;
return 0;
}
function str_has_dcrlf(string $buf): bool
{
return (count(explode("\r\n\r\n", $buf, 2)) > 1);
}
function http_parse_fhdr(StdClass $obj, string $fhdr): bool
{
$m = [];
if (!preg_match("/^(\S*) (\/\S*) HTTP\/.+$/", $fhdr, $m))
return false;
$obj->uri = $m[2];
$obj->method = $m[1];
return true;
}
function http_send_bad_req_response(StdClass $obj): void
{
$txt = "Bad Request!\n";
$buf = "HTTP/1.1 400 Bad Request\r\n".
"Content-Length: ".strlen($txt)."\r\n".
"Content-Type: text/plain\r\n\r\n".
$txt;
socket_send($obj->fd, $buf, strlen($buf), MSG_DONTWAIT);
}
function http_send_not_found_response(StdClass $obj): void
{
$txt = "Not Found!\n";
$buf = "HTTP/1.1 404 Not Found\r\n".
"Content-Length: ".strlen($txt)."\r\n".
"Content-Type: text/plain\r\n\r\n".
$txt;
socket_send($obj->fd, $buf, strlen($buf), MSG_DONTWAIT);
}
function http_send_ok_response(StdClass $obj, string $txt): void
{
$buf = "HTTP/1.1 200 OK\r\n".
"Content-Length: ".strlen($txt)."\r\n".
"Content-Type: text/plain\r\n\r\n".
$txt;
socket_send($obj->fd, $buf, strlen($buf), MSG_DONTWAIT);
}
function http_process_route(StdClass $obj): void
{
$uri = explode("?", $obj->uri, 2);
$qs = $uri[1] ?? NULL;
$path = $uri[0];
switch ($path) {
case "/":
$txt = "This is the index page\n";
break;
case "/hello":
$txt = "Hello World!\n";
break;
default:
http_send_not_found_response($obj);
return;
}
http_send_ok_response($obj, $txt);
}
function http_process_client_buffer(StdClass $obj): void
{
$buf = $obj->buf;
$buf = explode("\r\n\r\n", $buf, 2);
$headers = explode("\r\n", $buf[0]);
$body = $buf[1];
if (!http_parse_fhdr($obj, $headers[0])) {
http_send_bad_req_response($obj);
return;
}
/*
* Currently, we only support GET method.
*/
if ($obj->method !== "GET") {
http_send_bad_req_response($obj);
return;
}
http_process_route($obj);
}
function http_handle_client_packet(StdClass $obj): bool
{
$ret = socket_recv($obj->fd, $buf, 1024, MSG_DONTWAIT);
if ($ret === false)
return false;
if ($ret > 0)
$obj->buf .= $buf;
if (str_has_dcrlf($obj->buf)) {
http_process_client_buffer($obj);
return false;
}
return true;
}
function http_close_all_clients(array &$clients)
{
foreach ($clients as $k => $client) {
unset($clients[$k]);
socket_close($client);
}
}
function http_process_ready_sockets($tcp_fd, array &$read, array &$clients)
{
if (in_array($tcp_fd, $read)) {
$ret = http_accept_client($tcp_fd, $clients);
if ($ret)
return $ret;
}
foreach ($read as $client) {
if ($client === $tcp_fd)
continue;
$obj = http_get_client_state($client);
if (http_handle_client_packet($obj))
continue;
printf("Closing a connection from %s:%d...\n", $obj->addr,
$obj->port);
http_close_client_state($obj);
socket_close($obj->fd);
foreach ($clients as $k => $v) {
if ($v === $client)
unset($clients[$k]);
}
}
}
function http_do_event_loop($tcp_fd, array &$clients): int
{
/*
* Find all sockets that are ready for a read operation.
*/
$read = array_merge([$tcp_fd], $clients);
$write = NULL;
$except = NULL;
$ret = socket_select($read, $write, $except, NULL);
if ($ret === false)
goto err;
$ret = http_process_ready_sockets($tcp_fd, $read, $clients);
if ($ret)
goto err;
return 0;
err:
http_close_all_clients($clients);
return $ret;
}
function http_run_server($tcp_fd)
{
$clients = [];
while (true) {
$ret = http_do_event_loop($tcp_fd, $clients);
if ($ret)
break;
}
socket_close($tcp_fd);
return $ret;
}
function main(int $argc, array $argv)
{
$bind_addr = "0.0.0.0";
$bind_port = 4444;
$tcp_fd = create_tcp_server($bind_addr, $bind_port);
if (!$tcp_fd)
return 1;
printf("Listening on %s:%d...\n", $bind_addr, $bind_port);
return http_run_server($tcp_fd);
}
exit(main($_SERVER["argc"], $_SERVER["argv"]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment