chore: move files
This commit is contained in:
@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
const (
|
|
||||||
H_STATUS = "status"
|
|
||||||
H_DATA_FORMAT = "data-format"
|
|
||||||
H_ROUTE = "route"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DF_BYTES = "bytes"
|
|
||||||
DF_TEXT = "text"
|
|
||||||
DF_JSON = "json"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
E_UTF8 = "utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
STATUS_SUCCESS = 0
|
|
||||||
STATUS_NOTFOUND = 69
|
|
||||||
STATUS_INTERNALERR = 129
|
|
||||||
)
|
|
||||||
|
|
||||||
var DATA_FORMATS map[string]string = map[string]string{
|
|
||||||
"bytes": DF_BYTES,
|
|
||||||
"text": DF_TEXT,
|
|
||||||
"json": DF_JSON,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ENCODINGS map[string]string = map[string]string{
|
|
||||||
"utf-8": E_UTF8,
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataFormat struct {
|
|
||||||
Format string
|
|
||||||
Encoding string
|
|
||||||
}
|
|
||||||
|
|
188
server/packet.go
188
server/packet.go
@ -1,188 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Magic uint32 = 0xDEADBEEF
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PacketVersion int = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawPacket struct {
|
|
||||||
Magic uint32
|
|
||||||
Version uint8
|
|
||||||
Flags uint8
|
|
||||||
HeaderSize uint16
|
|
||||||
PayloadSize uint32
|
|
||||||
Header []byte
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type Packet struct {
|
|
||||||
Version int
|
|
||||||
Flags int
|
|
||||||
Headers map[string]string
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketDuplex struct {
|
|
||||||
conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildPacket(headers map[string]string, payload []byte) *Packet {
|
|
||||||
return &Packet{
|
|
||||||
Version: PacketVersion,
|
|
||||||
Flags: 0, // TODO:
|
|
||||||
Headers: headers,
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHeaders(rawHeaders []byte, headers *map[string]string) error {
|
|
||||||
i := 0
|
|
||||||
for i < len(rawHeaders) {
|
|
||||||
if rawHeaders[i] == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var key string
|
|
||||||
for rawHeaders[i] != ':' {
|
|
||||||
if rawHeaders[i] != ' ' {
|
|
||||||
key += string(rawHeaders[i])
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
var value string
|
|
||||||
for rawHeaders[i] != '\n' {
|
|
||||||
if rawHeaders[i] != ' ' {
|
|
||||||
value += string(rawHeaders[i])
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
(*headers)[key] = value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SerializeHeaders(headers *map[string]string) []byte {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
for k, v := range(*headers) {
|
|
||||||
fmt.Fprintf(buf, "%s:%s\n", k, v)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(buf, "\n")
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketDuplex(conn net.Conn) *PacketDuplex {
|
|
||||||
return &PacketDuplex{
|
|
||||||
conn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PacketDuplex) ReadPacket() (*Packet, error) {
|
|
||||||
rpkt := &RawPacket{}
|
|
||||||
|
|
||||||
err := binary.Read(r.conn, binary.BigEndian, &rpkt.Magic)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rpkt.Magic != Magic {
|
|
||||||
return nil, errors.New("Magic bytes are invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(r.conn, binary.BigEndian, &rpkt.Version)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(r.conn, binary.BigEndian, &rpkt.Flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(r.conn, binary.BigEndian, &rpkt.HeaderSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(r.conn, binary.BigEndian, &rpkt.PayloadSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rpkt.Header = make([]byte, rpkt.HeaderSize)
|
|
||||||
if _, err := io.ReadFull(r.conn, rpkt.Header); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rpkt.Payload = make([]byte, rpkt.PayloadSize)
|
|
||||||
if _, err := io.ReadFull(r.conn, rpkt.Payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt := &Packet{
|
|
||||||
Version: int(rpkt.Version),
|
|
||||||
Flags: int(rpkt.Flags),
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
Payload: rpkt.Payload,
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseHeaders(rpkt.Header, &pkt.Headers)
|
|
||||||
|
|
||||||
return pkt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *PacketDuplex) WritePacket(packet *Packet) (int, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, Magic); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write magic into packet: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, uint8(packet.Version)); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write version into packet: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, uint8(packet.Flags)); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write flags into packet: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
rawHeaders := SerializeHeaders(&packet.Headers)
|
|
||||||
headerSize := len(rawHeaders)
|
|
||||||
payloadSize := len(packet.Payload)
|
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, uint16(headerSize)); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write header size into packet: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, uint32(payloadSize)); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write payload size into packet: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := buf.Write(rawHeaders[:headerSize]); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write raw headers: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := buf.Write(packet.Payload[:payloadSize]); err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to write payload: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := r.conn.Write(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.New(fmt.Sprintf("Failed to send packet over connection: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
conn net.Conn
|
|
||||||
packet *Packet
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(conn net.Conn, packet *Packet) *Request {
|
|
||||||
return &Request{
|
|
||||||
conn, packet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *Request) Conn() net.Conn {
|
|
||||||
return req.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *Request) GetHeader(key string) (string, bool) {
|
|
||||||
value, ok := req.packet.Headers[key]
|
|
||||||
return value, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *Request) GetDataFormat() (*DataFormat, error) {
|
|
||||||
// TODO: use predefined header names
|
|
||||||
format, ok := req.packet.Headers["data-format"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("Data format header is not provided in request")
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(format, ":")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
if format == "bytes" {
|
|
||||||
return &DataFormat{
|
|
||||||
Format: DF_BYTES,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("Invalid data format header")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, ok := DATA_FORMATS[parts[0]]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New(fmt.Sprintf("Unknown data format: %s", parts[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
encoding, ok := ENCODINGS[parts[1]]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New(fmt.Sprintf("Unknown payload encoding: %s", parts[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DataFormat{
|
|
||||||
Format: f,
|
|
||||||
Encoding: encoding,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *Request) ExtractText() (string, error) {
|
|
||||||
df, err := req.GetDataFormat()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !slices.Contains([]string{DF_TEXT, DF_JSON}, df.Format) {
|
|
||||||
return "", errors.New(fmt.Sprintf("Data format '%s' cannot be extracted as text", df.Format))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(req.packet.Payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *Request) ExtractBytes() ([]byte, error) {
|
|
||||||
df, err := req.GetDataFormat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if df.Format != "bytes" {
|
|
||||||
return nil, errors.New(fmt.Sprintf("Data format '%s' is invalid for extracting bytes", df.Format))
|
|
||||||
}
|
|
||||||
|
|
||||||
return req.packet.Payload, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"maps"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
StatusCode int
|
|
||||||
Format DataFormat
|
|
||||||
Headers map[string]string
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStatusResponse(status int) *Response {
|
|
||||||
return &Response{
|
|
||||||
StatusCode: status,
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
Format: DataFormat{
|
|
||||||
Format: DF_BYTES,
|
|
||||||
Encoding: "",
|
|
||||||
},
|
|
||||||
Payload: make([]byte, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTextResponse(text string) *Response {
|
|
||||||
return &Response{
|
|
||||||
StatusCode: STATUS_SUCCESS,
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
Format: DataFormat{
|
|
||||||
Format: DF_TEXT,
|
|
||||||
Encoding: E_UTF8,
|
|
||||||
},
|
|
||||||
Payload: []byte(text),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJsonResponse(data map[string]string) (*Response, error) {
|
|
||||||
jsonBytes, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Response{
|
|
||||||
StatusCode: STATUS_SUCCESS,
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
Format: DataFormat{
|
|
||||||
Format: DF_JSON,
|
|
||||||
Encoding: E_UTF8,
|
|
||||||
},
|
|
||||||
Payload: jsonBytes,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res *Response) ToPacket() *Packet {
|
|
||||||
headers := make(map[string]string)
|
|
||||||
|
|
||||||
maps.Copy(headers, res.Headers)
|
|
||||||
|
|
||||||
headers[H_DATA_FORMAT] = fmt.Sprintf("%s:%s", res.Format.Format, res.Format.Encoding)
|
|
||||||
headers[H_STATUS] = strconv.Itoa(res.StatusCode)
|
|
||||||
|
|
||||||
return BuildPacket(headers, res.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res *Response) AddHeader(key, value string) {
|
|
||||||
if _, ok := res.Headers[key]; ok {
|
|
||||||
log.Printf("WARN: Rewriting already existing header: '%s'\n", key)
|
|
||||||
}
|
|
||||||
res.Headers[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res *Response) Write(p []byte) (int, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
n, err := buf.Write(p)
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Payload = buf.Bytes()
|
|
||||||
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RouteHandler func(req *Request) *Response
|
|
||||||
|
|
||||||
type Router struct {
|
|
||||||
Routes map[string]RouteHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouter() *Router {
|
|
||||||
return &Router{
|
|
||||||
Routes: make(map[string]RouteHandler),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) AddRoute(pathname string, handler RouteHandler) {
|
|
||||||
if _, ok := r.Routes[pathname]; ok {
|
|
||||||
log.Printf("WARN: Rewriting existing route '%s'\n", pathname)
|
|
||||||
}
|
|
||||||
r.Routes[pathname] = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Handle(conn net.Conn) error {
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
log.Printf("Got new connection from %s\n", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
dupl := NewPacketDuplex(conn)
|
|
||||||
|
|
||||||
// TODO: Ability to keep connection alive
|
|
||||||
packet, err := dupl.ReadPacket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if route, ok := packet.Headers["route"]; ok {
|
|
||||||
log.Printf("[ROUTER] New connection to '%s'", route)
|
|
||||||
if handler, ok := r.Routes[route]; ok {
|
|
||||||
req := NewRequest(conn, packet)
|
|
||||||
res := handler(req)
|
|
||||||
_, err := dupl.WritePacket(res.ToPacket())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("Not Found")
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Addr string
|
|
||||||
Running bool
|
|
||||||
ConnChan chan net.Conn
|
|
||||||
listener net.Listener
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(addr string) *Server {
|
|
||||||
return &Server{
|
|
||||||
Addr: addr,
|
|
||||||
Running: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) SetListener(ln chan net.Conn) {
|
|
||||||
s.ConnChan = ln
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
ln, err := net.Listen("tcp", ":3000")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.listener = ln
|
|
||||||
s.Running = true
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
for s.IsRunning() {
|
|
||||||
log.Println("DEBUG:", "Waiting for new connection to accept")
|
|
||||||
conn, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ConnChan != nil {
|
|
||||||
s.ConnChan <- conn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("DEBUG:", "Finished listening for connections")
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.Running = false
|
|
||||||
s.listener = nil
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
s.Running = false
|
|
||||||
if s.listener != nil {
|
|
||||||
return s.listener.Close()
|
|
||||||
}
|
|
||||||
if s.ConnChan != nil {
|
|
||||||
close(s.ConnChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) IsRunning() bool {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return s.Running
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user