initial commit

This commit is contained in:
overflowerror 2021-11-14 12:17:47 +01:00
commit 6257896d1a
12 changed files with 307 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

9
.idea/iot-relay.iml Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

24
cmd/main.go Normal file
View file

@ -0,0 +1,24 @@
package main
import (
. "iot-relay/internal/client"
. "iot-relay/internal/config"
. "iot-relay/internal/server"
"log"
)
const configFile = "config.json"
func main() {
log.Println("reading config file")
config, err := ReadConfig(configFile)
if err != nil {
log.Fatalln(err)
}
log.Println("starting server")
err = Listen(config, GetHandler(config))
if err != nil {
log.Fatalln(err)
}
}

12
config.json Normal file
View file

@ -0,0 +1,12 @@
{
"server": {
"bind": "0.0.0.0:20159",
"timeout": 30
},
"client": {
"address": "http://localhost:8086",
"db": "grafana",
"measurement": "iot",
"host": "iot-relay"
}
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module iot-relay
go 1.17

58
internal/client/client.go Normal file
View file

@ -0,0 +1,58 @@
package client
import (
"fmt"
"iot-relay/internal/config"
"iot-relay/internal/types"
"log"
"net/http"
"strconv"
"strings"
"time"
)
func lineProtocol(request types.Request, config config.Config) string {
var builder strings.Builder
builder.WriteString(config.Client.Measurement)
builder.WriteString(",host=")
builder.WriteString(config.Client.Host)
builder.WriteString(",ip=")
builder.WriteString(request.IP)
builder.WriteString(",id=")
builder.WriteString(request.ID)
builder.WriteString(",loc=")
builder.WriteString(request.Location)
builder.WriteString(" ")
for key, value := range request.Data {
builder.WriteString(key)
builder.WriteString("=")
builder.WriteString(value)
builder.WriteString(" ")
}
builder.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
return builder.String()
}
func GetHandler(config config.Config) types.Callback {
return func(request types.Request) error {
line := lineProtocol(request, config)
url := config.Client.Address + "/write?db=" + config.Client.DB
resp, err := http.Post(url, "application/octet-stream", strings.NewReader(line))
if err != nil {
log.Println(err)
return err
}
if resp.StatusCode != 204 {
err = fmt.Errorf("influxdb responded with %d", resp.StatusCode)
log.Println(err)
return err
}
return nil
}
}

41
internal/config/config.go Normal file
View file

@ -0,0 +1,41 @@
package config
import (
"encoding/json"
"io/ioutil"
"os"
)
type Config struct {
Server struct {
Bind string `json:"bind"`
Timeout int `json:"timeout"` // in seconds
} `json:"server"`
Client struct {
Address string `json:"address"`
DB string `json:"db"`
Measurement string `json:"measurement"`
Host string `json:"host"`
} `json:"client"`
}
func ReadConfig(filename string) (Config, error) {
file, err := os.Open(filename)
if err != nil {
return Config{}, err
}
content, err := ioutil.ReadAll(file)
if err != nil {
return Config{}, err
}
var config Config
err = json.Unmarshal(content, &config)
if err != nil {
return Config{}, err
}
return config, nil
}

View file

@ -0,0 +1,57 @@
package server
import (
"bufio"
"iot-relay/internal/types"
"log"
"net"
"strings"
)
func connectionHandler(conn net.Conn) {
reader := bufio.NewReader(conn)
defer func() {
log.Println("closing connection")
_ = conn.Close()
}()
var request types.Request
request.Data = make(map[string]string)
request.IP = strings.Split(conn.RemoteAddr().String(), ":")[0]
for {
line, err := reader.ReadString('\n')
if err != nil {
log.Printf("connection error: %v", err)
break
}
if len(line) == 1 {
// end of request
if !request.IsValid() {
log.Println("protocol error")
_, _ = conn.Write([]byte("bad\n"))
break
}
err = callback(request)
if err != nil {
// should be logged by callback
_, _ = conn.Write([]byte("fail\n"))
} else {
_, _ = conn.Write([]byte("ok\n"))
}
break
} else {
err = parseLine(line, &request)
if err != nil {
log.Printf("parsing error: %v", err)
_, _ = conn.Write([]byte("bad\n"))
break
}
}
}
}

38
internal/server/parser.go Normal file
View file

@ -0,0 +1,38 @@
package server
import (
"errors"
"iot-relay/internal/types"
"strings"
)
var forbiddenKeys = []string{
"ip",
}
func parseLine(line string, request *types.Request) error {
token := strings.Split(line, "=")
if len(token) != 2 {
return errors.New("malformed; missing =")
}
key := strings.ToLower(token[0])
value := strings.TrimRight(token[1], "\n")
for _, forbidden := range forbiddenKeys {
if forbidden == key {
return errors.New("forbidden key")
}
}
switch key {
case "id":
request.ID = value
case "loc":
request.Location = value
default:
request.Data[key] = value
}
return nil
}

32
internal/server/server.go Normal file
View file

@ -0,0 +1,32 @@
package server
import (
"iot-relay/internal/config"
"iot-relay/internal/types"
"log"
"net"
"time"
)
var callback types.Callback
func Listen(config config.Config, _callback types.Callback) error {
callback = _callback
listener, err := net.Listen("tcp", config.Server.Bind)
if err != nil {
return err
}
for {
connection, err := listener.Accept()
if err != nil {
log.Println(err)
} else {
log.Printf("connection from %v", connection.RemoteAddr())
_ = connection.SetReadDeadline(time.Now().Add(time.Second * time.Duration(config.Server.Timeout)))
go connectionHandler(connection)
}
}
}

View file

@ -0,0 +1,3 @@
package types
type Callback func(request Request) error

22
internal/types/request.go Normal file
View file

@ -0,0 +1,22 @@
package types
type Request struct {
ID string
Location string
IP string
Data map[string]string
}
func (r Request) IsValid() bool {
if len(r.IP) == 0 {
return false
}
if len(r.ID) == 0 {
return false
}
if len(r.Location) == 0 {
return false
}
return true
}