• Stars
    star
    209
  • Rank 188,325 (Top 4 %)
  • Language
    Scala
  • Created almost 8 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Distributed web chat application base websocket built on akka.

CookIM - is a distributed websocket chat applications based on akka

Github All Releases

  • Support private message and group message
  • Support chat servers cluster communication
  • Now we support send text message, file message and voice message. Thanks for ft115637850 's PR for voice message.

CookIM logo



Category

  1. Demo
    1. Demo on PC
    2. Demo on Mobile
    3. Demo link
  2. Start multiple nodes CookIM in docker compose
    1. Start docker compose
    2. Add nodes in docker compose
    3. Debug in docker container
    4. Stop docker compose
  3. How to run
    1. Prerequisites
    2. Clone source code
    3. Configuration and assembly
    4. Start CookIM server
    5. Open browser and access web port 8080
    6. Start another CookIM server
    7. Open browser and access web port 8081
  4. Architecture
    1. Architecture picture
    2. akka stream websocket graph
    3. MongoDB tables specification
    4. Websocket message type
  5. ChangeLog
    1. 0.1.0-SNAPSHOT
    2. 0.2.0-SNAPSHOT
    3. 0.2.4-SNAPSHOT

Category

###Demo

Demo on PC

screen snapshot

Demo on Mobile

screen snapshot

Demo link

https://im.cookeem.com


Start multiple nodes CookIM in docker compose

Start docker compose

Change into CookIM directory, run command below, start multiple nodes CookIM servers in docker compose mode. This way will start 3 container: mongo, cookim1 and cookim2

$ git clone https://github.com/cookeem/CookIM.git

$ cd CookIM

$ sudo docker-compose up -d
Creating mongo
Creating cookim1
Creating cookim2

After run docker compose, use different browser to access the URLs below to connect to cookim1 and cookim2

http://localhost:8080 http://localhost:8081


Category

Add nodes in docker compose

You can add config in docker-compose.yml (in CookIM directory) to add CookIM server nodes, this example show how to add cookim3 in docker compose:

      cookim3:
        image: cookeem/cookim
        container_name: cookim3
        hostname: cookim3
        environment:
          HOST_NAME: cookim3
          WEB_PORT: 8080
          AKKA_PORT: 2551
          SEED_NODES: cookim1:2551
          MONGO_URI: mongodb://mongo:27017/local
        ports:
        - "8082:8080"
        depends_on:
        - mongo
        - cookim1

Category

Debug in docker container

View container cookim1 logs output

$ sudo docker logs -f cookim1

Exec into container cookim1 to debug

$ sudo docker exec -ti cookim1 bash

Category

Stop docker compose

$ sudo docker-compose stop
$ sudo docker-compose rm

Category

How to run

Prerequisites

  • JDK 8+
  • Scala 2.11+
  • SBT 0.13.15
  • MongoDB 2.6 - 3.4

Category

Clone source code

git clone https://github.com/cookeem/CookIM.git

cd CookIM

Category

Configuration and assembly

The configuration file locate at conf/application.conf, please make sure your mongodb uri configuration.

mongodb {
  dbname = "cookim"
  uri = "mongodb://mongo:27017/local"
}

Assembly CookIM project to a fatjar, target jar locate at target/scala-2.11/CookIM-assembly-0.2.0-SNAPSHOT.jar

sbt clean assembly

Category

Start CookIM server

CookIM use MongoDB to store chat messages and users data, make sure you startup MongoDB before you startup CookIM.

There are two ways to start CookIM server: sbt and java

a. sbt debug way:

$ cd #CookIM directory#

$ sbt "run-main com.cookeem.chat.CookIM -h localhost -w 8080 -a 2551 -s localhost:2551"

b. pack and compile fat jar:

$ sbt assembly

c. java production way:

$ java -classpath "target/scala-2.11/CookIM-assembly-0.2.4-SNAPSHOT.jar" com.cookeem.chat.CookIM -h localhost -w 8080 -a 2551 -s localhost:2551

Command above has start a web server listen port 8080 and akka system listen port 2551

Parameters:

-a -h [-m ] [-n] -s -w -a,--akka-port akka cluster node port -h,--host-name current web service external host name -m,--mongo-uri mongodb connection uri, example: mongodb://localhost:27017/local -n,--nat is nat network or in docker -s,--seed-nodes akka cluster seed nodes, seperate with comma, example: localhost:2551,localhost:2552 -w,--web-port web service port


Category

Open browser and access web port 8080

http://localhost:8080


Category

Start another CookIM server

Open another terminal, start another CookIM server to test message communication between servers:

a. sbt debug way:

$ sbt "run-main com.cookeem.chat.CookIM -h localhost -w 8081 -a 2552 -s localhost:2551"

b. java production way:

$ java -classpath "target/scala-2.11/CookIM-assembly-0.2.0-SNAPSHOT.jar" com.cookeem.chat.CookIM -h localhost -w 8081 -a 2552 -s localhost:2551

Command above has start a web server listen port 8081 and akka system listen port 2552


Category

Open browser and access web port 8081

http://localhost:8081


Category

Architecture

Architecture picture

Architecture picture

**CookIM server make from 3 parts: **

  1. akka http: provide web service, browser connect distributed chat servers by websocket
  1. akka stream: akka http receive websocket message (websocket message include TextMessage and BinaryMessage), then send message to chatService by akka stream way, websocket message include JWT(Javascript web token), if JWT verify failure, chatService stream will return reject message; if JWT verify success, chatService stream will send message to ChatSessionActor
  1. akka cluster:akka stream send websocket message to akka cluster ChatSessionActor, ChatSessionActor use DistributedPubSub to subscribe and publish message in akka cluster. When user online session, it will subscribe the session; when user send message in session, it will publish message in akka cluster, the actors who subscribe the session will receive the publish message

Category

akka stream websocket graph

CookIM stream

  • When akka http receive messsage from websocket, it will send message to chatService flow, here we use akka stream graph:
  1. Websocket message body include JWT, flowFromWS use to receive websocket message and decode JWT;
  1. When JWT verify failure, it will broadcast to filterFailure to filter to fail message; When JWT verify success, it will broadcast to filterSuccess to filter to success message;
  1. When akka stream created, builder.materializedValue will send message to connectedWs, connectedWs convert message receive to UserOnline message, then send to chatSinkActor finally send to ChatSessionActor;
  1. chatActorSink send message to chatSessionActor, when akka stream closed if will send UserOffline message to down stream;
  1. chatSource receive message back from ChatSessionActor, then send message back to flowAcceptBack;
  1. flowAcceptBack will let the websocket connection keepAlive;
  1. flowReject and flowAcceptBack messages finally send to flowBackWs, flowBackWs convert messages to websocket format then send back to users;

Category

MongoDB tables specification

  • users: users table
*login (login email)
nickname (nickname)
password (password SHA1)
gender (gender: unknow:0, boy:1, girl:2)
avatar (avatar abs path, example: /upload/avatar/201610/26/xxxx.JPG)
lastLogin (last login timestamp)
loginCount (login counts)
sessionsStatus (user joined sessions status)
    [{sessionid: session id, newCount: unread message count in this session}]
friends (user's friends list: [friends uid])
dateline (register timestamp)
  • sessions: sessions table
*createuid (creator uid)
*ouid (receiver uid, when session type is private available)
sessionIcon (session icon, when session type is group available)
sessionType (session type: 0:private, 1:group)
publicType (public type: 0:not public, 1:public)
sessionName (session name)
dateline (created timestamp)
usersStatus (users who joined this session status)
    [{uid: uid, online: (true, false)}]
lastMsgid (last message id)
lastUpdate (last update timestamp)
  • messages: messages tables
*uid (send user uid)
*sessionid (relative session id)
msgType (message type)
content (message content)
fileInfo (file information)
    {
        filePath
        fileName
        fileType
        fileSize
        fileThumb
    }
*dateline (created timestamp)
  • onlines: online users table
*uid (online user id)
dateline (last update timestamp)
  • notifications: receive notifications table
noticeType (notification type: "joinFriend", "removeFriend", "inviteSession")
senduid (send user id)
*recvuid (receive user id)
sessionid (relative session id)
isRead (notification is read: 0:not read, 1:already read)
dateline (created timestamp)

Category

Websocket message type

There are two websocket channel: ws-push and ws-chat

ws-push send sessions new message to users, when user not online the session, they still can receive which sessions has new messages

/ws-push channel

up message, use to subscribe push message:
{ userToken: "xxx" }

down message:
acceptMsg:     { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "accept", content: "xxx", dateline: "xxx" }
rejectMsg:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "reject", content: "xxx", dateline: "xxx" }
keepAlive:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "keepalive", content: "", dateline: "xxx" }
textMsg:       { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "text", content: "xxx", dateline: "xxx" }
fileMsg:       { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "file", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx", dateline: "xxx" }
onlineMsg:     { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "online", content: "xxx", dateline: "xxx" }
offlineMsg:    { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "offline", content: "xxx", dateline: "xxx" }
joinSessionMsg: { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "join", content: "xxx", dateline: "xxx" }
leaveSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "leave", content: "xxx", dateline: "xxx" }
noticeMsg:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "xxx", sessionIcon: "xxx", msgType: "system", content: "xxx", dateline: "xxx" }

message push to browser:
pushMsg:       { 
                    uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "xxx", 
                    content: "xxx", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx",
                    dateline: "xxx" 
               }

Category

ws-chat is session chat channel, user send and receive session messages in this channel

/ws-chat channel
up message: 
onlineMsg:     { userToken: "xxx", sessionToken: "xxx", msgType:"online", content:"" }
textMsg:       { userToken: "xxx", sessionToken: "xxx", msgType:"text", content:"xxx" }
fileMsg:       { userToken: "xxx", sessionToken: "xxx", msgType:"file", fileName:"xxx", fileSize: 999, fileType: "xxx" }<#BinaryInfo#>binary_file_array_buffer

down message:   
rejectMsg:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "reject", content: "xxx", dateline: "xxx" }
keepAlive:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "", sessionIcon: "", msgType: "keepalive", content: "", dateline: "xxx" }
textMsg:       { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "text", content: "xxx", dateline: "xxx" }
fileMsg:       { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "file", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx", dateline: "xxx" }
onlineMsg:     { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "online", content: "xxx", dateline: "xxx" }
offlineMsg:    { uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "offline", content: "xxx", dateline: "xxx" }
joinSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "join", content: "xxx", dateline: "xxx" }
leaveSessionMsg:{ uid: "xxx", nickname: "xxx", avatar: "xxx", sessionid: "xxx", sessionName: "xxx", sessionIcon: "xxx", msgType: "leave", content: "xxx", dateline: "xxx" }
noticeMsg:     { uid: "", nickname: "", avatar: "", sessionid: "", sessionName: "xxx", sessionIcon: "xxx", msgType: "system", content: "xxx", dateline: "xxx" }

message push to browser:
chatMsg:       { 
                    uid: "xxx", nickname: "xxx", avatar: "xxx", msgType: "xxx", 
                    content: "xxx", fileName: "xxx", fileType: "xxx", fileid: "xxx", thumbid: "xxx",
                    dateline: "xxx" 
               }

Category

ChangeLog

0.1.0-SNAPSHOT


Category

0.2.0-SNAPSHOT

  • CookIM now support MongoDB 3.4.4
  • Upgrade akka version to 2.5.2
  • Update docker-compose startup CookIM cluster readme

Category

0.2.4-SNAPSHOT

  • Now support send voice message, required WebRTC support browser, now Chrome Firefox and the new Safari11 available.
  • Configurate mongodb connection params by command line.
  • Update docker startup mode.

Category