PHP: php-uv を使って echo サーバーをつくる

PECL モジュールの php-uv は libuv のバイディングである。

$tcp = uv_tcp_init();

uv_tcp_bind($tcp, uv_ip4_addr('0.0.0.0', 12345));

uv_listen($tcp, 100, function($server) {
    $client = uv_tcp_init();
    uv_accept($server, $client);

    $body = 'hello world';

    $msg = "HTTP/1.1 200 OK\r\n".
        "Content-Length: " . strlen($body) . "\r\n" .
        "Content-Type: text/html; charset=utf-8\r\n".
        "Connection: close\r\n".
        "\r\n".
        $body ."\r\n".
        "\r\n";

    uv_write($client, $msg, function($stream, $stat) {
        uv_close($stream);
    });

});

uv_run();

PHP: pecl_http を導入する

phpbrew で導入した際に少し手間取ったので、記録に残しておく。pecl_http は raphf と propro に依存しているので、 これらの PECL モジュールを先にインストールしておく必要がある。

phpbrew ext install raphf
phpbrew ext install propro
phpbrew ext install pecl_http

上記のコマンドを実行した後で pecl_http のクラスを利用しようとしても、raphf モジュールがすでにロードされたという警告と、クラスが定義されていないというエラーが表示される。

Warning: Module 'raphf' already loaded in Unknown on line 0
PHP Fatal error:  Class 'http\Message' not found in /Users/masakielastic/test/test.php on line 12

これは追加の ini ファイルで指定している PECL モジュールのファイルの名前がまちがっているためである。raphf.so を raphf.so に修正してみよう。

# /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/var/db/pecl_http.ini
-extension=raphf.so
+extension=http.so

この状態で PHP コマンドを実行すると、今度は次のような警告が発せられる。

PHP Warning:  PHP Startup: Unable to load dynamic library '/Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/lib/php/extensions/no-debug-non-zts-20131226/http.so' - dlopen(/Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/lib/php/extensions/no-debug-non-zts-20131226/http.so, 9): Symbol not found: _php_persistent_handle_abandon
  Referenced from: /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/lib/php/extensions/no-debug-non-zts-20131226/http.so
  Expected in: flat namespace
 in /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/lib/php/extensions/no-debug-non-zts-20131226/http.so in Unknown on line 0

これは raphf がロードされる前に pecl_http がロードされるために起きる問題である。 対策は pecl_http.ini、propro.ini、raphf.ini の名前を修正してロードされる順番を修正するか、1つのファイルにまとめてしまうかである。 私は pecl_http.ini で3つのモジュールをロードすることにした。

# /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/var/db/pecl_http.ini
extension=propro.so
extension=raphf.so
extension=http.so

モジュールのロードが重複しないように、次のファイルを削除した。

rm /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/var/db/propro.ini
rm /Users/masakielastic/.phpbrew/php/php-5.6.0alpha3/var/db/raphf.ini

PHP: HTTP メッセージを解析する

PHP は HTTP メッセージを内部で解析する機能をユーザー関数としては公開していないので、 自前で解析するか、PHP ライブラリや PECL を導入する必要がある。PECL であれば pecl_http や php-uv を使うことができる。

$body = 'hello world';

$msg = "HTTP/1.1 200 OK\r\n".
       "Content-Length: " . strlen($body) . "\r\n" .
       "Content-Type: text/html; charset=utf-8\r\n".
       "Connection: close\r\n".
       "\r\n".
       $body ."\r\n".
       "\r\n";

$obj = new http\Message($msg);

var_dump(
[
    $obj->getHeaders(),
    (string) $obj->getBody()
]);

$parser = uv_http_parser_init(UV::HTTP_BOTH);
$ret = [];

uv_http_parser_execute($parser, $msg, $ret);

var_dump($ret);

Ruby: TCPSocket、Socket クラスを使って TCP、SSL クライアントをつくる

まずは TCPSocket を使う場合。

require 'socket'

msg = "GET / HTTP/1.1\r\n" \
     + "Host: localhost\r\n" \
     + "Connection: close\r\n" \
     + "\r\n"

s = TCPSocket.new 'localhost', 12345
s.puts msg

ret = ''
while line = s.gets
  ret += line
end

s.close

puts ret

Socket クラスを使う場合、次のようになる。

require 'socket'
include Socket::Constants

port = 12345
host = '0.0.0.0'

msg = "GET / HTTP/1.1\r\n" \
     + "Host: localhost\r\n" \
     + "Connection: close\r\n" \
     + "\r\n"

socket = Socket.new AF_INET, SOCK_STREAM
socket.set_encoding 'UTF-8'
sockaddr = Socket.pack_sockaddr_in port, host
socket.connect sockaddr
socket.write msg
ret = socket.read
socket.close

puts ret

次に SSL クライアントを書いてみる。TCPSocket を OpenSSL::SSL::SSLSocket で包めばよい。

require 'socket'
require 'openssl'

port = 12345
host = '0.0.0.0'

msg = "GET / HTTP/1.1\r\n" \
     + "Host: localhost\r\n" \
     + "Connection: close\r\n" \
     + "\r\n"

context = OpenSSL::SSL::SSLContext.new
socket = TCPSocket.new host, port
ssl_client = OpenSSL::SSL::SSLSocket.new socket, context
ssl_client.connect
ssl_client.puts msg
ret = ssl_client.read

puts ret

Ruby: HTTP メッセージを解析する

Node.js で使われている HTTP Parser の Ruby バインディングを利用すれば、生の HTML メッセージを解析することができる。

require "http/parser"

body = "hello world"
msg = "HTTP/1.1 200 OK\r\n" \
    + "Content-Type: text/html; charset=utf-8\r\n" \
    + ("Content-Length: %d\r\n" % body.bytesize) \
    + "Connection: Close\r\n" \
    + "\r\n" \
    + body

parser = Http::Parser.new
ret = ''
parser.on_body = proc do |chunk|
  ret += chunk
end

parser << msg

puts parser.headers
puts ret
アーカイブ ランダム ホーム 次のページ ページ 1 / 332