自定义 Web 代理:分叉进程或发出 http 请求更好吗?

发布于 2024-10-01 08:43:30 字数 364 浏览 1 评论 0原文

很抱歉这个标题,但很难用几句话来解释。

我编写了一个小型 Web 代理(不是 apache 或任何类型的常见 Web 服务器),其作用是执行一些 php 代码。

有两种方法:

1) fork 一个新的 php -f file.php

2) 调用 http://localhost/file .php 来自网络代理。

我认为该代理会有很多并发请求,并且每个请求将保持活动状态至少 20-30 秒。

我的问题是:分叉和通过 http 请求哪个更好?

感谢您的任何提示!

达里奥

sorry for that title but it is very hard to explain in a few words.

I wrote a little web proxy -not apache or any kind of common webserver- who's role is to execute some php code.

There are two ways of do it:

1) fork a new php -f file.php

2) call http://localhost/file.php from within the web proxy.

I think there would be a lot of concurrent requests to that proxy, and each request will keep alive for at least 20-30 seconds.

My question is: which is better between forking and requesting via http ?

Thanks for any hint!

Dario

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

捎一片雪花 2024-10-08 08:43:31

我最近也做了代理。而且 - 还有第三种选择。甚至不需要调用自身或另一个脚本,它是完全独立的,并且跨平台......

所以 - 第一件事是我已经使用套接字完成了此操作。我想你也这样做了,但只是写在这里以防万一你没有这样做。我将浏览器中的代理设置为特定端口,允许该端口通过防火墙进入侦听 PHP 脚本。为了让它开始监听,我必须在端口 80 上“运行”脚本,这样我也得到了一个很好的实时控制台。

因此 - 脚本侦听套接字,并将其设置为非阻塞。它使用循环(无限,需要时使用中断超时) - *@socket_accept()* 用于查看是否有任何新连接,检查是否 !== false。

最后,循环 (:D) 遍历所有生成的子套接字,这些子套接字存储在数组中。它是一个 HTTP 代理,因此它们都有一些特定的阶段(客户端发送标头、尝试到达远程服务器、发送客户端标头、从远程服务器接收数据)。像读取这样的事情(在使用 fsockopen() 打开的远程套接字上)通常会被阻塞,并且没有办法将其设置为非阻塞,通过以下方式“模拟”非阻塞模式非常极端的超时 - 比如 5 微秒,最多 5 微秒。读取的字符数为 128。

tl;dr;本质上,这就像处理器多线程。

但是!!!如果这样做,您需要套接字。

编辑:添加从所有自定义操作中删除的示例代码:(是的,它很长)

set_time_limit(0); // so we don't get a timeout from PHP
// - can lead to sockets left open...

$config_port = 85; // port
$config_address = "192.168.0.199"; // IP address, most likely local with router
// if there's not a router between server and wan, use the wan IP
$config_to = 30; // global timeout in seconds
$config_connect_to = 2; // timeout for connecting to remote server
$config_client_to = 0.1; // timeout for reading client data
$config_remote_to = 10; // timeout for reading remote data (microseconds)
$config_remote_stage_to = 15; // timeout for last stage (seconds)
$config_backlog = 5000; // max backlogs, the more the better (usually)

$parent_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // parent socket
$tmp_res = @socket_bind($parent_sock, $config_address, $config_port);
if ($tmp_res === false){
    echo "Can't bind socket.";
    exit;
    // address or port in use, for example by Apache
}

$tmp_res = @socket_listen($parent_sock, $config_backlog);
if ($tmp_res === false){
    echo "Can't start socket listener.";
    exit;
    // hard to tell what can cause this
}

socket_set_nonblock($parent_sock); // non-blocking mode

$sockets = array(); // children sockets
$la = time(); // last activity
while (time() - $la < $config_to){
    $spawn = @socket_accept($parent_sock); // check for new connection
    if ($spawn !== false){
        $la = time();

        $ni = count($sockets);
        $sockets[$ni] = array();
        $sockets[$ni]["handle"] = $spawn;
        $sockets[$ni]["stage"] = 1;
        $sockets[$ni]["la"] = time(); // for some stages
        $sockets[$ni]["client_data"] = "";
        $sockets[$ni]["headers"] = array();
        $sockets[$ni]["remote"] = false;
    }

    foreach ($sockets as &$sock){ // &$sock because we're gonna edit the var
        switch ($sock["stage"]){
        case 1: // receive client data
        $read_data = @socket_read($sock["handle"], 2048);
        if ($read_data !== false && $read_data !== ""){
            $la = time();
            $sock["la"] = microtime(true);
            $sock["client_data"] .= $read_data;
        } else if (microtime(true) - $sock["la"] > $config_client_to) {
            // client data received (or too slow :D)
            $sock["stage"] = 2; // go on
        }
        break;
        case 2: // connect to remote
        $headers = explode("\r\n", $sock["client_data"]);
        foreach ($headers as $hdr){
            $h_pos = strpos($hdr, ":");
            if ($h_pos !== false){
                $nhid = count($sock["headers"]);
                $sock["headers"][strtolower(substr($hdr, 0, $h_pos))] = ltrim(substr($hdr, $h_pos + 1));
            }
        }

        // we'll have to use the "Host" header to know target server
        $sock["remote"] = @fsockopen($sock["headers"]["host"], 80, $sock["errno"], $sock["errstr"], $config_connect_to);
        if ($sock["remote"] === false){
            // error, echo it and close client socket, set stage to 0
            echo "Socket error: #".$sock["errno"].": ".$sock["errstr"]."<br />\n";
            flush(); // immediately show the error
            @socket_close($sock["handle"]);
            $sock["handle"] = 0;
        } else {
            $la = time();
            // okay - connected
            $sock["stage"] = 3;
        }
        break;
        case 3: // send client data
        $tmp_res = @fwrite($sock["remote"], $sock["client_data"]);
        // this currently supports just client data up to 8192 bytes long
        if ($tmp_res === false){
            // error
            echo "Couldn't send client data to remote server!<br />\n";
            flush();
            @socket_close($sock["handle"]);
            @fclose($sock["remote"]);
            $sock["stage"] = 0;
        } else {
            // client data sent
            $la = time();
            stream_set_timeout($sock["remote"], $config_remote_to);
            $sock["la"] = time(); // we'll need this in stage 4
            $sock["stage"] = 4;
        }
        break;
        case 4:
        $remote_read = @fread($sock["remote"], 128);
        if ($remote_read !== false && $remote_read !== ""){
            $la = time();
            $sock["la"] = time();
            @socket_write($sock["handle"], $remote_read);
        } else {
            if (time() - $sock["la"] >= $config_remote_stage_to){
                echo "Timeout.<br />\n";
                flush();
                @socket_close($sock["handle"]);
                @fclose($sock["remote"]);
                $sock["stage"] = 0;
            }
        }
        break;
        }
    }
}

foreach($sockets as $sock){
    @socket_close($sock["handle"]);
    @fclose($sock["remote"]);
}
@socket_close($parent_sock);

I did a proxy too, recently. And - there's a third option. Doesn't even need to call itself or another script, it's completely self-contained, and cross-platform...

So - first thing is that I have done this using sockets. I guess you did too, but just writing it here in case you did not. I set the proxy in browser to a specific port, which is allowed through firewall into the listening PHP script. To make it start listening, I have to "run" the script on port 80, so I also get a nice real-time console.

So - the script listens on the socket, and it is set to NON-BLOCKING. It works with a loop (infinite, times out using break when needed) - *@socket_accept()* is used to see if there is any new connection, checked if !== false.

Finally, the loop loops (:D) through all of the spawned children sockets, which are stored in an array. It's an HTTP proxy, so all of them have a few specific stages (client sending headers, trying to reach remote server, sending client headers, receiving data from remote server). Things like reading which would (on remote socket opened with fsockopen()) normally be blocked, and there is no way to set that to non-blocking on these, are "emulating" non-blocking mode by a very extreme time out - like 5 microseconds, and max. chars read is 128.

tl;dr; essentially this is like processor multi-threading.

HOWEVER!!! you need sockets if it's done like this.

EDIT: adding an example code stripped from all custom actions: (yeah, it's long)

set_time_limit(0); // so we don't get a timeout from PHP
// - can lead to sockets left open...

$config_port = 85; // port
$config_address = "192.168.0.199"; // IP address, most likely local with router
// if there's not a router between server and wan, use the wan IP
$config_to = 30; // global timeout in seconds
$config_connect_to = 2; // timeout for connecting to remote server
$config_client_to = 0.1; // timeout for reading client data
$config_remote_to = 10; // timeout for reading remote data (microseconds)
$config_remote_stage_to = 15; // timeout for last stage (seconds)
$config_backlog = 5000; // max backlogs, the more the better (usually)

$parent_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // parent socket
$tmp_res = @socket_bind($parent_sock, $config_address, $config_port);
if ($tmp_res === false){
    echo "Can't bind socket.";
    exit;
    // address or port in use, for example by Apache
}

$tmp_res = @socket_listen($parent_sock, $config_backlog);
if ($tmp_res === false){
    echo "Can't start socket listener.";
    exit;
    // hard to tell what can cause this
}

socket_set_nonblock($parent_sock); // non-blocking mode

$sockets = array(); // children sockets
$la = time(); // last activity
while (time() - $la < $config_to){
    $spawn = @socket_accept($parent_sock); // check for new connection
    if ($spawn !== false){
        $la = time();

        $ni = count($sockets);
        $sockets[$ni] = array();
        $sockets[$ni]["handle"] = $spawn;
        $sockets[$ni]["stage"] = 1;
        $sockets[$ni]["la"] = time(); // for some stages
        $sockets[$ni]["client_data"] = "";
        $sockets[$ni]["headers"] = array();
        $sockets[$ni]["remote"] = false;
    }

    foreach ($sockets as &$sock){ // &$sock because we're gonna edit the var
        switch ($sock["stage"]){
        case 1: // receive client data
        $read_data = @socket_read($sock["handle"], 2048);
        if ($read_data !== false && $read_data !== ""){
            $la = time();
            $sock["la"] = microtime(true);
            $sock["client_data"] .= $read_data;
        } else if (microtime(true) - $sock["la"] > $config_client_to) {
            // client data received (or too slow :D)
            $sock["stage"] = 2; // go on
        }
        break;
        case 2: // connect to remote
        $headers = explode("\r\n", $sock["client_data"]);
        foreach ($headers as $hdr){
            $h_pos = strpos($hdr, ":");
            if ($h_pos !== false){
                $nhid = count($sock["headers"]);
                $sock["headers"][strtolower(substr($hdr, 0, $h_pos))] = ltrim(substr($hdr, $h_pos + 1));
            }
        }

        // we'll have to use the "Host" header to know target server
        $sock["remote"] = @fsockopen($sock["headers"]["host"], 80, $sock["errno"], $sock["errstr"], $config_connect_to);
        if ($sock["remote"] === false){
            // error, echo it and close client socket, set stage to 0
            echo "Socket error: #".$sock["errno"].": ".$sock["errstr"]."<br />\n";
            flush(); // immediately show the error
            @socket_close($sock["handle"]);
            $sock["handle"] = 0;
        } else {
            $la = time();
            // okay - connected
            $sock["stage"] = 3;
        }
        break;
        case 3: // send client data
        $tmp_res = @fwrite($sock["remote"], $sock["client_data"]);
        // this currently supports just client data up to 8192 bytes long
        if ($tmp_res === false){
            // error
            echo "Couldn't send client data to remote server!<br />\n";
            flush();
            @socket_close($sock["handle"]);
            @fclose($sock["remote"]);
            $sock["stage"] = 0;
        } else {
            // client data sent
            $la = time();
            stream_set_timeout($sock["remote"], $config_remote_to);
            $sock["la"] = time(); // we'll need this in stage 4
            $sock["stage"] = 4;
        }
        break;
        case 4:
        $remote_read = @fread($sock["remote"], 128);
        if ($remote_read !== false && $remote_read !== ""){
            $la = time();
            $sock["la"] = time();
            @socket_write($sock["handle"], $remote_read);
        } else {
            if (time() - $sock["la"] >= $config_remote_stage_to){
                echo "Timeout.<br />\n";
                flush();
                @socket_close($sock["handle"]);
                @fclose($sock["remote"]);
                $sock["stage"] = 0;
            }
        }
        break;
        }
    }
}

foreach($sockets as $sock){
    @socket_close($sock["handle"]);
    @fclose($sock["remote"]);
}
@socket_close($parent_sock);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文