leslie-fei / gnet

🚀 gnet is a high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go./ gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。
https://gnet.host
Apache License 2.0
0 stars 1 forks source link

[Question]: how to use latest gnet tls with this code? #3

Closed kolinfluence closed 6 months ago

kolinfluence commented 6 months ago

Actions I've taken before I'm here

Questions with details

@leslie-fei

can u help run this code and provide the "debug" solution? i'm updating my repo to use tls. hope panjf can pull it soon.

i will help create examples for use with this tls

my original old code not sure how to port it anymore.

it runs but will crash when http request is suggested. possible to provide the solution fix to this?

package main

import (
        "bytes"
        "flag"
        "fmt"
        "log"
        "net"
        "net/netip"
        "sync/atomic"
        "time"

        cxstrconv "github.com/cloudxaas/gostrconv"
        "github.com/panjf2000/gnet/v2"
        "github.com/valyala/bytebufferpool"
        "github.com/leslie-fei/gnettls"
        "github.com/leslie-fei/gnettls/tls"

)

type httpServer struct {
        gnet.BuiltinEventEngine
        addr      string
        multicore bool
        eng       gnet.Engine
}

type httpCodec struct {
        buf           *bytebufferpool.ByteBuffer // Main buffer reused for all I/O operations
        headersBuffer []byte                     // Buffer containing all headers as raw bytes
        method        []byte                     // Method as a slice of headersBuffer
        uri           []byte                     // URI as a slice of headersBuffer
        host          []byte                     // Host as a slice of headersBuffer
}

var (
        responseHeader = []byte("HTTP/1.1 200 OK\r\nServer: gnet\r\nContent-Type: text/plain\r\nDate: ")
        now            atomic.Value
        bufferPool     bytebufferpool.Pool
)

func updateCurrentTime() {
        now.Store(time.Now().Format(time.RFC1123))
}

func (hc *httpCodec) appendResponse(body []byte) {
        updateCurrentTime() // Update time only when responding
        hc.buf.Reset()
        hc.buf.Write(responseHeader)
        hc.buf.WriteString(now.Load().(string))
        hc.buf.WriteString("\r\nContent-Length: ")
        hc.buf.WriteString(cxstrconv.Inttoa(len(body)))
        hc.buf.WriteString("\r\n\r\n")
        hc.buf.Write(body)
}

func (hc *httpCodec) parse(data []byte) error {
        idx := bytes.Index(data, []byte("\r\n\r\n"))
        if idx == -1 {
                return fmt.Errorf("incomplete headers")
        }

        hc.headersBuffer = data[:idx] // Store only header part
        lines := bytes.Split(hc.headersBuffer, []byte("\r\n"))
        if len(lines) < 1 {
                return fmt.Errorf("invalid request")
        }

        requestLine := bytes.SplitN(lines[0], []byte(" "), 3)
        if len(requestLine) < 3 {
                return fmt.Errorf("invalid request line")
        }
        hc.method, hc.uri = requestLine[0], requestLine[1]

        for _, line := range lines[1:] {
                if bytes.HasPrefix(line, []byte("Host: ")) {
                        hc.host = line[6:]
                }
        }
        return nil
}

func route(hc *httpCodec) {
        var response []byte
        if bytes.Equal(hc.method, []byte("GET")) {
                switch {
                case bytes.Equal(hc.uri, []byte("/hello")):
                        response = []byte("Hello, World!")
                case bytes.Equal(hc.uri, []byte("/time")):
                        response = []byte("Current Time: " + now.Load().(string))
                default:
                        response = []byte("404 Not Found")
                }
        } else {
                response = []byte("405 Method Not Allowed")
        }
        hc.appendResponse(response)
}

func (hs *httpServer) OnBoot(eng gnet.Engine) gnet.Action {
        hs.eng = eng
        log.Printf("HTTP server with multi-core=%t is listening on %s\n", hs.multicore, hs.addr)
        return gnet.None
}

func (hs *httpServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
        buf := bufferPool.Get()
        c.SetContext(&httpCodec{buf: buf})
        return nil, gnet.None
}

func (hs *httpServer) OnTraffic(c gnet.Conn) gnet.Action {
        //addr := c.RemoteAddr()
        // Cast net.Addr to net.TCPAddr to access the IP directly
        tcpAddr, ok := c.RemoteAddr().(*net.TCPAddr)
        if !ok {
                //log.Printf("failed to cast remote address to TCPAddr")
                return gnet.Close
        }

        //ipAddr := tcpAddr.IP
        // Convert net.IP to netip.Addr (requires Go 1.18+ for netip)
        ipNetip, ok := netip.AddrFromSlice(tcpAddr.IP)
        if !ok {
                //log.Printf("could not parse IP address: %s", err)
                return gnet.Close
        }
        if ipNetip.Is4In6() {
                ipNetip = ipNetip.Unmap()
        }

        // Use ipNetip.AsSlice to get []byte representation of the IP address
        //_ = ipNetip.AsSlice()
        //log.Printf("ip Add = %s", ipNetip.String())

        buf, _ := c.Next(-1)

        hc := c.Context().(*httpCodec)

        err := hc.parse(buf)
        if err != nil {
                log.Printf("Error parsing request from %s: %v", c.RemoteAddr().String(), err)
                return gnet.Close
        }

        route(hc)
        c.Write(hc.buf.B)
        return gnet.None
}

func (hs *httpServer) OnClose(c gnet.Conn, err error) gnet.Action {
        hc := c.Context().(*httpCodec)
        bufferPool.Put(hc.buf)
        return gnet.None
}

func mustLoadCertificate() tls.Certificate {
        cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
        if err != nil {
                log.Fatalf("Failed to load server certificate: %v", err)
        }
        return cert
}

func main() {
        var port int
        var multicore bool

        flag.IntVar(&port, "port", 8080, "server port")
        flag.BoolVar(&multicore, "multicore", true, "use multicore")
        flag.Parse()

        tlsConfig := &tls.Config{
                Certificates: []tls.Certificate{mustLoadCertificate()},
        }

        addr := fmt.Sprintf("tcp://:%d", port)
        hs := &httpServer{addr: addr, multicore: multicore}

        options := []gnet.Option{
                gnet.WithMulticore(multicore),
                gnet.WithTCPKeepAlive(time.Minute * 5),
                gnet.WithReusePort(true),
        }

        //log.Println("server exits:", gnettls.Run(hs, addr, gnet.WithReusePort(true), gnet.WithMulticore(multicore)))
        log.Fatal(gnettls.Run(hs, hs.addr, tlsConfig, options...))

}

Code snippets (optional)

No response

kolinfluence commented 6 months ago

got it working this way, comments / feedback appreciated:

package main

import (
    "bytes"
    "flag"
    "fmt"
    "log"
    "sync/atomic"
    "time"

    cxstrconv "github.com/cloudxaas/gostrconv"
    "github.com/panjf2000/gnet/v2"
    "github.com/valyala/bytebufferpool"
    "github.com/leslie-fei/gnettls"
    "github.com/leslie-fei/gnettls/tls"

)

type httpServer struct {
    gnet.BuiltinEventEngine
    addr      string
    multicore bool
    eng       gnet.Engine
}

type httpCodec struct {
    buf           *bytebufferpool.ByteBuffer // Main buffer reused for all I/O operations
    headersBuffer []byte                     // Buffer containing all headers as raw bytes
    method        []byte                     // Method as a slice of headersBuffer
    uri           []byte                     // URI as a slice of headersBuffer
    host          []byte                     // Host as a slice of headersBuffer
}

var (
    responseHeader = []byte("HTTP/1.1 200 OK\r\nServer: gnet\r\nContent-Type: text/plain\r\nDate: ")
    now            atomic.Value
    bufferPool     bytebufferpool.Pool
)

func updateCurrentTime() {
    now.Store(time.Now().Format(time.RFC1123))
}

func (hc *httpCodec) appendResponse(body []byte) {
    updateCurrentTime() // Update time only when responding
    hc.buf.Reset()
    hc.buf.Write(responseHeader)
    hc.buf.WriteString(now.Load().(string))
    hc.buf.WriteString("\r\nContent-Length: ")
    hc.buf.WriteString(cxstrconv.Inttoa(len(body)))
    hc.buf.WriteString("\r\n\r\n")
    hc.buf.Write(body)
}

func (hc *httpCodec) parse(data []byte) error {
    idx := bytes.Index(data, []byte("\r\n\r\n"))
    if idx == -1 {
        return fmt.Errorf("incomplete headers")
    }

    hc.headersBuffer = data[:idx] // Store only header part
    lines := bytes.Split(hc.headersBuffer, []byte("\r\n"))
    if len(lines) < 1 {
        return fmt.Errorf("invalid request")
    }

    requestLine := bytes.SplitN(lines[0], []byte(" "), 3)
    if len(requestLine) < 3 {
        return fmt.Errorf("invalid request line")
    }
    hc.method, hc.uri = requestLine[0], requestLine[1]

    for _, line := range lines[1:] {
        if bytes.HasPrefix(line, []byte("Host: ")) {
            hc.host = line[6:]
        }
    }
    return nil
}

func route(hc *httpCodec) {
    var response []byte
    if bytes.Equal(hc.method, []byte("GET")) {
        switch {
        case bytes.Equal(hc.uri, []byte("/hello")):
            response = []byte("Hello, World!")
        case bytes.Equal(hc.uri, []byte("/time")):
            response = []byte("Current Time: " + now.Load().(string))
        default:
            response = []byte("404 Not Found")
        }
    } else {
        response = []byte("405 Method Not Allowed")
    }
    hc.appendResponse(response)
}

func (hs *httpServer) OnBoot(eng gnet.Engine) gnet.Action {
    hs.eng = eng
    log.Printf("HTTP server with multi-core=%t is listening on %s\n", hs.multicore, hs.addr)
    return gnet.None
}

func (hs *httpServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
    buf := bufferPool.Get()
    c.SetContext(&httpCodec{buf: buf})
    return nil, gnet.None
}

func (hs *httpServer) OnClose(c gnet.Conn, err error) gnet.Action {
    if hc, ok := c.Context().(*httpCodec); ok {
        bufferPool.Put(hc.buf)
    } else {
        // Adjusted to not log unexpected context type to handle mixed connection types gracefully
    }
    return gnet.None
}

func (hs *httpServer) OnTraffic(c gnet.Conn) gnet.Action {
    hc, ok := c.Context().(*httpCodec)
    if !ok {
        log.Printf("DDSDASD")
        return gnet.Close // Silently handle the mismatch, assuming context may be overridden by TLS
    }

    buf, _ := c.Next(-1)
    if err := hc.parse(buf); err != nil {
        log.Printf("Error parsing request from %s: %v", c.RemoteAddr().String(), err)
        return gnet.Close
    }
    route(hc)
    c.Write(hc.buf.B)
    return gnet.None
}

func mustLoadCertificate() tls.Certificate {
    cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        log.Fatalf("Failed to load server certificate: %v", err)
    }
    return cert
}

func main() {

    var port int
    var multicore bool

    flag.IntVar(&port, "port", 8080, "server port")
    flag.BoolVar(&multicore, "multicore", true, "use multicore")
    flag.Parse()

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{mustLoadCertificate()},
    }

    addr := fmt.Sprintf("tcp://:%d", port)
    hs := &httpServer{addr: addr, multicore: multicore}

    options := []gnet.Option{
        gnet.WithMulticore(multicore),
        gnet.WithTCPKeepAlive(time.Minute * 5),
        gnet.WithReusePort(true),
    }

    log.Fatal(gnettls.Run(hs, hs.addr, tlsConfig, options...))
}