I’m trying to develop a client-server application using RESTful web services.
I want to have user input as a user name. However, when I tried bufio.NewReader(os.Stdin), the rest of my program does not run and it does not use the name as clientName.
Can someone help me to solve it, please?
I signed it with comments.
Server:
package main
import (
"github.com/gorilla/mux"
"fmt"
//"./myUtils"
"net/http"
"bufio"
"os"
)
var helpInfo = [...]string {"help and command info:",
"/help: use this command to get some help",
"/createroom roomName : creates a room with the name roomName",
"/listrooms: lists all rooms available for joining",
"/join roomName: adds you to a chatroom",
"/leave removes you from current room",
}
var ClientArray []*Client
var RoomArray []*Room
var sender *Client
//STRUCTURES
/*****************Rooms*****************/
type Room struct{
name string
clientList []*Client
chatLog []*ChatMessage
creator *Client
}
//Structure holding messages sent to a chat, stores meta information on the client who sent it
type ChatMessage struct {
client *Client
message string
}
//Clients have names, and a reader and writer as well as a link to their connection
//Client names are guaranteed by the generateName function to be unique for the duration of program execution (NOT persisted)
type Client struct {
currentRoom *Room
outputChannel chan string
name string
}
//Main function for starting the server, will open the server on the SERVER_IP and the SERVER_PORT
func main() {
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", Connect).
Methods("GET")
router.HandleFunc("/help", help).
Methods("GET")
router.HandleFunc("/{USER}/messages", MessageClient).
Methods("GET")
router.HandleFunc("/rooms", listrooms).
Methods("GET")
router.HandleFunc("/rooms/{ROOMNAME}", createroom).
Methods("POST")
router.HandleFunc("/rooms/{ROOMNAME}/{USER}", join).
Methods("POST")
router.HandleFunc("/{USER}/leaveroom", leaveroom).
Methods("DELETE")
router.HandleFunc("/{USER}/messageRoom", SendMessageToCurrentRoom).
Methods("POST")
http.ListenAndServe(":8080", router)
fmt.Println("Launching server...")
//Start the server on the constant IP and port
}
//checks the room name against the current list of rooms to make sure it is unique, returns true if it is, false if not
//returns true if a user is already in the room, false otherwise
func (room Room) isClientInRoom(client *Client) bool {
for _, roomClient := range room.clientList {
if client.name == roomClient.name {
return true
}
}
return false
}
//checks to see if a room with the given name exists in the RoomArray, if it does return it, if not return nil
func getRoomByName(roomName string) *Room{
for _, room := range RoomArray{
if room.name == roomName{
return room
}
}
return nil
}
//this function will check the room
func getClientByName(clientName string) *Client{
for _, cli := range ClientArray{
if cli.name == clientName{
return cli
}
}
return nil
}
/*
creates a new client with a name and returns the name
*/
func addClient() string{
createOutputChannel := make(chan string)
////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////here///////////////////////////////////////////////////////////////////
reader := bufio.NewReader(os.Stdin)
clientName, _ := reader.ReadString('\n')
cli := Client{
currentRoom: nil, //starts as nil because the user is not initally in a room
outputChannel: createOutputChannel,
name: clientName,
}
ClientArray = append(ClientArray, &cli)
return cli.name
}
//adds message to the clients output channel, messages should be single line, NON delimited strings, that is the message should not include a new line
//the name of the sender will be added to the message to form a final message in the form of "sender says: message\n"
func (cli *Client) messageClientFromClient(message string, sender *Client){
cli.outputChannel <- (sender.name)+": "+message+"\n"
}
//removes the client from his current room and takes the client out of the rooms list of users
func (cli *Client)removeClientFromCurrentRoom(){
//not in a current room so just return
if cli.currentRoom == nil {
return
} else {
sendMessageToCurrentRoom(cli, "CLIENT HAS LEFT THE ROOM")
cl := cli.currentRoom.clientList
for i,roomClients := range cl{
if cli == roomClients {
cli.currentRoom.clientList = append(cl[:i], cl[i+1:]...)//deletes the element
}
}
cli.currentRoom = nil
return
}
}
/**********************************/
//wrapper for the internal function of the same name so that it can be used internally as well as by the client
func SendMessageToCurrentRoom(w http.ResponseWriter, r *http.Request) {
clientName := mux.Vars(r)["USER"]
message := r.Header.Get("message")
sender := getClientByName(clientName)
sendMessageToCurrentRoom(sender, message)
}
//sends a message to the clients current room, this function will replacee the WriteToAllChans function which sends a message to every client on the server
func sendMessageToCurrentRoom(sender *Client, message string){
//check if the client is currently in a room warn otherwise
if sender.currentRoom == nil {
//sender is not in room yet warn and exit
sender.outputChannel <- "You are not in a room yet\n"
return
}
//get the current room and its list of clients
//send the message to everyone in the room list that is CURRENTLY in the room
room := sender.currentRoom
chatMessage := &ChatMessage{
client: sender,
message: message,
}
fmt.Println("current room UserArray: ")
fmt.Println(room.clientList)
fmt.Println(room.clientList[0].currentRoom)
for _, roomUser := range room.clientList {
//check to see if the user is currently active in the room
if roomUser.currentRoom.name == room.name {
go roomUser.messageClientFromClient(chatMessage.message, chatMessage.client)
}
}
//save the message into the array of the rooms messages
room.chatLog = append(room.chatLog, chatMessage)
}
//*****************RPC SERVER OBJECT************************
//here we will create a server object and expose the processing commands to the client, this way the client will be able to
//directly call the below functions
//the client must call this one time, it will set the client up on the server and send the server back their unique name
func Connect(w http.ResponseWriter, r *http.Request) {
reply := ""
if len(ClientArray) < 10{//server can have more clients
reply = addClient()
}else{
reply = "ERROR_FULL"
}
fmt.Fprintf(w, reply)
}
//passes the latest message on the clients output channel to the client
func MessageClient(w http.ResponseWriter, r *http.Request) {
clientName := mux.Vars(r)["USER"]
cli := getClientByName(clientName)
reply := <-cli.outputChannel
fmt.Fprintf(w, reply)
}
//creates a room and logs to the console
func createroom(w http.ResponseWriter, r *http.Request){
roomName := mux.Vars(r)["ROOMNAME"]
clientName := r.Header.Get("username")
client := getClientByName(clientName)
//Creates a new room, with a specified roomCreator and roomName. the room will be added to the global list of rooms, if room is not unique, the client will be messaged
//check uniqueness of name, warn user and abort if not unique
for _, room := range RoomArray {
if roomName == room.name {
client.outputChannel <- "The room name you have specified is already in use\n"
return
}
}
newRoom := &Room{
name: roomName,
clientList: make([]*Client, 0),//room will start empty, we wont add the creator in
chatLog: nil,
creator: client,
}
RoomArray = append(RoomArray, newRoom)
if newRoom == nil { //name of room was not unique
return
}
client.outputChannel <- client.name+" created a new room called "+roomName + "\n"
}
func leaveroom(w http.ResponseWriter, r *http.Request){
clientName := mux.Vars(r)["USER"]
client := getClientByName(clientName)
client.removeClientFromCurrentRoom()
client.outputChannel <- "You have left the room\n"
}
//Loops through the HELP_INFO array and sends all the lines of help info to the user
func help(w http.ResponseWriter, r *http.Request){
clientName := r.Header.Get("username")
fmt.Println(clientName)
client := getClientByName(clientName)
for _, helpLine := range helpInfo{
client.outputChannel <- helpLine + "\n"
}
}
//sends the list of rooms to the client
func listrooms(w http.ResponseWriter, r *http.Request){
clientName := r.Header.Get("username")
client := getClientByName(clientName)
client.outputChannel <- "List of rooms:\n"
for _, roomName := range RoomArray{
client.outputChannel <- roomName.name + "\n"
}
}
//returns true of the room was joined successfully, returns false if there was a problem like the room does not exist
func join(w http.ResponseWriter, r *http.Request){
clientName := mux.Vars(r)["USER"]
roomName := mux.Vars(r)["ROOMNAME"]
client := getClientByName(clientName)
roomToJoin := getRoomByName(roomName)
if roomToJoin == nil{ //the room doesnt exist
fmt.Println(client.name+" tried to enter room: "+roomName+" which does not exist")
client.outputChannel <- "The room "+roomName+" does not exist\n"
return
}
//Room exists so now we can join it.
//check if user is already in the room
//add user to room if not in it already
if roomToJoin.isClientInRoom(client) {
client.outputChannel <- "You are already in that room\n"
} else {//join room and display all the rooms messages
client.removeClientFromCurrentRoom()
roomToJoin.clientList = append(roomToJoin.clientList, client)// add client to the rooms list
//switch users current room to room
client.currentRoom = roomToJoin
sendMessageToCurrentRoom(client, "CLIENT HAS JOINED THE ROOM")
//display all messages in the room when a user first joins a room
if roomToJoin.chatLog == nil{
return
}
for _, messages := range roomToJoin.chatLog {
client.messageClientFromClient(messages.message, messages.client)
}
client.outputChannel <- "----------------------\n"
}
}
**Client**:
package main
import "io/ioutil"
import "fmt"
import "bufio"
import "os"
import "strings"
import "net/http"
import "errors"
var stayAlive = true
var myName = ""
var site = ""
//starts up the client, starts the receiving thread and the input threads and then loops forever
func main() {
arguments := os.Args[1:]
if len(arguments) == 0 {
//no arguments start on localhost 8080
} else if len(arguments) != 2 {
fmt.Println("I cannot understand your arguments, you must specify no arguments or exactly 2, first the IP and the second as the port")
return
}
fmt.Println("Attempting to connect to localhost: 8080")
fmt.Print("Enter your name: ")
site = "http://localhost:8080"
resp, err := http.Get(site)
if err != nil{
fmt.Printf("Something went wrong with the connection, check that the server exists and that your IP/Port are correct:\nError Message: %v", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
////////////////////////////////////////////////////////////////////
//////////////////////and here I use that name as a user name/////////////////////////////////////////
myName = string(body)
fmt.Println("Your Username is: " + myName)
go getfromUser()
go getFromServer()
for stayAlive {
//loops forever until stayAlive is set to false and then it shuts down
}
}
//continually asks the server for input by calling for messages for the user
func getFromServer(){
for stayAlive {
resp, err := http.Get(site+"/"+myName+"/messages")
if err != nil{
fmt.Println("error in getting messages")
fmt.Println(err)
stayAlive = false
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Print(string(body))
}
}
//creates the http message that will be sent to the server
func messageHelper(method string, url string) error{
client := &http.Client{
CheckRedirect: nil,
}
reply, err := http.NewRequest(method, url, nil)
reply.Header.Add("username", myName)
client.Do(reply)
return err
}
//Handles user input, reads from stdin and then posts that line to the server
func getfromUser(){
for stayAlive{
reader := bufio.NewReader(os.Stdin)
message, _ := reader.ReadString('\n')//read from stdin till the next newline
var err error
message = strings.TrimSpace(message)//strips the newlines from the input
isCommand := strings.HasPrefix(message, "/")//checks to see if the line starts with /
if isCommand{
//parse command line, commands should be in the exact form of "/command arg arg arg" where args are not required
parsedCommand := strings.Split(message, " ")
if parsedCommand[0] == "/help" {
err = messageHelper("GET", site+"/help")
}else if parsedCommand[0] == "/createroom" {
// not enough arguments to the command
if len(parsedCommand) < 2{
err = errors.New("not enough args for create room")
}else{
err = messageHelper("POST", site+"/rooms/"+parsedCommand[1])
}
}else if parsedCommand[0] == "/listrooms" {
err = messageHelper("GET", site+"/rooms")
}else if parsedCommand[0] == "/join" {
//not enough given to the command
if len(parsedCommand) < 2{
err = errors.New("you must specify a room to join")
}else{
err = messageHelper("POST", site+"/rooms/"+parsedCommand[1]+"/"+myName)
}
}else if parsedCommand[0] == "/leave"{
err = messageHelper("DELETE", site+"/"+myName+"/leaveroom")
}
}else if stayAlive{ // message is not a command
//we need to create a post request to send the message to the server
client := &http.Client{
CheckRedirect: nil,
}
sendReply, _ := http.NewRequest("POST", site+"/"+myName+"/messageRoom", nil)
sendReply.Header.Add("message", message)
client.Do(sendReply)
}
if err != nil{
fmt.Println(err)
}
}
}