I am working on a Go project which use gRPC as server and Voltdb as DB. I have an issue when sending many DB read/write requests to gRPC server. Request has been handled but it does not release DB connection after finishing operator. So when connection in pool run out I get this error:
Rejected connection from /127.0.0.1:63015 because the connection limit of 9940 has been reached
proto file
syntax = "proto3";
package grpc;
import "google/protobuf/struct.proto";
option java_multiple_files = true;
option java_package = "com.io.test";
service TestService {
rpc updateUser (UserRequest) returns (Status) {};
}
message UserRequest {
string userId = 1;
string name = 2;
}
message Status {
string code = 1;
}
Grpc implement in Go
package service
import (
"context"
"log"
api "testgrpc/pkg/api/grpc"
"testgrpc/pkg/db"
"testgrpc/pkg/model"
)
var voltDbClient *db.VoltDBClient
func init() {
voltDbClient = db.NewVoltDBClient()
}
type GrpcService struct {
}
func NewGrpcService() *GrpcService {
return &GrpcService{}
}
func (sv *GrpcService) UpdateUser(ctx context.Context, req *api.UserRequest) (*api.Status, error) {
user, err := voltDbClient.FindByID(req.GetUserId())
if user == nil || err != nil {
log.Print("error while reading")
} else {
log.Println(user.(*model.User).UserId)
log.Println(user.(*model.User).Name)
}
err = voltDbClient.Put(req.GetUserId(), req.GetName())
if err != nil {
log.Print("error while writing")
}
return &api.Status{Code: "0"}, nil
}
DB client
package db
import (
"database/sql"
"log"
"testgrpc/pkg/model"
_ "github.com/VoltDB/voltdb-client-go/voltdbclient"
)
var Db *sql.DB
func init() {
if Db == nil {
var err error
Db, err = sql.Open("voltdb", "localhost:21212")
if err != nil {
log.Fatal(err)
}
//limit max connection to reproduce issue
Db.SetMaxOpenConns(20)
}
}
type VoltDBClient struct {
}
func NewVoltDBClient() *VoltDBClient {
return &VoltDBClient{}
}
//Put add or update record
func (dbClient *VoltDBClient) Put(key interface{}, value interface{}) error {
res, err := Db.Exec("GROUPINFO.upsert", key, value)
if err != nil {
log.Println(err)
return err
}
rowCnt, err := res.RowsAffected()
if err != nil {
log.Println(err)
return err
}
log.Printf("affected = %d\n", rowCnt)
return nil
}
//FindByID find a record by ID
func (dbClient *VoltDBClient) FindByID(key interface{}) (interface{}, error) {
rows, err := Db.Query("GROUPSELECT", key)
if err != nil {
log.Println(err)
return nil, err
}
for rows.Next() {
user := model.User{UserId: "", Name: ""}
err := rows.Scan(&user.UserId, &user.Name)
if err != nil {
log.Println(err)
return nil, err
}
return &user, nil
}
return nil, nil
}
Note: I have tried run project without grpc, read/write operators work well.
Related
I am trying to create an SMTP client that uses a proxy connection, SOCKS5.
When I use a local host proxy the code successfully creates an SMTP client.
When I try to use a remote proxy, I am getting a TTL expired. I am also getting an EOF error when trying to use a different proxy connection.
I have set up a proxy server in my localhost, socks5://dante:maluki#127.0.0.1:1080
I have also set up an identical proxy server on my remote VM, socks5://dante:maluki#35.242.186.23:1080
package main
import (
"errors"
"log"
"net"
"net/smtp"
"net/url"
"time"
"golang.org/x/net/idna"
"golang.org/x/net/proxy"
)
const (
smtpTimeout = time.Second * 60
smtpPort = ":25"
)
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func main() {
// The code works when I use a localhost proxy
// socks5://dante:maluki#127.0.0.1:1080
client, err := newSMTPClient("gmail.com", "socks5://dante:maluki#35.242.186.23:1080")
if err != nil {
log.Println(err)
return
}
log.Println(client)
}
// establishProxyConnection connects to the address on the named network address
// via proxy protocol
func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
// return socks.Dial(proxyURI)("tcp", addr)
u, err := url.Parse(proxyURI)
if err != nil {
log.Println(err)
return nil, err
}
var iface proxy.Dialer
if u.User != nil {
auth := proxy.Auth{}
auth.User = u.User.Username()
auth.Password, _ = u.User.Password()
iface, err = proxy.SOCKS5("tcp", u.Host, &auth, &net.Dialer{Timeout: 30 * time.Second})
if err != nil {
log.Println(err)
return nil, err
}
} else {
iface, err = proxy.SOCKS5("tcp", u.Host, nil, proxy.FromEnvironment())
if err != nil {
log.Println(err)
return nil, err
}
}
dialfunc := iface.Dial
return dialfunc("tcp", addr)
}
// newSMTPClient generates a new available SMTP client
func newSMTPClient(domain, proxyURI string) (*smtp.Client, error) {
domain = domainToASCII(domain)
mxRecords, err := net.LookupMX(domain)
if err != nil {
log.Println(err)
return nil, err
}
if len(mxRecords) == 0 {
return nil, errors.New("No MX records found")
}
// Attempt to connect to SMTP servers
for _, r := range mxRecords {
// Simplified to make the code short
addr := r.Host + smtpPort
c, err := dialSMTP(addr, proxyURI)
if err != nil {
log.Println(err)
continue
}
return c, err
}
return nil, errors.New("failed to created smtp.Client")
}
// dialSMTP is a timeout wrapper for smtp.Dial. It attempts to dial an
// SMTP server (socks5 proxy supported) and fails with a timeout if timeout is reached while
// attempting to establish a new connection
func dialSMTP(addr, proxyURI string) (*smtp.Client, error) {
// Channel holding the new smtp.Client or error
ch := make(chan interface{}, 1)
// Dial the new smtp connection
go func() {
var conn net.Conn
var err error
conn, err = establishProxyConnection(addr, proxyURI)
if err != nil {
log.Println(err)
}
if err != nil {
ch <- err
return
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
log.Println(err)
}
client, err := smtp.NewClient(conn, host)
log.Println(client)
if err != nil {
log.Println(err)
ch <- err
return
}
ch <- client
}()
// Retrieve the smtp client from our client channel or timeout
select {
case res := <-ch:
switch r := res.(type) {
case *smtp.Client:
return r, nil
case error:
return nil, r
default:
return nil, errors.New("Unexpected response dialing SMTP server")
}
case <-time.After(smtpTimeout):
return nil, errors.New("Timeout connecting to mail-exchanger")
}
}
// domainToASCII converts any internationalized domain names to ASCII
// reference: https://en.wikipedia.org/wiki/Punycode
func domainToASCII(domain string) string {
asciiDomain, err := idna.ToASCII(domain)
if err != nil {
return domain
}
return asciiDomain
}
I am trying to create an API with Go, gorm and gin. However, using a direct SQL query did work, but it became too cumbersome and I decided to leverage gorm which is fantastic. However after my implementation, I am unable to get the project up again and keep getting error
panic: runtime error: invalid memory address or nil pointer
dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0xb0
pc=0x137b606]
I have pasted my code with packages here. I would be glad if someone could point me to the issue.
PostgresDoa.go
package postgres
import (
"fmt"
"log"
"net/http"
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
type Sever struct {
DB *gorm.DB
Router *gin.Engine
}
func (server *Sever) Initialize(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) {
var err error
if Dbdriver == "mysql" {
DBURL := fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", DbUser, DbPassword, DbHost, DbPort, DbName)
server.DB, err = gorm.Open(Dbdriver, DBURL)
if err != nil {
fmt.Printf("Cannot connect to %s database", Dbdriver)
log.Fatal("This is the error:", err)
} else {
fmt.Printf("We are connected to the %s database", Dbdriver)
}
}
if Dbdriver == "postgres" {
DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
server.DB, err = gorm.Open(Dbdriver, DBURL)
if err != nil {
fmt.Printf("Cannot connect to %s database", Dbdriver)
log.Fatal("This is the error:", err)
} else {
fmt.Printf("We are connected to the %s database", Dbdriver)
}
}
server.DB.Debug().AutoMigrate(&users.User{}) //database migration
server.Router = gin.Default()
}
func (server *Sever) Run(addr string) {
fmt.Println("Listening to port 8080")
log.Fatal(http.ListenAndServe(addr, server.Router))
}
UserDto.go
package users
import (
"strings"
"github.com/AdieOlami/bookstore_users-api/domain/base"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
"github.com/jinzhu/gorm"
uuid "github.com/satori/go.uuid"
)
type User struct {
base.Base
UserID uuid.UUID `gorm:"type:uuid;column:userId;not null;" json:"userId"`
FirstName string `gorm:"size:255;not null;unique;column:firstName" json:"firstName"`
LastName string `gorm:"size:255;not null;unique;column:lastName" json:"lastName"`
Email string `gorm:"size:100;not null;unique;column:email" json:"email"`
}
func (user *User) Validate() *errors.Error {
user.Email = strings.TrimSpace(strings.ToLower(user.Email))
if user.Email == "" {
return errors.NewBadRequestError("invalid email address")
}
return nil
}
func (user *User) SaveUser(db *gorm.DB) *errors.Error {
var err error
err = db.Debug().Create(&user).Error
if err != nil {
return errors.NewInteralServerError(err.Error())
}
return nil
}
UserService.go
package services
import (
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
)
func (server *Server) CreateUser(user users.User) (*users.User, *errors.Error) {
if err := user.Validate(); err != nil {
return nil, err
}
if err := user.SaveUser(server.DB); err != nil {
return nil, err
}
return &user, nil
}
UserController.go
package users
import (
"net/http"
"strconv"
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/AdieOlami/bookstore_users-api/services"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
"github.com/gin-gonic/gin"
)
var (
server = services.Server{}
)
// CREATE USER
func CreateUser(c *gin.Context) {
var user users.User
if err := c.ShouldBindJSON(&user); err != nil {
// TODO: hnadle json error return bad request
err := errors.NewBadRequestError("invalid json body")
c.JSON(err.Status, err)
// fmt.Println(err.Error())
return
}
result, saveErr := server.CreateUser(user)
if saveErr != nil {
// TODO: handle user createing error
c.JSON(saveErr.Status, saveErr)
return
}
c.JSON(http.StatusCreated, result)
}
Routes.go
package app
import (
"github.com/AdieOlami/bookstore_users-api/controllers/users"
"github.com/AdieOlami/bookstore_users-api/database/postgres"
)
var (
server = postgres.Sever{}
)
func initializeRoutes() {
server.Router.POST("/users", users.CreateUser)
}
Application.go
package app
import (
"os"
"github.com/AdieOlami/bookstore_users-api/seed"
_ "github.com/jinzhu/gorm/dialects/mysql" //mysql database driver
_ "github.com/jinzhu/gorm/dialects/postgres" //postgres database driver
)
func StartApplication() {
server.Initialize(os.Getenv("DB_DRIVER"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_PORT"), os.Getenv("DB_HOST"), os.Getenv("DB_NAME"))
seed.Load(server.DB)
server.Run(":8088")
initializeRoutes()
}
in my Main.go
func main() {
app.StartApplication()
}
As far as I can understand from your code. You declared below function in Services package whereas Server object is declared in the Postgres package. This causes dereferencing of pointer server *Server to an invalid address. you have to declare this function in posgres package.
func (server *Server) CreateUser(user users.User) (*users.User, *errors.Error) {
if err := user.Validate(); err != nil {
return nil, err
}
if err := user.SaveUser(server.DB); err != nil {
return nil, err
}
return &user, nil
}
I'm trying to mark messages as seen using this IMAP protocol implementation but It's not working as intended.
I have a function that prints unseen messages and my intention is that by the end, it mark each message as seen.
package main
import (
"emailmonitor/util"
"fmt"
)
func main() {
serverGmail := util.NewServerGmail()
serverGmail.Connect()
serverGmail.Login()
serverGmail.ListUnseenMessages()
}
//-----------------------------------------
package util
import (
"io/ioutil"
"log"
"net/mail"
"net/smtp"
imap "github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
)
type ServerGmail struct {
user string
pass string
erro string
cliente *client.Client
}
func NewServerGmail() *ServerGmail {
serverGmail := &ServerGmail{}
serverGmail.user = "xxxxxx#gmail.com"
serverGmail.pass = "xxxxx"
serverGmail.erro = ""
return serverGmail
}
func (serverGmail *ServerGmail) Connect() {
// Connect to server
cliente, erro := client.DialTLS("smtp.gmail.com:993", nil)
if erro != nil {
serverGmail.erro = erro.Error()
}
log.Println("Connected")
serverGmail.cliente = cliente
}
func (serverGmail *ServerGmail) Login() {
// Login
if erro := serverGmail.cliente.Login(serverGmail.user, serverGmail.pass); erro != nil {
serverGmail.erro = erro.Error()
}
log.Println("Logged")
}
func (serverGmail *ServerGmail) setLabelBox(label string) *imap.MailboxStatus {
mailbox, erro := serverGmail.cliente.Select(label, true)
if erro != nil {
serverGmail.erro = erro.Error()
}
return mailbox
}
func (serverGmail *ServerGmail) ListUnseenMessages() {
// set mailbox to INBOX
serverGmail.setLabelBox("INBOX")
// criteria to search for unseen messages
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{"\\Seen"}
uids, err := serverGmail.cliente.UidSearch(criteria)
if err != nil {
log.Println(err)
}
seqSet := new(imap.SeqSet)
seqSet.AddNum(uids...)
section := &imap.BodySectionName{}
items := []imap.FetchItem{imap.FetchEnvelope, imap.FetchFlags, imap.FetchInternalDate, section.FetchItem()}
messages := make(chan *imap.Message)
go func() {
if err := serverGmail.cliente.UidFetch(seqSet, items, messages); err != nil {
log.Fatal(err)
}
}()
for message := range messages {
log.Println(message.Uid)
if message == nil {
log.Fatal("Server didn't returned message")
}
r := message.GetBody(section)
if r == nil {
log.Fatal("Server didn't returned message body")
}
// Create a new mail reader
mr, err := mail.CreateReader(r)
if err != nil {
log.Fatal(err)
}
// Print some info about the message
header := mr.Header
if date, err := header.Date(); err == nil {
log.Println("Date:", date)
}
if from, err := header.AddressList("From"); err == nil {
log.Println("From:", from)
}
if to, err := header.AddressList("To"); err == nil {
log.Println("To:", to)
}
if subject, err := header.Subject(); err == nil {
log.Println("Subject:", subject)
}
// MARK "SEEN" ------- STARTS HERE ---------
seqSet.Clear()
seqSet.AddNum(message.Uid)
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.SeenFlag}
erro := serverGmail.cliente.UidStore(seqSet, item, flags, nil)
if erro != nil {
panic("error!")
}
}
}
Link from Documentation: https://godoc.org/github.com/emersion/go-imap/client#Client.UidStore
Tried to do something similar to Store example.
What can be done to fix it?
modify the following line by changing true to false
mailbox, erro := serverGmail.cliente.Select(label, true)
once you've done this, when the message is fetched (using the UidFetch), it will be automatically marked to "Seen"
I'm currently trying to connect to the CEX.IO bitcoin exchange's websocket, but have been having issues not only with CEX.IO but with others too. All of my connections drop around the 120-second mark which makes me think there is some TTL problem going on. The Process() goroutine in the main package ends up just hanging and waiting for data from the readLoop which just stops receiving data. I've included some read-only API keys in the code so you can test if you'd like.
package main
import (
"fmt"
"bitbucket.org/tradedefender/cryptocurrency/exchange-connector/cexio"
"github.com/shopspring/decimal"
"encoding/json"
"time"
)
type OrderBook struct {
Asks []Ask
Bids []Bid
}
type Ask struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
type Bid struct {
Rate decimal.Decimal
Amount decimal.Decimal
}
func main() {
cexioConn := new(cexio.Connection)
err := cexioConn.Connect()
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
err = cexioConn.Authenticate("TLwYkktLf7Im6nqSKt6UO1IrU", "9ImOJcR7Qj3LMIyPCzky0D7WE")
if err != nil {
fmt.Errorf("error: %s", err.Error())
}
readChannel := make(chan cexio.IntraAppMessage, 25)
go cexioConn.ReadLoop(readChannel)
processor := Processor{
WatchPairs: [][2]string{
[2]string{
"BTC", "USD",
},
},
conn: cexioConn,
}
go processor.Process(readChannel)
// LOL
for {
continue
}
}
type Processor struct {
WatchPairs [][2]string
conn *cexio.Connection
}
func (p *Processor) Process(ch <-chan cexio.IntraAppMessage) {
p.conn.SubscribeToOrderBook(p.WatchPairs[0])
pingTimer := time.Now().Unix()
for {
fmt.Printf("(%v)\n", time.Now().Unix())
if (time.Now().Unix() - pingTimer) >= 10 {
fmt.Println("sending ping")
p.conn.SendPing()
pingTimer = time.Now().Unix()
}
readMsg := <- ch
output, _ := json.Marshal(readMsg.SocketMessage)
fmt.Println(string(output))
if readMsg.SocketMessage.Event == "ping" {
fmt.Println("sending pong")
p.conn.SendPong()
pingTimer = time.Now().Unix()
}
}
}
Below is the connector to the cexio websocket. Here is a link to their API: https://cex.io/websocket-api
package cexio
import (
"github.com/gorilla/websocket"
//"github.com/shopspring/decimal"
"github.com/satori/go.uuid"
"encoding/hex"
"encoding/json"
"crypto/hmac"
"crypto/sha256"
"bytes"
"strconv"
"time"
"fmt"
)
const Url = "wss://ws.cex.io/ws/"
type Connection struct {
conn *websocket.Conn
}
type IntraAppMessage struct {
SocketMessage GenericMessage
ProgramMessage ProgramMessage
}
type GenericMessage struct {
Event string `json:"e"`
Data interface{} `json:"data"`
Auth AuthData `json:"auth,omitempty"`
Ok string `json:"ok,omitempty"`
Oid string `json:"oid,omitempty"`
Time int64 `json:"time,omitempty"`
}
type ProgramMessage struct {
Error string
}
type AuthData struct {
Key string `json:"key"`
Signature string `json:"signature"`
Timestamp int64 `json:"timestamp"`
}
type OrderBookSubscribeData struct {
Pair [2]string `json:"pair"`
Subscribe bool `json:"subscribe"`
Depth int `json:"depth"`
}
func (c *Connection) SendPong() error {
pongMsg := GenericMessage{
Event: "pong",
}
err := c.conn.WriteJSON(pongMsg)
if err != nil {
return nil
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PongMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) SendPing() error {
pingMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
return err
}
deadline := time.Now().Add(15*time.Second)
err = c.conn.WriteControl(websocket.PingMessage, nil, deadline)
if err != nil {
return err
}
return nil
}
func (c *Connection) Connect() error {
dialer := *websocket.DefaultDialer
wsConn, _, err := dialer.Dial(Url, nil)
if err != nil {
return err
}
c.conn = wsConn
//c.conn.SetPingHandler(c.HandlePing)
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "connected" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) Disconnect() error {
return c.conn.Close()
}
func (c *Connection) ReadLoop(ch chan<- IntraAppMessage) {
for {
fmt.Println("starting new read")
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
ch <- IntraAppMessage{
ProgramMessage: ProgramMessage{
Error: err.Error(),
},
}
continue
}
ch <- IntraAppMessage{
SocketMessage: m,
}
}
}
func CreateSignature(timestamp int64, key, secret string) string {
secretBytes := []byte(secret)
h := hmac.New(sha256.New, secretBytes)
var buffer bytes.Buffer
buffer.WriteString(strconv.FormatInt(timestamp, 10))
buffer.WriteString(key)
h.Write(buffer.Bytes())
return hex.EncodeToString(h.Sum(nil))
}
func (c *Connection) Authenticate(key, secret string) error {
timestamp := time.Now().Unix()
signature := CreateSignature(timestamp, key, secret)
var authMsg GenericMessage
authMsg.Event = "auth"
authMsg.Auth = AuthData{
Key: key,
Signature: signature,
Timestamp: timestamp,
}
err := c.conn.WriteJSON(authMsg)
if err != nil {
return err
}
for {
_, msgBytes, err := c.conn.ReadMessage()
if err != nil {
c.Disconnect()
return err
}
fmt.Println(string(msgBytes))
var m GenericMessage
err = json.Unmarshal(msgBytes, &m)
if err != nil {
c.Disconnect()
return err
}
if m.Event != "auth" && m.Ok != "ok" {
c.Disconnect()
return err
} else {
break
}
}
return nil
}
func (c *Connection) SubscribeToOrderBook(pair [2]string) error {
sendMsg := GenericMessage{
Event: "order-book-subscribe",
Data: OrderBookSubscribeData{
Pair: pair,
Subscribe: true,
Depth: 0,
},
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}
func (c *Connection) GetBalance() error {
sendMsg := GenericMessage{
Event: "get-balance",
Oid: uuid.NewV4().String(),
}
err := c.conn.WriteJSON(sendMsg)
if err != nil {
return err
}
return nil
}
Solution was to remove the
for {
continue
}
at the end of the main function
I was performing some RPC tests, and stumbled across a problem I can't seem to solve. In my testing I create three separate RPC servers, all of which I try to close and shutdown. However upon performing my last test (TestRpcCodecServerClientComm), it seems my client connection is connecting to the first RPC server I started (I know this because I at some point attached IDs to the RPCHandlers), even though I attempted everything I could to make sure it was shutdown. Though the code is not there I have attempted to inspect every single error I could, but that did not bring about anything.
rpc.go
package rbot
import (
"io"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func RpcCodecClientWithPort(port string) (rpc.ClientCodec, error) {
conn, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
return nil, err
}
return jsonrpc.NewClientCodec(conn), nil
}
func RpcCodecServer(conn io.ReadWriteCloser) rpc.ServerCodec {
return jsonrpc.NewServerCodec(conn)
}
rpc_test.go
package rbot
import (
"errors"
"fmt"
"net"
"net/rpc"
"testing"
)
type RPCHandler struct {
RPCServer net.Listener
conn rpc.ServerCodec
done chan bool
TestPort string
stop bool
GotRPC bool
}
func (r *RPCHandler) SetupTest() {
r.stop = false
r.GotRPC = false
r.done = make(chan bool)
r.TestPort = "5556"
}
// TODO: Create separate function to handle erroring
func (r *RPCHandler) CreateRPCServer() error {
rpc.RegisterName("TestMaster", TestAPI{r})
var err error
r.RPCServer, err = net.Listen("tcp", ":"+r.TestPort)
if err != nil {
return err
}
go func() {
for {
conn, err := r.RPCServer.Accept()
if err != nil || r.stop {
r.done <- true
return
}
r.conn = RpcCodecServer(conn)
rpc.ServeCodec(r.conn)
}
}()
return nil
}
func (r *RPCHandler) CloseRPCServer() error {
r.stop = true
if r.conn != nil {
err := r.conn.Close()
if err != nil {
fmt.Println(err)
}
}
err := r.RPCServer.Close()
<-r.done
return err
}
type TestAPI struct {
t *RPCHandler
}
func (tapi TestAPI) Send(msg string, result *string) error {
if msg == "Got RPC?" {
tapi.t.GotRPC = true
return nil
}
return errors.New("Didn't receive right message")
}
// Check if we can create and close an RPC server successfully using the RPC server codec.
func TestRpcCodecServer(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
err := r.CreateRPCServer()
if err != nil {
t.Fatalf("Could not create rpc server! %s:", err.Error())
}
err = r.CloseRPCServer()
if err != nil {
t.Fatalf("Could not close RPC server! %s:", err.Error())
}
}
// Check if we can create a client without erroring.
func TestRpcCodecClientWithPortt(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCClient, err := RpcCodecClientWithPort(r.TestPort)
defer RPCClient.Close()
if err != nil {
t.Fatalf("Could not create an RPC client! %s:", err.Error())
}
}
// Let's double check and make sure our server and client can speak to each other
func TestRpcCodecServerClientComm(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCCodec, _ := RpcCodecClientWithPort(r.TestPort)
RPCClient := rpc.NewClientWithCodec(RPCCodec)
defer RPCClient.Close()
var result string
err := RPCClient.Call("TestMaster.Send", "Got RPC?", &result)
if err != nil {
t.Fatalf("Error while trying to send RPC message: %s", err.Error())
}
if !r.GotRPC {
t.Fatalf("Could not send correct message over RPC")
}
}
Not sure if I'm just mishandling the connection or something of the like, any help would be much appreciated.
For the Record The RPC api does receive the correct string message
While not the source of your problems, your test configuration has a few race conditions which you should take care of before they cause problems. Always check for issues with the -race option. You should also let the OS allocate the port so you don't run into conflicts. See for example how httptest.Server works.
Your failure here is that you're not creating a new rpc.Server for each test, you're reusing the rpc.DefaultServer. The first call to CreateRPCServer registers a TestAPI under the name TestMaster. Each subsequent call uses the already registered instance.
If you create a new rpc.Server each time you setup the test and register a new TestAPI, the final test will pass.
srv := rpc.NewServer()
srv.RegisterName("TestMaster", testAPI)
...
// and then use srv to handle the new connection
srv.ServeCodec(RpcCodecServer(conn))