цепочка инструментов, обеспечивающая двустороннюю связь между приложением D и браузером - PullRequest
2 голосов
/ 24 октября 2011

Я хочу, чтобы приложение, написанное на языке программирования D, обновляло отображение в браузере. Браузер также должен отправлять входные данные обратно в приложение.

Я все еще новичок в программировании, и меня смущает, как сокеты / веб-сокеты / серверы сочетаются друг с другом. Кто-нибудь может предложить подход?

1 Ответ

2 голосов
/ 29 октября 2011

Большое спасибо gmfawcett за ссылку на его базовый пример D-сервера, который я соединил с простой реализацией веб-сокета спецификации версии 8, которую я нашел в другом месте (я думаю, что в настоящее время работает только в Chrome 14/15),Это в значительной степени нарезанная паста, но, кажется, работает достаточно хорошо, и я ожидаю, что этого будет достаточно для удовлетворения моих потребностей.

Если у кого-то есть желание бросить быстрый взгляд на мой код для любого откровенногоНет, пожалуйста, не стесняйтесь, и спасибо!

Impl веб-сокета: http://blog.vunie.com/implementing-websocket-draft-10

Спецификация веб-сокета v8 (протокол-17): http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17

module wsserver;

import std.algorithm;
import std.base64;
import std.conv;
import std.stdio;
import std.socket;
import std.string;

//std.crypto: https://github.com/pszturmaj/phobos/tree/master/std/crypto
import crypto.hash.base;
import crypto.hash.sha;

struct WsServer
{
    private
    {
        Socket s;
        Socket conn;
        string subProtocol;
    }

    this(string host, ushort port = 8080, string subProtocol = "null")
    {
        this.subProtocol = subProtocol;

        s = new TcpSocket(AddressFamily.INET);
        s.bind(new InternetAddress(host, port));
        s.listen(8);

        conn = s.accept();

        writeln("point/refresh your browser to \"http://", host, "\" to intiate the websocket handshake");

        try
        {
           initHandshake(conn);
        }
        catch (Throwable e)
        {
            stderr.writeln("thrown: ", e);
        }
    }

    ~this()
    {
        conn.shutdown(SocketShutdown.BOTH);
        conn.close();

        s.shutdown(SocketShutdown.BOTH);
        s.close();
    }

    string data()
    {
        ubyte[8192] msgBuf;
        auto msgBufLen = conn.receive(msgBuf);

        auto firstByte = msgBuf[0];
        auto secondByte = msgBuf[1];

        // not sure these two checks are woking correctly!!!
        enforce((firstByte & 0x81), "Fragments not supported"); // enforce FIN bit is present
        enforce((secondByte & 0x80), "Masking bit not present"); // enforce masking bit is present

        auto msgLen = secondByte & 0x7f;

        ubyte[] mask, msg;

        if(msgLen < 126)
        {
            mask = msgBuf[2..6];
            msg = msgBuf[6..msgBufLen];
        }
        else if (msgLen == 126)
        {
            mask = msgBuf[4..8];
            msg = msgBuf[8..msgBufLen];
        }

        foreach (i, ref e; msg)
            e = msg[i] ^ mask[i%4];

        debug writeln("Client: " ~ cast(string) msg);

        return cast(string) msg;
    }

    void data(string msg)
    {
        ubyte[] newFrame;

        if (msg.length > 125)
            newFrame = new ubyte[4];
        else
            newFrame = new ubyte[2];

        newFrame[0] = 0x81;

        if (msg.length > 125)
        {
            newFrame[1] = 126;

            newFrame[2] = cast(ubyte) msg.length >> 8;
            newFrame[3] = msg.length & 0xFF;
        }
        else
            newFrame[1] = cast(ubyte) msg.length;

        conn.send(newFrame ~= msg);

        debug writeln("Server: " ~ msg);
    }

    private void initHandshake(Socket conn)
    {
        ubyte[8192] buf;  // big enough for some purposes...
        size_t position, headerEnd, len, newpos;

        // Receive the whole header before parsing it.
        while (true)
        {
            len = conn.receive(buf[position..$]);

            debug writeln(cast(string)buf);

            if (len == 0)               // empty request
              return;

            newpos = position + len;
            headerEnd = countUntil(buf[position..newpos], "\r\n\r\n");
            position = newpos;

            if (headerEnd >= 0)
                break;
        }

        // Now parse the header.
        auto lines = splitter(buf[0..headerEnd], "\r\n");
        string request_line = cast(string) lines.front;
        lines.popFront;

        // a very simple Header structure.
        struct Pair
        {
            string key, value;

            this(ubyte[] line)
            {
              auto tmp = countUntil(line, ": ");
              key = cast(string) line[0..tmp]; // maybe down-case these?
              value = cast(string) line[tmp+2..$];
            }
        }

        Pair[] headers;
        foreach(line; lines)
            headers ~= Pair(line);

        auto tmp            = splitter(request_line, ' ');
        string method       = tmp.front; tmp.popFront;
        string url          = tmp.front; tmp.popFront;
        string protocol     = tmp.front; tmp.popFront;

        enum GUID_v8 = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // version 8 spec... might change
        auto sha1 = new SHA1;
        sha1.put(strip(headers[5].value) ~ GUID_v8);
        auto respKey = to!string(Base64.encode(sha1.finish()));

        // Prepare a response, and send it
        string resp = join(["HTTP/1.1 101 Switching Protocols",
                            "Upgrade: websocket",
                            "Connection: Upgrade",
                            "Sec-WebSocket-Accept: " ~ respKey,
                            "Sec-WebSocket-Protocol: " ~ subProtocol,
                            ""],
                            "\r\n");

        conn.send(cast(ubyte[]) (resp ~ "\r\n"));

        debug writeln(resp);
    }
}
...