Cơ chế hoạt động của websocket trên heroku?

mấy bạn cho mình hỏi , theo lý thuyết thì giao thức http là không lưu giữ trạng thái , còn giao thức socket là có giữ trạng thái , mình có làm 1 cái example nhỏ bằng java sử dụng websocket để chạy trên heroku thì thấy nó hoạt động liên tục không thấy mất trạng thái cả server và client có thể trao đổi dữ liệu liên tục cho nhau , mà theo mình biết thì heroku nó chỉ cho open 2 port 80,443 và nó chỉ hỗ trợ http ,https thì làm sao mà tạo đc websocket nhỉ ?

mình thử tự viết 1 cái web proxy để mô phỏng lại cái websocket kia (cái websocket kia mình dùng thư viện của spring và cấu hình theo example) thì bị mắc 1 vấn đề đó là request đi vào proxy thì bắt đc , nhưng để gửi ngược dữ liệu từ proxy về lại client thì không đc?

                        Heroku              Proxy Server
+---------+           +---------+           +---------+
|         |           |         |           |         |
|         | --------> |         | --------> |         |
|  Client |           |  Router |           |         |
|         | <-------- |         | <-------- |         |
|         |           |         |           |         |
+---------+           +---------+           +---------+

mình tự hiểu cái heroku như hình trên có đúng ko , client sẽ kết nối đến 1 cái router của heroku , sau đó cái router này sẽ binding dữ liệu của mình đến proxy server đặt bên trong heroku , mình đã làm cho client gửi đc dữ liệu vào cái proxy bên trong heroku nhưng để đẩy ngược dữ liệu từ proxy đó qua router về lại client thì không đc

   Client                   Proxy Server 
+----------+                +----------+
|          |                |          |
|          | -------------- |          |
|          |   --------->   |          |
|          |    <------     |          |
|          | -------------- |          |
|          |                |          |
+----------+                +----------+

còn hình này là cái proxy mình test dưới local vì dưới local nên mình có thể cho nó listen ở bất kì port nào mình muốn và kết nối giữa client - proxy server luôn hoạt động liên tục

giờ mình muốn làm sao để cái proxy hoạt động trên heroku giống như hoạt động ở local

example websocket : https://o7planning.org/vi/10719/tao-ung-dung-chat-don-gian-voi-spring-boot-va-websocket

1 Like

Vấn đề đầu tiên, websocket trên port 80,443: với network thì cứ lấy mô hình 7 lớp osi nói chi dễ nha bạn. Đầu tiên, cần phân biệt socket (ở tầng 3-network) và websocket (tầng 7- application). Tiếp theo là, cả http (tầng 7) và websocket đều dựa trên các tầng dưới nó, bao gồm cả tầng 4 (transport, tầng liên quan tới port) để giao tiếp. Việc quy định 80 và 443 cho http và https chỉ là quy ước thôi, bạn hoàn toàn có thể đổi. Tuy nhiên, với websocket, bạn đã check protocol spec nó làm gì chưa? Theo mình nhớ, những gói bắt tay ban đầu của nó là dùng http/https để trao đổi port sẽ dùng để connect liên tục. Nên ban đầu websocket dùng port 80 và 443 cũng chẳng có gì lạ cả.

Thứ hai, heroku và webproxy. Cái này mình không biết heroku nó hoặt động ra sao. Nhưng theo hình vẽ của bạn, có vẻ là router, vậy bạn cần phải biết các kiến thức liên quan network infrastructure như Lan, wan, switch/router và cả NAT để hiểu.

6 Likes

Hi there,

Cậu có rất nhiều câu hỏi, nên tớ sẽ viết câu trả lời theo từng câu hỏi một. Nó sẽ hơi dài chút, tớ nghĩ là cậu đã expect điều đó :wink:


Về câu hỏi này:

Như Stanley đã đề cập ở trên, những gói bắt tay ban đầu của http/https và websocket là như nhau, nên về cơ bản, cậu có thể upgrade HTTP thành web socket. Đó là lý do cậu hoàn toàn có thể sử dụng port 80/443 để tạo websocket.
Dưới đây là trích dẫn từ RFC 6455:

The WebSocket Protocol is an independent TCP-based protocol. Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request.
By default, the WebSocket Protocol uses port 80 for regular WebSocket connections and port 443 for WebSocket connections tunneled over Transport Layer Security (TLS) [RFC2818].

Trong link cậu refer tới thực chất đã làm như vậy.

Cơ sở hạ tầng hiện có gây ra các hạn chế cho việc triển khai WebSocket , thông thường HTTP đã sử dụng cổng 80 & 443 , vì vậy WebSocket phải sử dụng các cổng khác, trong khi đó hầu hết các Firewall (Tường lửa) chặn các cổng khác 80 & 443 , sử dụng các Proxy (Ủy quyền) cũng có nhiều vấn đề xẩy ra. Vì vậy để có thể dễ dàng triển khai, WebSocket sử dụng HTTP Handshake (Cái bắt tay với HTTP ) để nâng cấp. Điều đó có nghĩa là trong lần đầu tiên client gửi một yêu cầu dựa trên HTTP tới server , nói với server rằng đó không phải là HTTP , hãy nâng cấp lên WebSocket , và như vậy chúng hình thành một kết nối.


Về vấn đề này:

Tớ chưa làm việc với Heroku bao giờ, nhưng như tớ đọc từ Heroku’s websockets, họ có đề cập

WebSocket support is available everywhere, except SSL endpoint traffic for endpoints created before July 7th, 2014.

Điều đó có nghĩa: chỉ cần cậu lập trình đúng cách, hệ thống của cậu nên hoạt động.
Cậu nên thử đọc các ví dụ ở trong article Heroku websocket - phần “Futher reading” xem. Có thể nó sẽ giúp ích chút cho cậu.


Note thêm về sơ đồ dưới đây:

Tớ chỉ muốn correct lại chút.
Refer tới document của Heroku về routing

Inbound requests are received by a load balancer that offers SSL termination. From here they are passed directly to a set of routers.
The routers are responsible for determining the location of your application’s web dynos and forwarding the HTTP request to one of these dynos.
A request’s unobfuscated path from the end-client through the Heroku infrastructure to your application allows for full support of HTTP 1.1 features such as chunked responses, long polling, websockets, and using an async webserver to handle multiple responses from a single web process. HTTP 1.0 compatibility is also maintained.

Router chỉ có nhiệm vụ forward dữ liệu tới ai thôi cậu :smiley: Hero của chúng ta là ở đây là load balancer, vậy nên cậu nên đổi “Router” thành “Load balancer”.
Ngoài ra, không có việc binding dữ liệu gì đâu cậu :smiley: Cậu chỉ đơn giản mở ra 1 kết nối trao đổi 2 chiều, và Load balancer là người trung gian.


Về câu hỏi cuối:

Trước khi bàn tiếp, cậu có thể cho bọn tớ biết sao cậu lại đưa ra kết luận trên Heroku, request tới proxy (?) thì bắt được, nhưng ngược lại thì không được không? Tớ hi vọng cậu có thể đưa cho bọn tớ 1 chút log/thông báo lỗi/bất cứ thứ gì cậu có thể dùng để chứng minh nhận định trên của cậu.

Hi vọng mấy câu trả lời trên giúp cậu hiểu rõ hơn cậu cần phải làm gì.

8 Likes

mình viết 1 cái socket đơn giản như thế này để test , thử dưới local chạy ok hết , nhưng lên heroku chỉ gửi được gửi được dữ liệu đi vào thôi , còn lúc mình flush để đẩy ngược dữ liệu lại thì ko đc

class ThreadSocket extends Thread {

    private Socket insocket;

    ThreadSocket(Socket insocket) {
        this.insocket = insocket;
        this.start();
    }

    @Override
    public void run() {
        try {
            InputStream is = insocket.getInputStream();

            PrintWriter out = new PrintWriter(insocket.getOutputStream());
            BufferedReader in = new BufferedReader(new InputStreamReader(is));

            String line;
            line = in.readLine();
            String request_method = line;
            System.out.println("HTTP-HEADER: " + line);
            line = "";
            // looks for post data
            int postDataI = -1;
            while ((line = in.readLine()) != null && (line.length() != 0)) {
                System.out.println("HTTP-HEADER: " + line);
                if (line.indexOf("Content-Length:") > -1) {
                    postDataI = new Integer(
                            line.substring(
                                    line.indexOf("Content-Length:") + 16,
                                    line.length())).intValue();
                }
            }

            String postData = "";
            // read the post data
            if (postDataI > 0) {
                char[] charArray = new char[postDataI];
                in.read(charArray, 0, postDataI);
                postData = new String(charArray);
            }

            out.println("<H1>Welcome to the Mini Server</H1>");
            out.println("<H2>Request Method->" + "" + "</H2>");
            out.println("<H2>Post->" + "" + "</H2>");
            out.println("<form name=\"input\" action=\"form_submited\" method=\"post\">");
            out.println("Username: <input type=\"text\" name=\"user\"><input type=\"submit\" value=\"Submit\"></form>");
            out.println("\n");
            out.flush();

            System.out.println("ket thuc 1 luot");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

vì sao mình biết nó đẩy dữ liệu vào đc >>> đây là log trên heroku chứng tỏ đã chạy được vào socket nhưng ko đẩy ngược dữ liệu lại

mình có thử dùng close() socket nhưng vẫn ko đc , cái này chắc nằm ở cơ chế xử lý của heroku rồi

ok lý thuyết này mình nắm được rồi , nhưng trên heroku nó có cho mình đào sâu vào phần cấu hình kết nối của nó đâu , hình như heroku chỉ cho phép mình upgrade http lên thành websocket thôi , chứ mình ko thể dùng socketserver ở tầng tcp để xử lý đc …

Tớ đồ rằng socket của cậu sử dụng cổng khác cổng 80/443 để gửi dữ liệu về. Cậu biết Heroku chỉ mở cổng 80/443 rồi đó :smile:
Cậu thử tạo socket mới gửi dữ liệu về cổng 80 của client xem.

2 Likes

là sao bạn ? , trước gìo mình toàn viết kết nối socket theo kiểu
server tạo 1 socketserver và listen trên 1 port nào đó , client sẽ tạo 1 socket connect vào port mà server listen , sau đó client và server sẽ trao đổi dữ liệu qua socket , chứ tạo socket mới gửi dữ liệu về cổng 80 của client là sao ? không lẻ ở client lại viết 1 serversocket listen ở cổng 80 nữa à nghe nó ngược đời vậy ?

Không cậu. Ý tớ là thế này: Cậu cần tạo socket mới kết nối với cổng 80 như thế này:

Socket outgoing = new Socket("localhost", 80);

Sau đó, cậu copy dữ liệu từ incoming socket’s inputStream sang outgoing socket’s outputStream.

Như cậu biết, Heroku họ block các cổng khác ngoài cổng 80/443. 1 socket của cậu sẽ mở 2 channel cho input và output stream, kết nối với 2 cổng khác nhau. Đó là lý do cậu không gửi lại được kết quả khi deploy trên Heroku (cậu gửi kết quả về incoming socket’s outputStream - cổng của nó khả năng cao không phải là 80/443 rồi :smiley: )
Ở trên local environment, cậu không có bất cứ restriction nào về ports, nên cậu sẽ thoải mái forward dữ liệu qua cổng của incoming socket’s outputStream.

Hi vọng nó giúp cậu!

P/s: Proxy là server đối với người gửi và là client đối với người nhận, thế nên tớ có bảo cậu “gửi dữ liệu về cổng 80 của client” là gửi dữ liệu qua cổng 80 của proxy. Tớ nghĩ đó là cách diễn đạt khá tệ :man_facepalming: Sorry 'bout that!

4 Likes

ok thanks mình hiểu rồi

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?