Last active
December 2, 2022 23:53
-
-
Save ammarfaizi2/bab26ac544b4ee23bdc156ad074a563d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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