├── Dockerfile ├── LICENSE ├── README.md ├── build.sh ├── config ├── config.yml └── configuration.go ├── lib ├── callbacks.go ├── client.go └── tcp_server.go ├── main.go ├── messages ├── consumer.go └── producer.go └── models ├── device_request.go ├── device_response.go ├── server_request.go └── server_response.go /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian 2 | MAINTAINER "Orr Chen" 3 | WORKDIR /app 4 | # Now just add the binary 5 | ADD app/tcp-server.linux /app/ 6 | ADD config /app/ 7 | EXPOSE 8080 3000 8 | CMD ["./tcp-server.linux","-config=config.yml"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Orr Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simple messaging framework using Go TCP server and Kafka 2 | Part of the advent 2017 series of Gopher Academy, more info can be found at: 3 | https://blog.gopheracademy.com/advent-2017/messaging-framework/ 4 | 5 | ## Running 6 | to run server: `go run main.go -config=config/config.yml` 7 | 8 | 9 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Transform long options to short ones 3 | for arg in "$@"; do 4 | shift 5 | case "$arg" in 6 | "--tag") set -- "$@" "-t" ;; 7 | "--repository") set -- "$@" "-r" ;; 8 | "--name") set -- "$@" "-n" ;; 9 | "--help") set -- "$@" "-h" ;; 10 | *) set -- "$@" "$arg" 11 | esac 12 | done 13 | 14 | OPTIND=1 15 | 16 | while getopts "r:t:i:n:h" opt; do 17 | case $opt in 18 | h) echo "usage build.sh --tag --name --repository " 19 | exit 20 | ;; 21 | t) tag="$OPTARG" 22 | ;; 23 | r) repository="$OPTARG" 24 | ;; 25 | n) name="$OPTARG" 26 | ;; 27 | \?) echo "Invalid option -$OPTARG" >&2 28 | ;; 29 | esac 30 | done 31 | 32 | shift $(expr $OPTIND - 1) # remove options from positional parameters 33 | 34 | 35 | if [ -z ${tag+x} ]; then 36 | echo "tag is not set" 37 | exit; 38 | fi 39 | 40 | if [ -z ${name+x} ]; then 41 | echo "name is not set" 42 | exit; 43 | fi 44 | 45 | if [ -z ${repository+x} ]; then 46 | echo "repository is not set" 47 | exit; 48 | fi 49 | 50 | GOOS=linux GOARCH=amd64 go build -o app/tcp-server.linux main.go 51 | docker build -t ${name} . 52 | docker tag ${name} ${repository}:${tag} 53 | docker push ${repository}:${tag} 54 | 55 | 56 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | brokers_list: 2 | - "localhost:9092" 3 | producer_topic: "tcp_layer_messages" 4 | consumer_topics: 5 | - "workers_layer_messages" 6 | consumer_group_id: "id-1" -------------------------------------------------------------------------------- /config/configuration.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "path/filepath" 5 | "log" 6 | "io/ioutil" 7 | "gopkg.in/yaml.v2" 8 | ) 9 | 10 | var conf *Configuration 11 | 12 | func InitConfig(configPath string) { 13 | if conf == nil { 14 | filename, _ := filepath.Abs(configPath) 15 | log.Println("trying to read file ", filename) 16 | yamlFile, err := ioutil.ReadFile(filename) 17 | var confi Configuration 18 | err = yaml.Unmarshal(yamlFile, &confi) 19 | if err != nil { 20 | log.Println(err) 21 | panic(err) 22 | } 23 | conf = &confi 24 | } 25 | } 26 | 27 | func Get() *Configuration { 28 | return conf 29 | } 30 | 31 | type Configuration struct { 32 | BrokersList []string `yaml:"brokers_list"` 33 | ProducerTopic string `yaml:"producer_topic"` 34 | ConsumerTopics []string `yaml:"consumer_topics"` 35 | ConsumerGroupId string `yaml:"consumer_group_id"` 36 | } 37 | -------------------------------------------------------------------------------- /lib/callbacks.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | type Callbacks struct { 4 | OnNewConnection func (clientUid string) 5 | OnConnectionTerminated func (clientUid string) 6 | OnDataReceived func (clientUid string, data []byte) 7 | } 8 | -------------------------------------------------------------------------------- /lib/client.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "net" 5 | "bufio" 6 | "io" 7 | "log" 8 | ) 9 | type ConnectionEventType string 10 | 11 | const 12 | ( 13 | CONNECTION_EVENT_TYPE_NEW_CONNECTION ConnectionEventType = "new_connection" 14 | CONNECTION_EVENT_TYPE_CONNECTION_TERMINATED ConnectionEventType = "connection_terminated" 15 | CONNECTION_EVENT_TYPE_CONNECTION_GENERAL_ERROR ConnectionEventType = "general_error" 16 | ) 17 | 18 | // Client holds info about connection 19 | type Client struct { 20 | Uid string /* client is responsible of generating a uinque uid for each request, it will be sent in the response from the server so that client will know what request generated this response */ 21 | DeviceUid string /* a unique id generated from the client itself */ 22 | conn net.Conn 23 | onConnectionEvent func(c *Client, eventType ConnectionEventType, e error) /* function for handling new connections */ 24 | onDataEvent func(c *Client, data []byte) /* function for handling new date events */ 25 | } 26 | 27 | func NewClient(conn net.Conn,onConnectionEvent func(c *Client,eventType ConnectionEventType, e error), onDataEvent func(c *Client, data []byte)) *Client { 28 | return &Client{ 29 | conn: conn, 30 | onConnectionEvent: onConnectionEvent, 31 | onDataEvent: onDataEvent, 32 | } 33 | } 34 | 35 | // Read client data from channel 36 | func (c *Client) listen() { 37 | reader := bufio.NewReader(c.conn) 38 | buf := make([]byte, 1024) 39 | for { 40 | n, err := reader.Read(buf) 41 | 42 | switch err { 43 | case io.EOF: 44 | // connection terminated 45 | c.conn.Close() 46 | c.onConnectionEvent(c,CONNECTION_EVENT_TYPE_CONNECTION_TERMINATED, err) 47 | return 48 | case nil: 49 | // new data available 50 | c.onDataEvent(c, buf[:n]) 51 | default: 52 | log.Fatalf("Receive data failed:%s", err) 53 | c.conn.Close() 54 | c.onConnectionEvent(c, CONNECTION_EVENT_TYPE_CONNECTION_GENERAL_ERROR, err) 55 | return 56 | } 57 | } 58 | } 59 | 60 | // Send text message to client 61 | func (c *Client) Send(message []byte) error { 62 | _, err := c.conn.Write(message) 63 | return err 64 | } 65 | 66 | // Send bytes to client 67 | func (c *Client) SendBytes(b []byte) error { 68 | _, err := c.conn.Write(b) 69 | return err 70 | } 71 | 72 | func (c *Client) Conn() net.Conn { 73 | return c.conn 74 | } 75 | 76 | func (c *Client) Close() error { 77 | if c.conn!=nil { 78 | return c.conn.Close() 79 | } 80 | return nil 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib/tcp_server.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync" 7 | "github.com/satori/go.uuid" 8 | "errors" 9 | "fmt" 10 | ) 11 | 12 | 13 | 14 | // TCP Server 15 | type TcpServer struct { 16 | address string // Address to open connection: localhost:9999 17 | connLock sync.RWMutex 18 | connections map[string]*Client 19 | callbacks Callbacks 20 | listener net.Listener 21 | } 22 | 23 | func (s *TcpServer) onConnectionEvent(c *Client,eventType ConnectionEventType, e error ) { 24 | switch eventType { 25 | case CONNECTION_EVENT_TYPE_NEW_CONNECTION: 26 | s.connLock.Lock() 27 | u1 := uuid.NewV4() 28 | uidString := u1.String() 29 | c.Uid = uidString 30 | s.connections[uidString] = c 31 | s.connLock.Unlock() 32 | //log.Println(eventType , " , uid:", c.Uid, " , ip: ", c.Conn().RemoteAddr().String()) 33 | if s.callbacks.OnNewConnection != nil { 34 | s.callbacks.OnNewConnection(uidString) 35 | } 36 | case CONNECTION_EVENT_TYPE_CONNECTION_TERMINATED, CONNECTION_EVENT_TYPE_CONNECTION_GENERAL_ERROR: 37 | //log.Println(eventType , " , uid:", c.Uid, " , ip: ", c.Conn().RemoteAddr().String(), " , error: ", e.Error()) 38 | s.connLock.Lock() 39 | delete(s.connections,c.Uid) 40 | s.connLock.Unlock() 41 | if s.callbacks.OnConnectionTerminated!=nil { 42 | s.callbacks.OnConnectionTerminated(c.Uid) 43 | } 44 | } 45 | } 46 | 47 | func (s *TcpServer) onDataEvent(c *Client, data []byte) { 48 | //log.Println("onDataEvent, ", c.Conn().RemoteAddr().String(), " data: " , string(data)) 49 | if s.callbacks.OnDataReceived!=nil { 50 | s.callbacks.OnDataReceived(c.Uid, data) 51 | } 52 | } 53 | 54 | 55 | // Start network Server 56 | func (s *TcpServer) Listen() { 57 | var err error 58 | s.listener, err = net.Listen("tcp", s.address) 59 | if err != nil { 60 | log.Fatal("Error starting TCP Server.: " , err) 61 | } 62 | for { 63 | conn, _ := s.listener.Accept() 64 | client := NewClient(conn,s.onConnectionEvent,s.onDataEvent) 65 | s.onConnectionEvent(client, CONNECTION_EVENT_TYPE_NEW_CONNECTION,nil) 66 | go client.listen() 67 | 68 | } 69 | } 70 | 71 | // Creates new tcp Server instance 72 | func NewServer(address string, callbacks Callbacks ) *TcpServer { 73 | log.Println("Creating Server with address", address) 74 | s := &TcpServer{ 75 | address: address, 76 | callbacks: callbacks, 77 | } 78 | s.connections = make(map[string]*Client) 79 | return s 80 | } 81 | 82 | func (s *TcpServer) SendDataByClientId(clientUid string, data []byte) error{ 83 | if s.connections[clientUid]!=nil { 84 | return s.connections[clientUid].Send(data) 85 | } else { 86 | return errors.New(fmt.Sprint("no connection with uid ", clientUid)) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (s *TcpServer) SendDataByDeviceUid(deviceUid string, data []byte) error{ 93 | for k := range s.connections { 94 | if s.connections[k].DeviceUid == deviceUid { 95 | return s.connections[k].Send(data) 96 | } 97 | } 98 | return errors.New(fmt.Sprint("no connection with deviceUid ", deviceUid)) 99 | } 100 | 101 | func (s *TcpServer) Close(){ 102 | log.Println("TcpServer.Close()") 103 | log.Println("s.connections length: " , len(s.connections)) 104 | for k := range s.connections { 105 | fmt.Printf("key[%s]\n", k) 106 | s.connections[k].Close() 107 | } 108 | s.listener.Close() 109 | } 110 | 111 | func (s *TcpServer) SetDeviceUidToClient(clientUid string, deviceUid string) error{ 112 | if s.connections[clientUid]!=nil { 113 | s.connections[clientUid].DeviceUid = deviceUid 114 | return nil 115 | } else { 116 | return errors.New(fmt.Sprint("no connection with uid ", clientUid)) 117 | } 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | 5 | "./lib" 6 | "./messages" 7 | "./models" 8 | "./config" 9 | "log" 10 | "os" 11 | "flag" 12 | "strings" 13 | "os/signal" 14 | "syscall" 15 | "fmt" 16 | "encoding/json" 17 | "net/http" 18 | "github.com/satori/go.uuid" 19 | 20 | ) 21 | 22 | var tcpServer *lib.TcpServer 23 | var producer *messages.Producer 24 | var consumer *messages.Consumer 25 | 26 | var ( 27 | /*brokers = flag.String("brokers", os.Getenv("KAFKA_PEERS"), "The Kafka brokers to connect to, as a comma separated list") 28 | producerTopic = flag.String("producer_topic", "tcp_layer_messages", "The topic to produce messages to") 29 | consumerTopics = flag.String("consumer_topic", "workers_layer_messages", "The topic to consume messages from") 30 | consumerGroupId = flag.String("consumer_group_id", "", "consumer group id") 31 | verbose = flag.Bool("verbose", false, "Turn on Sarama logging") 32 | certFile = flag.String("certificate", "", "The optional certificate file for client authentication") 33 | keyFile = flag.String("key", "", "The optional key file for client authentication") 34 | caFile = flag.String("ca", "", "The optional certificate authority file for TLS client authentication") 35 | verifySsl = flag.Bool("verify", false, "Optional verify ssl certificates chain")*/ 36 | configPath = flag.String("config", "", "config file") 37 | consumerGroupId string 38 | ) 39 | 40 | func main() { 41 | flag.Parse() 42 | if *configPath == "" { 43 | flag.PrintDefaults() 44 | os.Exit(1) 45 | } 46 | config.InitConfig(*configPath) 47 | configuration := config.Get() 48 | /*if *brokers == "" { 49 | flag.PrintDefaults() 50 | os.Exit(1) 51 | }*/ 52 | 53 | if configuration.ConsumerGroupId==""{ 54 | consumerGroupId = uuid.NewV4().String() 55 | } else { 56 | consumerGroupId = configuration.ConsumerGroupId 57 | } 58 | 59 | log.Printf("Kafka brokers: %s", strings.Join(configuration.BrokersList, ", ")) 60 | callbacks := lib.Callbacks{ 61 | OnDataReceived: onDataReceived, 62 | OnConnectionTerminated: onConnectionTerminated, 63 | OnNewConnection: onNewConnection, 64 | } 65 | tcpServer = lib.NewServer(":3000", callbacks) 66 | producerCallbacks := messages.ProducerCallbacks{ 67 | OnError: onProducerError, 68 | } 69 | f := false 70 | producer = messages.NewProducer(producerCallbacks,configuration.BrokersList,configuration.ProducerTopic,nil,nil,nil,&f) 71 | 72 | consumerCallbacks := messages.ConsumerCallbacks{ 73 | OnDataReceived: onDataConsumed, 74 | OnError: onConsumerError, 75 | } 76 | consumer = messages.NewConsumer(consumerCallbacks,configuration.BrokersList,consumerGroupId,configuration.ConsumerTopics) 77 | consumer.Consume() 78 | 79 | signal_chan := make(chan os.Signal, 1) 80 | signal.Notify(signal_chan, 81 | syscall.SIGINT, 82 | syscall.SIGTERM, 83 | syscall.SIGQUIT, 84 | syscall.SIGKILL) 85 | 86 | go func() { 87 | for { 88 | s := <-signal_chan 89 | switch s { 90 | case syscall.SIGINT: 91 | fmt.Println("syscall.SIGINT") 92 | cleanup() 93 | // kill -SIGTERM XXXX 94 | case syscall.SIGTERM: 95 | fmt.Println("syscall.SIGTERM") 96 | cleanup() 97 | // kill -SIGQUIT XXXX 98 | case syscall.SIGQUIT: 99 | fmt.Println("syscall.SIGQUIT") 100 | cleanup() 101 | case syscall.SIGKILL: 102 | fmt.Println("syscall.SIGKILL") 103 | cleanup() 104 | default: 105 | fmt.Println("Unknown signal.") 106 | } 107 | } 108 | }() 109 | 110 | go func(){ 111 | http.HandleFunc("/", handler) 112 | http.ListenAndServe(":8080", nil) 113 | }() 114 | 115 | tcpServer.Listen() 116 | 117 | 118 | } 119 | 120 | func handler(w http.ResponseWriter, r *http.Request) { 121 | fmt.Fprint(w, "TCP Server is up and running!") 122 | } 123 | 124 | func cleanup(){ 125 | tcpServer.Close() 126 | producer.Close() 127 | consumer.Close() 128 | os.Exit(0) 129 | } 130 | 131 | 132 | 133 | func onNewConnection(clientUid string) { 134 | log.Println("onNewConnection, uid: ", clientUid) 135 | } 136 | 137 | func onConnectionTerminated(clientUid string) { 138 | log.Println("onConnectionTerminated, uid: ", clientUid) 139 | } 140 | 141 | /** 142 | Called when data is received from a TCP client, will generate a message to the message broker 143 | */ 144 | func onDataReceived(clientUid string, data []byte) { 145 | log.Println("onDataReceived, uid: ", clientUid, ", data: ", string(data)) 146 | if string(data)=="Ping" { 147 | log.Println("sending Pong") 148 | //answer with pong 149 | tcpServer.SendDataByClientId(clientUid, []byte("Pong")) 150 | } 151 | if producer!=nil { 152 | var deviceRequest models.DeviceRequest 153 | err:= json.Unmarshal(data,&deviceRequest) 154 | if err==nil { 155 | serverRequest := models.ServerRequest{ 156 | DeviceRequest: deviceRequest, 157 | ServerId: "1", 158 | ClientId: clientUid, 159 | } 160 | producer.Produce(serverRequest) 161 | } else { 162 | log.Println(err) 163 | } 164 | 165 | } 166 | 167 | } 168 | 169 | func onProducerError(err error){ 170 | log.Println("onProducerError: ", err) 171 | } 172 | 173 | func onConsumerError(err error){ 174 | log.Println("onConsumerError: ",err) 175 | } 176 | 177 | func onDataConsumed(data []byte){ 178 | log.Println("onDataConsumed: ", string(data)) 179 | var serverResponse models.ServerResponse 180 | err := json.Unmarshal(data,&serverResponse) 181 | if err!=nil { 182 | log.Println(err) 183 | return 184 | } 185 | if serverResponse.DeviceResponse.Action == "connect.response" && serverResponse.DeviceResponse.Status == "ok" && serverResponse.ClientId!= "" { 186 | //attach the device id to our existing client 187 | err =tcpServer.SetDeviceUidToClient(serverResponse.ClientId,serverResponse.DeviceUid) 188 | if err!=nil { 189 | log.Println(err) 190 | } 191 | } 192 | toSend, err := json.Marshal(serverResponse.DeviceResponse) 193 | if err!=nil { 194 | log.Println(err) 195 | return 196 | } 197 | if serverResponse.ClientId!="" { 198 | tcpServer.SendDataByClientId(serverResponse.ClientId,toSend) 199 | } else { 200 | if serverResponse.DeviceUid!=""{ 201 | tcpServer.SendDataByDeviceUid(serverResponse.DeviceUid,toSend) 202 | } 203 | } 204 | 205 | 206 | } -------------------------------------------------------------------------------- /messages/consumer.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "github.com/bsm/sarama-cluster" 5 | "github.com/Shopify/sarama" 6 | "github.com/satori/go.uuid" 7 | "os" 8 | "syscall" 9 | "fmt" 10 | "os/signal" 11 | "log" 12 | ) 13 | 14 | 15 | 16 | type Consumer struct { 17 | consumer *cluster.Consumer 18 | callbacks ConsumerCallbacks 19 | } 20 | 21 | func NewConsumer(callbacks ConsumerCallbacks,brokerList []string, groupId string, topics []string) *Consumer { 22 | consumer := Consumer{callbacks:callbacks} 23 | 24 | config := cluster.NewConfig() 25 | config.ClientID = uuid.NewV4().String() 26 | config.Consumer.Offsets.Initial = sarama.OffsetNewest 27 | saramaConsumer, err := cluster.NewConsumer(brokerList, groupId, topics, config) 28 | if err != nil { 29 | panic(err) 30 | } 31 | consumer.consumer = saramaConsumer 32 | return &consumer 33 | 34 | } 35 | 36 | func (c *Consumer) Consume() { 37 | // Create signal channel 38 | sigchan := make(chan os.Signal, 1) 39 | signal.Notify(sigchan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 40 | 41 | // Consume all channels, wait for signal to exit 42 | go func(){ 43 | for { 44 | select { 45 | case msg, more := <-c.consumer.Messages(): 46 | if more { 47 | if c.callbacks.OnDataReceived!=nil { 48 | c.callbacks.OnDataReceived(msg.Value) 49 | } 50 | fmt.Fprintf(os.Stdout, "%s/%d/%d\t%s\n", msg.Topic, msg.Partition, msg.Offset, msg.Value) 51 | c.consumer.MarkOffset(msg, "") 52 | } 53 | case ntf, more := <-c.consumer.Notifications(): 54 | if more { 55 | log.Printf("Rebalanced: %+v\n", ntf) 56 | } 57 | case err, more := <-c.consumer.Errors(): 58 | if more { 59 | if c.callbacks.OnError!=nil { 60 | c.callbacks.OnError(err) 61 | } 62 | //logger.Printf("Error: %s\n", err.Error()) 63 | } 64 | case <-sigchan: 65 | return 66 | } 67 | } 68 | }() 69 | 70 | } 71 | 72 | func (c *Consumer) Close(){ 73 | c.consumer.Close() 74 | } 75 | 76 | type ConsumerCallbacks struct { 77 | OnDataReceived func(msg []byte) 78 | OnError func(err error) 79 | } 80 | -------------------------------------------------------------------------------- /messages/producer.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "github.com/Shopify/sarama" 5 | "log" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "io/ioutil" 9 | "time" 10 | "encoding/json" 11 | ) 12 | 13 | type Producer struct { 14 | asyncProducer sarama.AsyncProducer 15 | callbacks ProducerCallbacks 16 | topic string 17 | } 18 | 19 | func NewProducer(callbacks ProducerCallbacks,brokerList []string,topic string,certFile *string,keyFile *string,caFile *string,verifySsl *bool ) *Producer { 20 | producer := Producer{ callbacks: callbacks, topic: topic} 21 | 22 | config := sarama.NewConfig() 23 | tlsConfig := createTlsConfiguration(certFile,keyFile,caFile,verifySsl) 24 | if tlsConfig != nil { 25 | config.Net.TLS.Enable = true 26 | config.Net.TLS.Config = tlsConfig 27 | } 28 | config.Producer.RequiredAcks = sarama.WaitForLocal // Only wait for the leader to ack 29 | config.Producer.Compression = sarama.CompressionSnappy // Compress messages 30 | config.Producer.Flush.Frequency = 500 * time.Millisecond // Flush batches every 500ms 31 | 32 | saramaProducer, err := sarama.NewAsyncProducer(brokerList, config) 33 | if err != nil { 34 | log.Fatalln("Failed to start Sarama producer:", err) 35 | panic(err) 36 | } 37 | go func() { 38 | for err := range saramaProducer.Errors() { 39 | if producer.callbacks.OnError!=nil { 40 | producer.callbacks.OnError(err) 41 | } 42 | } 43 | }() 44 | producer.asyncProducer = saramaProducer 45 | return &producer 46 | } 47 | 48 | func createTlsConfiguration(certFile *string,keyFile *string,caFile *string,verifySsl *bool)(t *tls.Config) { 49 | if certFile!=nil && keyFile!=nil && caFile!=nil && *certFile != "" && *keyFile != "" && *caFile != "" { 50 | cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | caCert, err := ioutil.ReadFile(*caFile) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | 60 | caCertPool := x509.NewCertPool() 61 | caCertPool.AppendCertsFromPEM(caCert) 62 | 63 | t = &tls.Config{ 64 | Certificates: []tls.Certificate{cert}, 65 | RootCAs: caCertPool, 66 | InsecureSkipVerify: *verifySsl, 67 | } 68 | } 69 | // will be nil by default if nothing is provided 70 | return t 71 | } 72 | 73 | func (p *Producer) Produce(payload interface{}) { 74 | value := message{ 75 | value: payload, 76 | } 77 | value.ensureEncoded() 78 | log.Println("producing: ", string(value.encoded)) 79 | p.asyncProducer.Input() <- &sarama.ProducerMessage{ 80 | Topic: p.topic, 81 | //Key: sarama.StringEncoder(r.RemoteAddr), 82 | Value: &value, 83 | } 84 | } 85 | 86 | type message struct { 87 | value interface{} 88 | encoded []byte 89 | err error 90 | } 91 | 92 | func (ale *message) ensureEncoded() { 93 | if ale.encoded == nil && ale.err == nil { 94 | ale.encoded, ale.err = json.Marshal(ale.value) 95 | if ale.err!=nil { 96 | log.Println(ale.err) 97 | } 98 | } 99 | } 100 | 101 | func (ale *message) Length() int { 102 | ale.ensureEncoded() 103 | return len(ale.encoded) 104 | } 105 | 106 | func (ale *message) Encode() ([]byte, error) { 107 | ale.ensureEncoded() 108 | return ale.encoded, ale.err 109 | } 110 | 111 | func (p *Producer) Close() error{ 112 | log.Println("Producer.Close()") 113 | if err := p.asyncProducer.Close(); err != nil { 114 | return err 115 | } 116 | return nil 117 | } 118 | 119 | type ProducerCallbacks struct { 120 | OnError func(error) 121 | } 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /models/device_request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type DeviceRequest struct { 4 | Action string `json:"action"` 5 | DeviceUid string `json:"deviceUid"` 6 | Uid string `json:"uid"` 7 | Data map[string]interface{} `json:"data"` 8 | } 9 | -------------------------------------------------------------------------------- /models/device_response.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type DeviceResponse struct { 4 | Action string `json:"action"` 5 | Uid string `json:"uid"` 6 | Data map[string]interface{} `json:"data"` 7 | Status string `json:"status"` 8 | ErrorCode string `json:"errorCode"` 9 | ErrorMessage string `json:"errorMessage"` 10 | } 11 | -------------------------------------------------------------------------------- /models/server_request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ServerRequest struct{ 4 | DeviceRequest DeviceRequest `json:"deviceRequest"` 5 | ServerId string `json:"serverId"` // unique identifier for each server in case of having more than 1 server 6 | ClientId string `json:"clientId"` 7 | } 8 | -------------------------------------------------------------------------------- /models/server_response.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ServerResponse struct { 4 | DeviceResponse DeviceResponse `json:"deviceResponse"` 5 | ServerId string `json:"serverId"` // unique identifier for each server in case of having more than 1 server 6 | ClientId string `json:"clientId"` 7 | DeviceUid string `json:"deviceUid"` 8 | } --------------------------------------------------------------------------------