simplistic web based chat solution

163 views
Skip to first unread message

Tong Sun

unread,
Jul 22, 2023, 11:26:16 PM7/22/23
to golang-nuts
I know web based chatting service can be as complicated as possible, but I want to build a web based chat server that is as simple as possible. 

And the chapter 8 of the book "The Go Programming Language" at
https://github.com/adonovan/gopl.io/tree/master/ch8/chat
seems to be a perfect starting point to learn the asynchronous web based chat service building.

I've built a whole (simplest) web service that support user login and logout, at:
https://github.com/suntong/lang/blob/master/lang/Go/src/net/HttpD/demo-redirect.go
and was planning that the next logic steps would be to

- incorporate the chat server as another goroutine besides my web server, and
- add the chatting to my web service by spinning up a new chatting client goroutine when a new user logs in.

However, it is incorporating the chatting client into my web service that made my head spinning none-stop. IE, for the following few lines of code from

done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done <- struct{}{} // signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done // wait for background goroutine to finish

I'm having a hard time putting it into my code. Specifically, how to handle incoming new chats message and push to all web base clients. What's the simplest solution for it? 
Note, to avoid user hitting refresh and lost all past conversation, I was planning to store all of them, including the connect variable returned from `net.Dial` into cookies. I know that there is a 4K size limit to total cookies size, but that's OK with me for such a simplest chatting service, which would add as minimum to the solution from ch8/chat as possible.

Anybody can help please?


Eli Lindsey

unread,
Jul 23, 2023, 12:47:00 AM7/23/23
to Tong Sun, golang-nuts
I’m taking a stab at answering, but I didn’t quite follow your intent and may have misunderstood your question. Please let me know if that’s the case. :)

I'm having a hard time putting it into my code. Specifically, how to handle incoming new chats message and push to all web base clients. What's the simplest solution for it? 

It’ll probably be easiest if you define what the client is/what protocol the client speaks and work back from there. Skimming the code, it looks like your demo is speaking http to web browsers and the chat example you’re interested in merging is a custom protocol (a very simple one, “echo what’s received”) on bare tcp. You have many options: if your clients are something like netcat, you can define the protocol to initially be http with an html response that then switches to tcp echo; if your clients are web browsers, some strategies to consider (from least to most complicated) are periodic polling, long polling, and a protocol that supports push out of the box like WebTransport.

If you want the simplest version that works in a web browser, I would write it so that the user has to manually refresh (or click a button) to see new messages. Once that works you could then look into how to make the client pull new messages without a manual refresh, but that’s likely to be client-side js and not server-side go, so may or may not be interesting depending on what you’re wanting to learn from this exercise.

Note, to avoid user hitting refresh and lost all past conversation, I was planning to store all of them, including the connect variable returned from `net.Dial` into cookies. I know that there is a 4K size limit to total cookies size, but that's OK with me for such a simplest chatting service, which would add as minimum to the solution from ch8/chat as possible.

I’m not entirely sure what you’re describing here, but carrying the net.Conn in a cookie sounds odd. Could you say more?

-eli

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/e3327bc2-4f2f-4038-b38d-0c9794ec6235n%40googlegroups.com.

Tong Sun

unread,
Jul 23, 2023, 10:34:25 AM7/23/23
to golang-nuts
Sending to the group instead.

On Sun, Jul 23, 2023 at 12:46 AM Eli Lindsey <e...@siliconsprawl.com> wrote:
>
> I’m taking a stab at answering, but I didn’t quite follow your intent and may have misunderstood your question. Please let me know if that’s the case. :)
>
> I'm having a hard time putting it into my code. Specifically, how to handle incoming new chats message and push to all web base clients. What's the simplest solution for it?
>
>
> It’ll probably be easiest if you define what the client is/what protocol the client speaks and work back from there. Skimming the code, it looks like your demo is speaking http to web browsers and the chat example you’re interested in merging is a custom protocol (a very simple one, “echo what’s received”) on bare tcp. You have many options: if your clients are something like netcat, you can define the protocol to initially be http with an html response that then switches to tcp echo; if your clients are web browsers, some strategies to consider (from least to most complicated) are periodic polling, long polling, and a protocol that supports push out of the box like WebTransport.
>
> If you want the simplest version that works in a web browser, I would write it so that the user has to manually refresh (or click a button) to see new messages. Once that works you could then look into how to make the client pull new messages without a manual refresh, but that’s likely to be client-side js and not server-side go, so may or may not be interesting depending on what you’re wanting to learn from this exercise.

Ah, yeah, manual refreshing is simplest to do. Good idea!

> Note, to avoid user hitting refresh and lost all past conversation, I was planning to store all of them, including the connect variable returned from `net.Dial` into cookies. I know that there is a 4K size limit to total cookies size, but that's OK with me for such a simplest chatting service, which would add as minimum to the solution from ch8/chat as possible.
>
>
> I’m not entirely sure what you’re describing here, but carrying the net.Conn in a cookie sounds odd. Could you say more?

Starting from
https://github.com/adonovan/gopl.io/blob/master/ch8/netcat3/netcat.go

Indeed, it is a custom protocol (a very simple one, “echo what’s
received”) on bare tcp. How it works on cli is that each client starts
their own cli client, having their own net.Conn for the whole
communication, and will get new messages from there.

Now, when shifting that onto a web service, where only one instance
would serve all different clients, the only thing *I can think of* is
to

- spinning up a new chatting client goroutine (mainly the quoted code
in OP) when a new user logs in, then there will be a new net.Conn for
the client
- since I'm only doing server side programming and one server needs to
serve different clients, the only way I can think of is to store their
own net.Conn in their own cookies to track individual status
- to avoid users hitting refresh and losing all past conversations, I
think I have to store the conversation (since login) in cookies as
well.

Hmm... on second thought, since I'm planning to let users do their
manual refreshing, maybe store the whole conversation on the server
and when refresh, it prints from the 1st line again might be another
option.

What would you think is the easiest thing to do here? Thanks!

Eli Lindsey

unread,
Jul 23, 2023, 12:24:59 PM7/23/23
to Tong Sun, golang-nuts

> - spinning up a new chatting client goroutine (mainly the quoted code
> in OP) when a new user logs in, then there will be a new net.Conn for
> the client
> - since I'm only doing server side programming and one server needs to
> serve different clients, the only way I can think of is to store their
> own net.Conn in their own cookies to track individual status
> - to avoid users hitting refresh and losing all past conversations, I
> think I have to store the conversation (since login) in cookies as
> well.


Cases where the client echos back its connection are usually suspect since that's information the server already knows by virtue of receiving the request. It sounds like this is trying to use a transport layer construct as an application layer identifier, and it’ll likely be easier (and a better learning exercise) to use a proper application layer identifier instead. That’ll also sidestep a pile of edge cases. An interesting thing about browsers is that, in contrast to CLI client, mobile clients, etc., you have very little control over the connections themselves. You control the requests, and the browser is responsible for servicing it - it may close connections, open new connections, use multiple connections, and so on.

It looks like you have a simulated login flow. One way this could work is by setting a session cookie after login - that gets you an application layer identifier that may map to one or multiple changing transport identifiers (net.Conns). You could then buffer unsent messages per session ID, send them regardless of what transport they come over, and delete buffers for old sessions after all their transports are closed and a timeout period has passed (which prevents the unbounded growth problem you’d mentioned). That’s close to how an actual system may work, with the big exception that in production the session ID must be encrypted server-side to prevent the client from tampering/spoofing (though that’s unnecessary for a learning demo).

> Hmm... on second thought, since I'm planning to let users do their
> manual refreshing, maybe store the whole conversation on the server
> and when refresh, it prints from the 1st line again might be another
> option.
>
> What would you think is the easiest thing to do here? Thanks!

Manually refreshing and echoing back the conversation from the beginning sounds like a great first version to me! You may encounter some odd cases where the full chat history is echoed back or a client unexpectedly disconnects due to the whims of the browser’s connection pooling logic, but no big deal.

If you finish that and are interested in learning more or making a second version, I’d then consider either going down the session ID path, or looking into WebSockets (which is what I meant to say earlier instead of WebTransport, oops - that’s a slightly different thing). WS would require a little client work, but would still be primarily server-side changes; it’s essentially a browser-supported API that gets you a TCP connection, so you could fully mimic the netcat behavior from the example.

-eli
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAMmz1Ofwc1DY7U6Xbzq04KmD4%3DJaP632ZKP8N_NW7jwEnXTraQ%40mail.gmail.com.

Reply all
Reply to author
Forward
0 new messages