Using user input in client server program

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)
              }
            }
          }

Are you able to create some smaller code example that exhibits the same problem? Remember that you expose your code to many eyes who all need to take time for reading through the code and trying to identify possible problems.

And when you say that the code does not run, does this mean it fails with an error message? Or does it just exit? Or does it continue to run but appear to do anything?

And one tip for troubleshooting: Always check errors. The suppressed error in this line

clientName, _ := reader.ReadString(’\n’)

might well hide the error message that could help solving your problem.

And one final tip: Use code fences when posting code here. That is, place your code between lines containing three backticks, optionally appending the name of the language:

```go
// Your go code here
package main
``` 

which then looks like this:

// Your go code here
package main
2 Likes

Thanks for the tips. All of them were related to each other so, I could not cut some part of code but you’re completely right.

When I get input from user, nothing happens after that and the rest of application won’t run.

And even ReadString() does not return any error if you replace _ by err and check against nil?

No, it does not return any errors.

I would guess that the server waits for the client to finish its request but for some reason the client fails to do so.

Check the error that client.Do() returns. And verify if the call returns at all. Some well-placed log statements may help revealing where the code starts hanging (and why).

@parastoo did you get solution to this?