Search code examples
gosocket.iogrpc

Starting a socket server interferes with the gRPC/http client server communication Golang


I have a server that is heavily based on this tutorial. It works fine after the additional changes I applied to it. But now that I'm trying to add socket.io to it, it now is having some issue. Upon testing the code for socket.io that I added, it seems like it's affecting the mechanism that makes the client code(endpoints) call the server side code(db query, processing). The call reaches the client side of the server as the log for the an endpoint call appears on the terminal, but it doesn't seem to be calling the server side.

Here's the code for the socket server:

package helpers

import (
    "fmt"
    "net/http"

    socketio "github.com/googollee/go-socket.io"
    "github.com/googollee/go-socket.io/engineio"
    "github.com/googollee/go-socket.io/engineio/transport"
    "github.com/googollee/go-socket.io/engineio/transport/polling"
    "github.com/googollee/go-socket.io/engineio/transport/websocket"
)

var allowOriginFunc = func(r *http.Request) bool {
    return true
}

func StartSocket() {
    server := socketio.NewServer(&engineio.Options{
        Transports: []transport.Transport{
            &polling.Transport{
                CheckOrigin: allowOriginFunc,
            },
            &websocket.Transport{
                CheckOrigin: allowOriginFunc,
            },
        },
    })

    server.OnConnect("/", func(s socketio.Conn) error {
        s.SetContext("")
        fmt.Println("connected:", s.ID())
        return nil
    })

    server.OnEvent("/", "notice", func(s socketio.Conn, msg string) {
        fmt.Println("notice:", msg)
        s.Emit("reply", "have "+msg)
    })

    server.OnError("/", func(s socketio.Conn, e error) {
        fmt.Println("socket error:", e)
    })

    server.OnDisconnect("/", func(s socketio.Conn, reason string) {
        fmt.Println("closed", reason)
    })

    go server.Serve()
    defer server.Close()

    http.Handle("/socket.io/", server)
    http.Handle("/", http.FileServer(http.Dir("./asset")))

    fmt.Println("Socket server serving at localhost:8000...")
    fmt.Print(http.ListenAndServe(":8000", nil))
}

// main.go server side

package main

import (
    "flag"
    "fmt"
    "log"
    "net"

    pb "github.com/<me>/<project_name>/api/proto/out"
    "github.com/<me>/<project_name>/cmd/server/handlers"
    "github.com/<me>/<project_name>/cmd/server/helpers"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func init() {
    helpers.DatabaseConnection()
}

var (
    tls      = flag.Bool("tls", true, "Connection uses TLS if true, else plain TCP")
    certFile = flag.String("cert_file", "", "The TLS cert file")
    keyFile  = flag.String("key_file", "", "The TLS key file")
    port     = flag.Int("port", 50051, "The server port")
)

func main() {
    flag.Parse()

    // helpers.StartSocket()

    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    var opts []grpc.ServerOption
    if *tls {
        if *certFile == "" {
            *certFile = "service.pem"
        }

        if *keyFile == "" {
            *keyFile = "service.key"
        }

        creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)

        if err != nil {
            log.Fatalf("Failed to generate credentials: %v", err)
        }

        opts = []grpc.ServerOption{grpc.Creds(creds)}
    }

    mServ := grpc.NewServer(opts...)

    fmt.Println("gRPC server running ...")

    pb.RegisterSomethingServiceServer(mServ, &handlers.SomethingServer{})

    log.Printf("Server listening at %v", lis.Addr())
    
    if err := mServ.Serve(lis); err != nil {
        log.Fatalf("failed to serve : %v", err)
    }
}

// main.go client-side

package main

import (
    "bytes"
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "sort"
    "strings"

    "github.com/<me>/<project_name>/cmd/client/handlers"

    "github.com/gin-gonic/gin"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
)

func main() {
    flag.Parse()

    creds, err := credentials.NewClientTLSFromFile("service.pem", "")

    if err != nil {
        log.Fatalf("could not process the credentials: %v", err)
    }

    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds))

    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }

    defer conn.Close()

    var ginGine = gin.Default()
    StartClient(conn, ginGine)
}

func StartClient(conn *grpc.ClientConn, ginGine *gin.Engine) {

    handlers.SomethingApiHandler(conn, ginGine)

    ginGine.Run(":5000")
}

For the full client and server code, you can check the tutorial I linked above. If I don't call StartSocket(), everything works fine.

When calling the an endpoint of my API, I get this error, and it's throw by the code calling the server side code:

"rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp [::1]:50051: connectex: No connection could be made because the target machine actively refused it.""

Here's what the code looks like:

ginGine.POST("/account/login", func(ctx *gin.Context) {
    var account models.Account

    err := ctx.ShouldBind(&account)
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error1": err.Error(),
        })
        return
    }

    res, err := srvClient.service.ValidateAccount(ctx, &pb.ValidateAccountRequest{
        Account: &pb.Account{
            Id:        account.ID,
            FirstName: account.FirstName,
            LastName:  account.LastName,
            UserName:  account.UserName,
            Password: account.Password,
        },
    })

    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "error2": err.Error(),
        })
        return
    }

    ctx.JSON(http.StatusOK, gin.H{
        "status":       res.Status,
        "access_token": res.AccessToken,
    })
})

error2 is what gets returned on the API call


Solution

  • This line of code is blocking and function StartSocket() never returns:

    fmt.Print(http.ListenAndServe(":8000", nil))

    To test, you can add log after it and that log message will be not printed.

    You need to run StartSocket() in a separate not blocking goroutine.

    P.S. In example, that line is log.Fatal(http.ListenAndServe(":8000", nil)). Your code swallows an error.