Using golang's defer in a separate method - go

I'm using the golang RabbitMQ library in a project, and I have a Connect function in a separate package. I'm calling Connect, in my main function, however because I connect to RabbitMQ in a separate function, the defer conn.Close() function is called, which closes the connection within the Connect function. Which makes perfect sense, but that begs the question, where then, do I call conn.Close()?
package drivers
import (
// Core
"log"
"os"
"time"
// Third party
"github.com/streadway/amqp"
)
type Queue struct {
Channel *amqp.Channel
}
func NewQueue() *Queue {
return &Queue{}
}
// Queue interface
type IQueue interface {
Connect(args ...interface{})
Publish(queue string, payload []byte) error
Listen(queue string) (<-chan amqp.Delivery, error)
Declare(queue string) (amqp.Queue, error)
}
// Connect - Connects to RabbitMQ
func (queue *Queue) Connect(args ...interface{}) {
var uri string
if args == nil {
// Get from env vars
uri = os.Getenv("RABBIT_MQ_URI")
if uri == "" {
log.Panic("No uri for queue given")
}
} else {
uri = args[0].(string)
}
// Make max 5 connection attempts, with a 1 second timeout
for i := 0; i < 5; i++ {
log.Println("Connecting to:", uri)
// If connection is successful, return new instance
conn, err := amqp.Dial(uri)
defer conn.Close()
if err == nil {
log.Println("Successfully connected to queue!")
channel, _ := conn.Channel()
queue.Channel = channel
return
}
log.Println("Failed to connect to queue, retrying...", err)
// Wait 1 second
time.Sleep(5 * time.Second)
}
}
// Declare a new queue
func (queue *Queue) Declare(queueName string) (amqp.Queue, error) {
return queue.Channel.QueueDeclare(
queueName,
true,
false,
false,
false,
nil,
)
}
// Publish a message
func (queue *Queue) Publish(queueName string, payload []byte) error {
return queue.Channel.Publish(
"",
queueName,
false,
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "application/json",
Body: payload,
},
)
}
// Listen for a new message
func (queue *Queue) Listen(queueName string) (<-chan amqp.Delivery, error) {
return queue.Channel.Consume(
queueName,
"",
true,
false,
false,
false,
nil,
)
}
As you can see in the code above, I'm calling defer conn.Close() after making a connection, however, this immediately closes the connection again.
Here's a Go Playground spoofing what I'm talking about... https://play.golang.org/p/5cz2D4gDgn

The simple solution is to call conn.Close() from elsewhere. This might just be me, but I think it's kinda odd that you wouldn't expose the connection elsewhere, i.e. as a field in Queue. Exposing the ability to close the connection from the Queue would solve this and give you more flexibility.
So this:
type Queue struct {
// your original fields
Conn amqp.Connection
}
// Somewhere else
queue.Conn.Close()
You're other option is connecting, then doing all the actions you want with that connection, then closing. I'm thinking something like:
func action(conn amqp.Connection, args ...interface{}) (<-chan bool) {
done := make(chan bool)
go func(amqpConn amqp.Connection, dChan chan bool){
// Do what you want with the connection
dChan <- true
}(conn, done)
return done
}
func (queue *Queue) Connect(args ...interface{}) {
// your connection code
doneChans := make([](chan bool), 5)
for i := 0; i < 5; i++ {
conn, err := amqp.Dial(uri)
defer conn.Close()
if err != nil {
// handle error
}
done := action(conn)
}
// This for loop will block until the 5 action calls are done
for j := range doneChans {
isFinish := <-doneChans[j]
if !isFinish {
// handle bad state
}
}
}

One option is to have Connect return conn, and call defer conn.Close() in the caller.
package driver
// imports, etc
func (queue *Queue) Connect(args ...interface{}) amqp.Connection, error {
// ...
conn, err := amqp.Dial(uri)
if err != nil {
return nil, err
}
// ...
return conn, nil
}
Then in another package:
package stuff
// imports, etc
func doStuff() {
queue = driver.NewQueue()
conn, err := queue.Connect(args...)
if err != nil {
log.Fatalf("oh no! %v!", err)
}
defer conn.Close()
// Do stuff
}

Related

WebSocket Server that feeds messages to clients in a round robin fashion

I have a websocket server in Go using the Gorilla websocket package. At this stage, I will have only one server serving 5 clients. I am getting some messages from upstream into the WebSocket server. My intention is to NOT BROADCAST all the messages to the connected clients. I would like to send only one copy of the message to the connected clients in a round robin fashion. It doesn't matter which client gets it as long as there is only one that gets it.
My attempted solution
I have a simple Go server, created a Pool of clients (websocket connections) that I am receiving. However, I do not see any options to round robin the messages as I mentioned above. All my clients are getting the message. How can I send only one copy of the message to the connected clients instead of broadcasting to all.
Discalimer
The code I have is taken from online sources and modified to my requirement. I am relatively new to Go and Websockets. Is this something even possible using Websockets?
main.go
package main
import (
"fmt"
"net/http"
"github.com/realtime-chat-go-react/backend/pkg/websocket"
)
func serveWs(pool *websocket.Pool, w http.ResponseWriter, r *http.Request) {
fmt.Println("WebSocket Endpoint Hit")
conn, err := websocket.Upgrade(w, r)
if err != nil {
fmt.Fprintf(w, "%+v\n", err)
}
client := &websocket.Client{
Conn: conn,
Pool: pool,
}
pool.Register <- client
client.Read()
}
func setupRoutes() {
pool := websocket.NewPool()
go pool.Start()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(pool, w, r)
})
}
func main() {
setupRoutes()
err := http.ListenAndServe(":8080",nil)
if err != nil {
fmt.Println(err)
}
}
websocket.go
package websocket
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
var wsList []*websocket.Conn
func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
conn, err := upgrader.Upgrade(w, r, nil)
wsList = append(wsList, conn) //Creating a list here to store all websocket clients.
if err != nil {
log.Println(err)
return nil, err
}
return conn, nil
}
pool.go
package websocket
import "fmt"
type Pool struct {
Register chan *Client
Unregister chan *Client
Clients map[*Client]bool
Broadcast chan Message
}
func NewPool() *Pool {
return &Pool{
Register: make(chan *Client),
Unregister: make(chan *Client),
Clients: make(map[*Client]bool),
Broadcast: make(chan Message),
}
}
func (pool *Pool) Start() {
for {
select {
case client := <-pool.Register:
pool.Clients[client] = true
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
fmt.Println(client)
client.Conn.WriteJSON(Message{Type: 1, Body: "New User Joined..."})
}
break
case client := <-pool.Unregister:
delete(pool.Clients, client)
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
client.Conn.WriteJSON(Message{Type: 1, Body: "User Disconnected..."})
}
break
case message := <-pool.Broadcast: //This is where I need to modify the code but not sure how
fmt.Println("Sending message to all clients in Pool")
for client, _ := range pool.Clients {
if err := client.Conn.WriteJSON(message); err != nil {
fmt.Println(err)
return
}
}
}
}
}
client.go
package websocket
import (
"fmt"
"log"
"sync"
"github.com/gorilla/websocket"
)
type Client struct {
ID string
Conn *websocket.Conn
Pool *Pool
mu sync.Mutex
}
type Message struct {
Type int `json:"type"`
Body string `json:"body"`
}
func (c *Client) Read() {
defer func() {
c.Pool.Unregister <- c
c.Conn.Close()
}()
for {
messageType, p, err := c.Conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
message := Message{Type: messageType, Body: string(p)}
c.Pool.Broadcast <- message
fmt.Printf("Message Received: %+v\n", message)
}
}
Modify the pool to store clients in a slice instead of a map. Add field to record index of the previous client used.
type Pool struct {
Register chan *Client
Unregister chan *Client
Clients []*Client
Broadcast chan Message
PrevClientIndex int
}
Round robin instead of broadcasting:
case message := <-pool.Broadcast:
if len(pool.Clients) == 0 {
continue
}
pool.PrevClientIndex++
if pool.PrevClientIndex >= len(pool.Clients) {
pool.PrevClientIndex = 0
}
client := pool.Clients[pool.PrevClientIndex]
if err := client.Conn.WriteJSON(message); err != nil {
// handle error
...
Register appends to the slice:
case client := <-pool.Register:
pool.Clients = append(pool.Clients, client)
...
Unregister removes the client from the slice:
case client := <-pool.Unregister:
j := 0
for _, c := range pool.Clients {
if c != client {
c.Clients[j] = c
j++
}
}
pool.Clients = pool.Clients[:j]
...

Sarama Kafka consumergroup function return

I am very new to Go Lang and attempting to make some adjustments to an open source library that consumes messages from Kafka using the Sarama library. The original code can be found here.
The original package implements a PartitionConsumer that works just fine if one doesn't need read consistency across multiple consumers consuming the same topic, however, that does not work for me.
I have done some work within the same application to implement the sarama NewConsumerGroup package using some examples I have found online.
Below is the code I currently have running:
package main
import (
"context"
// "flag"
"os"
"os/signal"
"sync"
"syscall"
"encoding/json"
"log"
"strings"
"github.com/Shopify/sarama"
// "github.com/Shopify/sarama/mocks"
)
// KafkaInput is used for recieving Kafka messages and
// transforming them into HTTP payloads.
type KafkaInput struct {
config *KafkaConfig
// consumers []sarama.PartitionConsumer
messages chan *sarama.ConsumerMessage
}
var (
brokers = ""
version = ""
group = ""
topics = ""
assignor = ""
oldest = true
verbose = false
)
// Consumer represents a Sarama consumer group consumer
type Consumer struct {
ready chan bool
}
// NewKafkaInput creates instance of kafka consumer client.
func NewKafkaInput(address string, config *KafkaConfig) *KafkaInput {
/**
* Construct a new Sarama configuration.
* The Kafka cluster version has to be defined before the consumer/producer is initialized.
*/
c := sarama.NewConfig()
// Configuration options go here
log.Println("Starting a new Sarama consumer")
if verbose {
sarama.Logger = log.New(os.Stdout, "[sarama] ", log.LstdFlags)
}
version, err := sarama.ParseKafkaVersion("2.1.1")
if err != nil {
log.Panicf("Error parsing Kafka version: %v", err)
}
c.Version = version
if oldest {
c.Consumer.Offsets.Initial = sarama.OffsetOldest
}
/**
* Setup a new Sarama consumer group
*/
consumer := Consumer{ready: make(chan bool)}
ctx, cancel := context.WithCancel(context.Background())
client, err := sarama.NewConsumerGroup(strings.Split(config.host, ","), config.group, c)
if err != nil {
log.Panicf("Error creating consumer group client: %v", err)
}
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for {
if err := client.Consume(ctx, []string{config.topic}, &consumer); err != nil {
log.Panicf("Error from consumer: %v", err)
}
// check if context was cancelled, signaling that the consumer should stop
if ctx.Err() != nil {
return
}
consumer.ready = make(chan bool)
}
}()
<-consumer.ready // Await till the consumer has been set up
log.Println("Sarama consumer up and running!...")
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
select {
case <-ctx.Done():
log.Println("terminating: context cancelled")
case <-sigterm:
log.Println("terminating: via signal")
}
cancel()
wg.Wait()
if err = client.Close(); err != nil {
log.Panicf("Error closing client: %v", err)
}
i := &KafkaInput{
config: config,
// consumers: make([]sarama.PartitionConsumer, len(partitions)),
// messages: make(chan *sarama.ConsumerMessage, 256),
messages: make(chan *sarama.ConsumerMessage, 256),
}
return i
}
// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().
func (consumer *Consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
// NOTE:
// Do not move the code below to a goroutine.
// The `ConsumeClaim` itself is called within a goroutine, see:
// https://github.com/Shopify/sarama/blob/master/consumer_group.go#L27-L29
for message := range claim.Messages() {
log.Printf("Message claimed: value = %s, timestamp = %v, topic = %s", string(message.Value), message.Timestamp, message.Topic)
session.MarkMessage(message, "")
}
return nil
}
// ErrorHandler should receive errors
func (i *KafkaInput) ErrorHandler(consumer sarama.PartitionConsumer) {
for err := range consumer.Errors() {
log.Println("Failed to read access log entry:", err)
}
}
// Read Comment
func (i *KafkaInput) Read(data []byte) (int, error) {
message := <-i.messages
if !i.config.useJSON {
copy(data, message.Value)
return len(message.Value), nil
}
var kafkaMessage KafkaMessage
json.Unmarshal(message.Value, &kafkaMessage)
buf, err := kafkaMessage.Dump()
if err != nil {
log.Println("Failed to decode access log entry:", err)
return 0, err
}
copy(data, buf)
return len(buf), nil
}
func (i *KafkaInput) String() string {
return "Kafka Input: " + i.config.host + "/" + i.config.topic
}
// Setup is run at the beginning of a new session, before ConsumeClaim
func (consumer *Consumer) Setup(sarama.ConsumerGroupSession) error {
// Mark the consumer as ready
close(consumer.ready)
return nil
}
// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
func (consumer *Consumer) Cleanup(sarama.ConsumerGroupSession) error {
return nil
}
The KafkaConfig carries the groupID and Topic for the consumer. When I run this program the consumer fires up and reads from the proper topic using the correct group and prints it to the STDOUT using the ConsumerClaim created in this function:
func (consumer *Consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
for message := range claim.Messages() {
log.Printf("Message claimed: value = %s, timestamp = %v, topic = %s", string(message.Value), message.Timestamp, message.Topic)
session.MarkMessage(message, "")
}
return nil
}
What I believe I need however is for the NewKafkaInput function to return *KafkaInput with the messages from the claim added to the struct (forgive me if I am using the wrong terminology here, this is my first Go rodeo).
...
i := &KafkaInput{
config: config,
// consumers: make([]sarama.PartitionConsumer, len(partitions)),
// messages: make(chan *sarama.ConsumerMessage, 256),
messages: make(chan *sarama.ConsumerMessage, 256),
}
return i
}
In the original example that is done here:
func NewKafkaInput(address string, config *KafkaConfig) *KafkaInput {
...
go func(consumer sarama.PartitionConsumer) {
defer consumer.Close()
for message := range consumer.Messages() {
i.messages <- message
}
}(consumer)
...
}
I have spent days toying around with moving functions in and out of the NewKafakInput function, attempting to add messages to the KafakInput struct outside the function and everything in between. I just can't get it to work. The NewKafakInput function needs to return the *KafkaInput with any messages so that this function can complete:
func (i *KafkaInput) Read(data []byte) (int, error) {
message := <-i.messages
if !i.config.useJSON {
copy(data, message.Value)
return len(message.Value), nil
}
var kafkaMessage KafkaMessage
json.Unmarshal(message.Value, &kafkaMessage)
buf, err := kafkaMessage.Dump()
if err != nil {
log.Println("Failed to decode access log entry:", err)
return 0, err
}
copy(data, buf)
return len(buf), nil
}
Its entirely possible I have made a complete mess of this thing as well, but any help and input is appreciated.
Thanks
Here is the solution to my problem. I had goroutines blocking the main function(s) and they needed to be broken out. If the code below doesn't make any sense, here is a link to the program I was modifying: https://github.com/buger/goreplay. If I can get a response from the owner I plan on cleaning up the code and submitting a pull request, or possibly publishing a fork.
package main
import (
"context"
"encoding/json"
"strings"
"os"
"log"
"github.com/Shopify/sarama"
)
// KafkaInput is used for recieving Kafka messages and
// transforming them into HTTP payloads.
type KafkaInput struct {
sarama.ConsumerGroup
config *KafkaConfig
consumer Consumer
messages chan *sarama.ConsumerMessage
}
// Consumer represents a Sarama consumer group consumer
type Consumer struct {
ready chan bool
messages chan *sarama.ConsumerMessage
}
var (
brokers = ""
version = ""
group = ""
topics = ""
assignor = ""
oldest = true
verbose = false
)
// NewKafkaInput creates instance of kafka consumer client.
func NewKafkaInput(address string, config *KafkaConfig) *KafkaInput {
/**
* Construct a new Sarama configuration.
* The Kafka cluster version has to be defined before the consumer/producer is initialized.
*/
c := sarama.NewConfig()
// Configuration options go here
log.Printf("KafkaConfig: %s", config.host)
log.Printf("KafkaConfig: %s", config.group)
log.Printf("KafkaConfig: %s", config.topic)
log.Println("Starting a new Sarama consumer")
if verbose {
sarama.Logger = log.New(os.Stdout, "[sarama] ", log.LstdFlags)
}
version, err := sarama.ParseKafkaVersion("2.1.1")
if err != nil {
log.Panicf("Error parsing Kafka version: %v", err)
}
c.Version = version
if oldest {
c.Consumer.Offsets.Initial = sarama.OffsetOldest
}
group, err := sarama.NewConsumerGroup(strings.Split(config.host, ","), config.group, c)
/**
* Setup a new Sarama consumer group
*/
consumer := Consumer{
ready: make(chan bool),
messages: make(chan *sarama.ConsumerMessage, 256),
}
i := &KafkaInput{
ConsumerGroup: group,
config: config,
messages: make(chan *sarama.ConsumerMessage, 256),
consumer: consumer,
}
go i.loop([]string{config.topic})
i.messages = consumer.messages
return i
}
//ConsumeClaim and stuff
func (i *KafkaInput) ConsumeClaim(s sarama.ConsumerGroupSession, c sarama.ConsumerGroupClaim) error {
for msg := range c.Messages() {
s.MarkMessage(msg, "")
i.Push(msg)
}
return nil
}
func (i *KafkaInput) loop(topic []string) {
ctx := context.Background()
for {
if err := i.Consume(ctx, []string{i.config.topic}, i); err != nil {
return
}
}
}
// Push Messages
func (i *KafkaInput) Push(m *sarama.ConsumerMessage) {
if i.consumer.messages != nil {
log.Printf("MSGPUSH: %s", m)
i.consumer.messages <- m
}
}
func (i *KafkaInput) Read(data []byte) (int, error) {
message := <-i.messages
log.Printf("Msg: %s", string(message.Value))
if !i.config.useJSON {
copy(data, message.Value)
return len(message.Value), nil
}
var kafkaMessage KafkaMessage
json.Unmarshal(message.Value, &kafkaMessage)
buf, err := kafkaMessage.Dump()
if err != nil {
log.Println("Failed to decode access log entry:", err)
return 0, err
}
copy(data, buf)
return len(buf), nil
}
func (i *KafkaInput) String() string {
return "Kafka Input: " + i.config.host + "/" + i.config.topic
}
// Setup is run at the beginning of a new session, before ConsumeClaim
func (i *KafkaInput) Setup(s sarama.ConsumerGroupSession) error {
return nil
}
// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
func (i *KafkaInput) Cleanup(s sarama.ConsumerGroupSession) error {
return nil
}

Websocket freezes if disconnected abnormally

I've created a simple websocket that publishes a JSON stream. I't works fine most of the time except for few cases where I think while looping through the clients to send them message, it gets hung up on a client that is being disconnected abnormally. What measure can I add to this code to mitigate it?
Client.go
import (
"github.com/gorilla/websocket"
)
type client struct {
socket *websocket.Conn
send chan *Message
}
func (c *client) read() {
defer c.socket.Close()
for {
_, _, err := c.socket.ReadMessage()
if err != nil {
log.Info("Websocket: %s", err)
break
}
}
}
func (c *client) write() {
defer c.socket.Close()
for msg := range c.send {
err := c.socket.WriteJSON(msg)
if err != nil {
break
}
}
}
Stream.go
import (
"net/http"
"github.com/gorilla/websocket"
)
const (
socketBufferSize = 1024
messageBufferSize = 256
)
var upgrader = &websocket.Upgrader{
ReadBufferSize: socketBufferSize,
WriteBufferSize: socketBufferSize,
}
type Stream struct {
Send chan *Message
join chan *client
leave chan *client
clients map[*client]bool
}
func (s *Stream) Run() {
for {
select {
case client := <-s.join: // joining
s.clients[client] = true
case client := <-s.leave: // leaving
delete(s.clients, client)
close(client.send)
case msg := <-s.Send: // send message to all clients
for client := range s.clients {
client.send <- msg
}
}
}
}
func (s *Stream) ServeHTTP(w http.ResponseWriter, res *http.Request) {
socket, err := upgrader.Upgrade(w, res, nil)
if err != nil {
log.Error(err)
return
}
defer func() {
socket.Close()
}()
client := &client{
socket: socket,
send: make(chan *Message, messageBufferSize),
}
s.join <- client
defer func() { s.leave <- client }()
go client.write()
client.read()
}
See the Gorilla Chat Application for an example of how to avoid blocking on a client.
The key parts are:
Use a buffered channel for sending to the client. Your application is already doing this.
Send to the client using select/default to avoid blocking. Assume that the client is blocked on write when the client cannot immediately receive a message. Close the client's channel in this situation to cause the client's write loop to exit.
Write with a deadline.

Multiple connections to a TCP server

I've developed a small Go TCP server to make a chat application. But when I try to connect clients to it, the server works fine with two clients, but whenever I tried to connect the third client it is not connected to the server. I am running on Windows. What could be the issue?
package main
import (
"bufio"
"fmt"
"net"
)
var allClients map[*Client]int
type Client struct {
// incoming chan string
outgoing chan string
reader *bufio.Reader
writer *bufio.Writer
conn net.Conn
connection *Client
}
func (client *Client) Read() {
for {
line, err := client.reader.ReadString('\n')
if err == nil {
if client.connection != nil {
client.connection.outgoing <- line
}
fmt.Println(line)
} else {
break
}
}
client.conn.Close()
delete(allClients, client)
if client.connection != nil {
client.connection.connection = nil
}
client = nil
}
func (client *Client) Write() {
for data := range client.outgoing {
client.writer.WriteString(data)
client.writer.Flush()
}
}
func (client *Client) Listen() {
go client.Read()
go client.Write()
}
func NewClient(connection net.Conn) *Client {
writer := bufio.NewWriter(connection)
reader := bufio.NewReader(connection)
client := &Client{
// incoming: make(chan string),
outgoing: make(chan string),
conn: connection,
reader: reader,
writer: writer,
}
client.Listen()
return client
}
func main() {
allClients = make(map[*Client]int)
listener, _ := net.Listen("tcp", ":8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err.Error())
}
client := NewClient(conn)
for clientList, _ := range allClients {
if clientList.connection == nil {
client.connection = clientList
clientList.connection = client
fmt.Println("Connected")
}
}
allClients[client] = 1
fmt.Println(len(allClients))
}
}
Your code is fine. I compiled in on Linux, tried with 4 connections. Everything worked as expected.

Go WebSocket server: Use of closed network connection

I'm working on a websocket server and for some reason it outputs:
"WSARecv tcp 127.0.0.1:8080: Use of closed network connection."
I don't know why it says that because I haven't closed the connection at any point in time.
Below are some source code files of the server. If needed, the full source code is here on GitHub.
connection.go
package net
import (
"log"
"golang.org/x/net/websocket"
pnet "kekocity/misc/packet"
"kekocity/interfaces"
)
type Connection struct {
socket *websocket.Conn
txChan chan pnet.INetMessageWriter
rxChan chan pnet.INetMessageReader
user interfaces.IUser
}
func NewConnection(_socket *websocket.Conn) *Connection {
// The pointer allow us to modify connection struct from outside
connection := &Connection{
socket: _socket,
txChan: make(chan pnet.INetMessageWriter),
rxChan: make(chan pnet.INetMessageReader),
}
go connection.ReceivePoller()
go connection.SendPoller()
return connection
}
func (c *Connection) AssignToUser(_user interfaces.IUser) {
if _user == nil {
panic("net.connection: the user interface can not be nil!")
return
}
c.user = _user
_user.SetNetworkChans(c.rxChan, c.txChan)
}
/*
* ReceivePoller and SendPoller starts listening when the first packet is verified and the new connection is started
*/
func (c *Connection) ReceivePoller() {
for {
packet := pnet.NewPacket()
var buffer []uint8
err := websocket.Message.Receive(c.socket, &buffer)
if err == nil {
copy(packet.Buffer[0:len(buffer)], buffer[0:len(buffer)])
c.parsePacket(packet)
} else {
println(err.Error())
break
}
}
}
func (c *Connection) SendPoller() {
for {
// Read messages from transmit channel
message := <-c.txChan
if message == nil {
log.Println("SenPoller", "The message is nil, break the loop")
break
}
// Convert netmessage to packet
packet := message.WritePacket()
packet.SetHeader()
// Create byte buffer
buffer := packet.GetBuffer()
data := buffer[0:packet.GetMsgSize()]
// Send bytes off to the internetz
websocket.Message.Send(c.socket, data)
}
}
func (c *Connection) parsePacket(_packet pnet.IPacket) {
log.Println("net.connection:", "Received new packet!")
}
func (c *Connection) Close() {
// Close channels
close(c.txChan)
close(c.rxChan)
// Close the socket
c.socket.Close()
c.user = nil
}
server.go
package net
// <imports>
import (
"log"
"fmt"
"net/http"
"golang.org/x/net/websocket"
pnet "kekocity/misc/packet"
cmap "kekocity/misc/concurrentmap"
"kekocity/data/helpers"
"kekocity/net/message"
)
var server *Server
type Server struct {
port int
connectedUsers *cmap.ConcurrentMap
}
func init() {
server = newServer()
}
func newServer() *Server {
return &Server{
port: 8080,
connectedUsers: cmap.New(),
}
}
func Listen(_port int) {
server.port = _port
log.Printf("Listening for connections on port %d!", _port)
http.Handle("/ws", websocket.Handler(clientConnection))
err := http.ListenAndServe(fmt.Sprintf(":%d", _port), nil)
if err != nil {
panic("ListenAndServe: " + err.Error())
}
}
func clientConnection(clientsock *websocket.Conn) {
packet := pnet.NewPacket()
buffer := make([]uint8, pnet.PACKET_MAXSIZE)
recv, err := clientsock.Read(buffer)
if err == nil {
copy(packet.Buffer[0:recv], buffer[0:recv])
parseFirstMessage(clientsock, packet)
} else {
if err.Error() != "EOF" {
log.Println("net.server", "Client connection error:", err.Error())
}
}
}
func parseFirstMessage(_conn *websocket.Conn, _packet *pnet.Packet) {
_message := _packet.ToString()
// If the first packet length is < 1 close the socket
if len(_message) < 1 {
_conn.Close()
return
}
// Create the connection
connection := NewConnection(_conn)
// Authentication wrapper
authPacket := &message.AuthMessage{}
user, err := helpers.AuthHelper.AuthenticateUsingCredentials(_message)
if err != nil {
log.Fatal("Invalid credentials!")
authPacket.Status = "error"
} else {
// Need to check if its already logged
authPacket.Status = "success"
connection.AssignToUser(user)
connection.txChan <- authPacket
return
}
// Send bad auth message and close
connection.txChan <- authPacket
connection.Close()
}
Full source code: github
The context in the request is canceled right after the handler finishes.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
This is the reason why your context. In this diagram, you can find how the context is created in the server.Serve method.
It is described in more detail in the blog post: HTTP context livetime.
In the websocket, the situation is very similar. The context is closed just after the handler finishes.
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
rwc, buf, err := w.(http.Hijacker).Hijack()
if err != nil {
panic("Hijack failed: " + err.Error())
}
// The server should abort the WebSocket connection if it finds
// the client did not send a handshake that matches with protocol
// specification.
defer rwc.Close() // <- here! It's executed when the s.Handler(conn) exites
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
if err != nil {
return
}
if conn == nil {
panic("unexpected nil conn")
}
s.Handler(conn)
}
To fix that, you can create a new context from context.Background(), add some timeouts if needed and use it instead.

Resources