Posting Slackbot response in Golang - go

I am following this tutorial trying to get the go slack lib working. I am able to receive messages from slack, stored in ev variable but can not reply
func replyToUser(ev *slack.MessageEvent) { //change this to the channel for the actual app
fmt.Printf(ev.User)
slackClient.PostMessage(ev.User,
slack.MsgOptionText("hello world", false),
slack.MsgOptionUser(ev.User),
slack.MsgOptionAsUser(true),
slack.MsgOptionUsername("songbot"),
)
}
I am passing the event i receive from slack to this function and hoping to reply with a hello world. I am using a classic slackbot.
Currently getting the error:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x127abb7]
Any help much appreciated !

really sorry I incorrectly defined the slack client. Full answer here: https://github.com/slack-go/slack/issues/796
Running code is:
package main
import (
"fmt"
"log"
"os"
"github.com/slack-go/slack"
)
var (
slackClient *slack.Client //initialise the slack event
)
func main() {
slackClient = slack.New(os.Getenv("SLACK_ACCESS_TOKEN"))
rtm := slackClient.NewRTM() //create the realtime messaging objext
go rtm.ManageConnection() // set it up in a go routine
for msg := range rtm.IncomingEvents {
switch ev := msg.Data.(type) {
case *slack.MessageEvent:
go handleMessage(ev)
}
}
}
func handleMessage(ev *slack.MessageEvent) {
fmt.Printf("%v\n", ev)
fmt.Printf("Text Received: %v\n", ev.Msg.Text)
_, _, err := slackClient.PostMessage(ev.Channel, slack.MsgOptionText("message", false), slack.MsgOptionAsUser(true))
if err != nil {
log.Println("error sending to slack: " + err.Error())
}
return
}

Related

Decoding ethereum transaction input function with Golang (go-ethereum in 2023)

I am trying to decode the input data for an ethereum transaction in Golang using the go-ethereum package. I have looked on here, ethereum stack exchange and the Go-ethereum documentation in order to try and help. I've even asked ChatGPT and it can't fix it either.
The error message I receive stems from the rlp package when calling the rlp.DecodeBytes function. The error message received is: rlp: expected input list for []main.SwapFunction.
Here is the transaction: https://etherscan.io/tx/0xd918b14504b82d4c5334a751e0439826e682a57c6813f16a707801f89f589b90
And here are my types in my code which correspond to the function being called in the transaction:
package main
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
)
type SwapDescription struct {
SrcToken common.Address
DstToken common.Address
SrcReceiver common.Address
DstReceiver common.Address
Amount *big.Int
MinReturnAmount *big.Int
GuaranteedAmount *big.Int
Flags *big.Int
Referrer common.Address
Permit []byte
}
type CallDescription struct {
TargetWithMandatory *big.Int
GasLimit *big.Int
Value *big.Int
Data []byte
}
type SwapFunction struct {
Caller common.Address
Desc SwapDescription
Calls []CallDescription
}
Here is my code to try and decode the input data into the SwapFunction struct in order to access the fields:
func main() {
// Connect to Ethereum network
client, err := ethclient.Dial("https://eth-mainnet.alchemyapi.io/jsonrpc/MY-API-KEY")
if err != nil {
fmt.Println("Error connecting to Ethereum network:", err)
return
}
// Define the transaction hash
txHash := "0xd918b14504b82d4c5334a751e0439826e682a57c6813f16a707801f89f589b90"
hash := common.HexToHash(txHash)
tx, isPending, err := client.TransactionByHash(context.Background(), hash)
if err != nil {
fmt.Println(err)
return
}
if isPending {
fmt.Println("Transaction is pending")
return
}
input := tx.Data()
var functionSignature string
if len(input) >= 4 {
functionSignature = string(input[:4])
}
fmt.Println("Function signature:", functionSignature)
var decodedData []SwapFunction
fmt.Println(decodedData)
err = rlp.DecodeBytes(input[4:], &decodedData)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Decoded data:", decodedData)
}
Please can someone show me how to fix the error "rlp: expected input list for []main.SwapFunction?
I have tried changing the decodedData variable to a single SwapFunction and then in the code above, to a slice of SwapFunction.
I am not sure why this error is happening and the documentation in rlp is relatviely vague around this area. I have read the following page: https://pkg.go.dev/github.com/ethereum/go-ethereum#v1.10.26/rlp.
Someone please help as I am at a roadblock. Thank you for anyone who helps and apologies if I have missed anything.

Global Variable Gives SIGSEGV

I am using Fiber to develop a backend.
I have a map that is a global variable that holds the socket connections.
When I use the global variable from the same package, no problem here, everything works fine. But, when I try to use the sockets from a route function, I am getting the error below.
I tried to use mutex.lock but no luck.
I checked the code, the socket is not nil in my sendToAll method but it becomes nil in the helper method( inside the lib: github.com/fasthttp/websocket.(*Conn).WriteMessage )
Any advice is welcome.
Thanks.
type ConnectedSocketsContainerType struct {
M sync.Mutex
ConnectedSockets map[string]*websocket.Conn
}
var ConnectedSocketsContainer = ConnectedSocketsContainerType{ M:sync.Mutex{} , ConnectedSockets: make(map[string]*websocket.Conn) }
In another package in GET request handler calls that method:
func send(socketID string,message string) {
sockethub.ConnectedSocketsContainer.M.Lock()
sendToAll(message)
sockethub.ConnectedSocketsContainer.M.Unlock()
}
func sendToAll(message string) {
for k := range sockethub.SocketsIDs {
k.WriteMessage(1, []byte(message))
}
}
The error:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x14d27f8]
goroutine 6 [running]:
github.com/fasthttp/websocket.(*Conn).WriteMessage(0xc00006aa78, 0xc00045e115, {0xc0004540f0, 0x29, 0xc00006ab2f})
/Users/emre/go/pkg/mod/github.com/fasthttp/websocket#v1.4.3-rc.10/conn.go:753 +0x38
goserver/controllers/api.sendToAll({0xc00045e115, 0x29})
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:11 +0xac
goserver/controllers/api.send({0xc000456000, 0x15edfe1}, {0xc00045e115, 0x0})
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:22 +0x65
goserver/controllers/api.SendMessageController(0xc000128a50)
/Users/emre/Documents/Free/SocketServer/goServer/controllers/api/socket.go:29 +0x71
github.com/gofiber/fiber/v2.(*App).next(0xc00019cb60, 0xc000456000)
/Users/emre/go/pkg/mod/github.com/gofiber/fiber/v2#v2.23.0/router.go:127 +0x1d8
github.com/gofiber/fiber/v2.(*App).handler(0xc00019cb60, 0x10bb517)
/Users/emre/go/pkg/mod/github.com/gofiber/fiber/v2#v2.23.0/router.go:155 +0xe5
github.com/valyala/fasthttp.(*Server).serveConn(0xc000126000, {0x16c4fa0, 0xc0000106e8})
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp#v1.31.0/server.go:2278 +0x122d
github.com/valyala/fasthttp.(*workerPool).workerFunc(0xc00014c000, 0xc00022dba0)
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp#v1.31.0/workerpool.go:223 +0xa9
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp#v1.31.0/workerpool.go:195 +0x38
created by github.com/valyala/fasthttp.(*workerPool).getCh
/Users/emre/go/pkg/mod/github.com/valyala/fasthttp#v1.31.0/workerpool.go:194 +0x1b5
exit status 2
Full example for go server. Please see two comments that specify working and not working code blocks.
package main
import (
"fmt"
"sync"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
func main() {
app := fiber.New()
ListenSocket(app)
app.Get("/socket/send", SendMessageController )
app.Listen(":3000")
}
const websocketHeaderKey = "Sec-Websocket-Key"
var ConnectedIDSockets sync.Map
func SendMessageController( c *fiber.Ctx ) error {
ConnectedIDSockets.Range(func(key, value interface{}) bool {
c := value.(*websocket.Conn)
if c == nil {
// that line is not printed, c is not nil.
fmt.Println("c is nil.")
return true
}
// we have crash at that line, even we read the err.
err := c.WriteMessage(websocket.TextMessage, []byte("message"))
// program does not runs to here since it crashed.
println("err:", err)
return true
})
return nil
}
func ListenSocket(app *fiber.App) {
app.Use("/ws", func(c *fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
c.Locals(websocketHeaderKey, string(c.Request().Header.Peek(websocketHeaderKey)))
return c.Next()
}
return fiber.ErrUpgradeRequired
})
app.Get("/ws/:projectKEY", websocket.New(func(c *websocket.Conn) {
socketID := c.Locals(websocketHeaderKey).(string)
ConnectedIDSockets.Store(socketID, c)
// that works.
conn, _ := ConnectedIDSockets.Load(socketID)
socketmap := conn.(*websocket.Conn)
socketmap.WriteMessage(1, []byte(socketID))
}))
}
This panic is confusing because there are actually two packages called websocket. One in github.com/gofiber/websocket/v2 and another one in github.com/fasthttp/websocket, and both have their own *websocket.Conn. However the websocket.Conn in github.com/gofiber/websocket actually embeds the websocket.Conn from github.com/fasthttp/websocket (I know, terrible design) making what's going on unclear.
Your call to c.WriteMessage is actually going to c.Conn.WriteMessage, and c.Conn is what's nil. So in your nil check, you actually need to do if c == nil || c.Conn == nil { to check the embedded struct as well.

How do you connect to an AMQP 1.0 topic not queue in Golang

I have been trying out the sample code on go-amp package README, but I wanted to connect to a topic and not a queue as shown in the sample code on that README as of today.
What I did was to just put the topic name where the 'queue-name' had been put like this.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/Azure/go-amqp"
)
const host = "example.com"
const topic = "/topic/my_topic"
const port = "5672"
const username = "my_username"
const password = "my_password"
// A hleper function to handle errors
func failOnError(err error, msg string){
if err != nil {
log.Fatalf("%s %s", msg, err)
}
}
func main(){
// connect to remote amqp server
host_address := fmt.Sprintf("amqps://%s:%s", host, port)
log.Println("Connecting to ", host_address)
client, err := amqp.Dial(host_address,
amqp.ConnSASLPlain(username, password),
)
failOnError(err, "Failed to connect to Server")
defer client.Close()
// Open a session
session, err := client.NewSession()
failOnError(err, "Failed to create AMQP session")
ctx := context.Background()
// Continuously read messages
{
// Create a receiver
receiver, err := session.NewReceiver(
amqp.LinkSourceAddress(topic),
amqp.LinkCredit(10),
)
failOnError(err, "Failed creating receiver link")
defer func() {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
receiver.Close(ctx)
cancel()
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
for {
// Receive next message
msg, err := receiver.Receive(ctx)
failOnError(err, "Failed reading message from AMQP:")
// Accept message
msg.Accept(context.Background())
fmt.Printf("Message received: Body: %s\n", msg.Value)
}
}
}
I kept getting this error.
Failed creating receiver link *Error{Condition: amqp:unauthorized-access,
Description: User my_username is not authorized to read from: queue:///topic/my_topic, Info: map[]}
It looks like it is treating my topic as a queue. How do I set the receiver to try to attach to a topic and not a queue?
EDIT
I am using an ActiveMQ broker that uses AMQP 1.0. It is managed by someone else so I just have to use AMQP 1.0.
That means I cannot use the more popular go amqp package as it has no support for AMQP 1.0. Thanks Tim Bish for alerting me to add this.
After trial and error, I figured one can just change topic to 'topic://my_topic'
const topic = "topic://my_topic"
The rest of the code remains the same. I created a gist for both sending to and receiving from a topic.
I hope that helps newbies like me some good hours of banging their heads on their keyboards.

How to get consumer group offsets for partition in Golang Kafka 10

Now that Golang Kafka library (sarama) is providing consumer group capability without any external library help with kafka 10. How can I get the current message offset being processed by a consumer group at any given time ?
Previously I used kazoo-go (https://github.com/wvanbergen/kazoo-go) to get my consumer group message offset as it is stored in Zookeeper. Now I use sarama-cluster (https://github.com/bsm/sarama-cluster), I am not sure which API to use to get my consumer group message offset.
Under the hood the consumerGroupSession struct is using PartitionOffsetManager to get next offset:
if pom := s.offsets.findPOM(topic, partition); pom != nil {
offset, _ = pom.NextOffset()
}
Here is the documentation of pom.NextOffset().
When a consumerGroupSession constructs a consumerGroupClaim struct via newConsumerGroupClaim() method, it passes offset, returned by pom.NextOffset(), as offset argument. You can access it later via claim.InitialOffset(). After you started consuming messages, you can use message.Offset of the currently processed message.
Unfortunately, consumerGroupSession.offsets.findPOM() can't be accessed from ConsumerGroupHandler.ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) method, because it receives session as a ConsumerGroupSession interface, not as consumerGroupSession struct. So the offsets variable is private and not accessible.
Thus we can't really access NextOffset() method, which does precisely what the OP wants.
I am also working with Sarama and Kafka to get offset of a topic.
You can get offset with following code.
package main
import (
"gopkg.in/Shopify/sarama"
"fmt"
)
func main(){
client , err := sarama.Client([]string{"localhost:9092"},nil) // I am not giving any configuration
if err != nil {
panic(err)
}
lastoffset, err := client.GetOffset("topic-test",0,sarama.OffsetNewest)
if err != nil {
panic(err)
}
fmt.Println("Last Commited Offset ",lastoffset)
}
Let me know if this is the answer you are looking for and if it is helpful.
Here's a sample code to get the consumer group offset (i.e. the offset where the consumer group will start):
package main
import (
"context"
"log"
"strings"
"github.com/Shopify/sarama"
)
func main() {
groupName := "testgrp"
topic := "topic_name"
offset, e := GetCGOffset(context.Background(), "localhost:9092", groupName, topic)
if e != nil {
log.Fatal(e)
}
log.Printf("Consumer group %s offset for topic %s is: %d", groupName, topic, offset)
}
type gcInfo struct {
offset int64
}
func (g *gcInfo) Setup(sarama.ConsumerGroupSession) error {
return nil
}
func (g *gcInfo) Cleanup(sarama.ConsumerGroupSession) error {
return nil
}
func (g *gcInfo) ConsumeClaim(_ sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
g.offset = claim.InitialOffset()
return nil
}
func GetCGOffset(ctx context.Context, brokers, groupName, topic string) (int64, error) {
config := sarama.NewConfig()
config.Consumer.Offsets.AutoCommit.Enable = false // we're not going to update the consumer group offsets
client, err := sarama.NewConsumerGroup(strings.Split(brokers, ","), groupName, config)
if err != nil {
return 0, err
}
info := gcInfo{}
if err := client.Consume(ctx, []string{topic}, &info); err != nil {
return 0, err
}
return info.offset, nil
}
I've just been doing work on this myself. As #boris-burkov mentioned you don't have access to the getPOM method, however, you can create a POM yourself and called NextOffset() to get the current consumer's actual offset:
offsetManager, _ := sarama.NewOffsetManagerFromClient(clientName, cl.Client)
offsetPartitionManager, _ := offsetManager.ManagePartition("test-topic", 0)
offsetPartitionManager.NextOffset()

How to handle HTTP timeout errors and accessing status codes in golang

I have some code (see below) written in Go which is supposed to "fan-out" HTTP requests, and collate/aggregate the details back.
I'm new to golang and so expect me to be a nOOb and my knowledge to be limited
The output of the program is currently something like:
{
"Status":"success",
"Components":[
{"Id":"foo","Status":200,"Body":"..."},
{"Id":"bar","Status":200,"Body":"..."},
{"Id":"baz","Status":404,"Body":"..."},
...
]
}
There is a local server running that is purposely slow (sleeps for 5 seconds and then returns a response). But I have other sites listed (see code below) that sometime trigger an error as well (if they error, then that's fine).
The problem I have at the moment is how best to handle these errors, and specifically the "timeout" related errors; in that I'm not sure how to recognise if a failure is a timeout or some other error?
At the moment I get a blanket error back all the time:
Get http://localhost:8080/pugs: read tcp 127.0.0.1:8080: use of closed network connection
Where http://localhost:8080/pugs will generally be the url that failed (hopefully by timeout!). But as you can see from the code (below), I'm not sure how to determine the error code is related to a timeout nor how to access the status code of the response (I'm currently just blanket setting it to 404 but obviously that's not right - if the server was to error I'd expect something like a 500 status code and obviously I'd like to reflect that in the aggregated response I send back).
The full code can be seen below. Any help appreciated.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)
type Component struct {
Id string `json:"id"`
Url string `json:"url"`
}
type ComponentsList struct {
Components []Component `json:"components"`
}
type ComponentResponse struct {
Id string
Status int
Body string
}
type Result struct {
Status string
Components []ComponentResponse
}
var overallStatus string = "success"
func main() {
var cr []ComponentResponse
var c ComponentsList
b := []byte(`{"components":[{"id":"local","url":"http://localhost:8080/pugs"},{"id":"google","url":"http://google.com/"},{"id":"integralist","url":"http://integralist.co.uk/"},{"id":"sloooow","url":"http://stevesouders.com/cuzillion/?c0=hj1hfff30_5_f&t=1439194716962"}]}`)
json.Unmarshal(b, &c)
var wg sync.WaitGroup
timeout := time.Duration(1 * time.Second)
client := http.Client{
Timeout: timeout,
}
for i, v := range c.Components {
wg.Add(1)
go func(i int, v Component) {
defer wg.Done()
resp, err := client.Get(v.Url)
if err != nil {
fmt.Printf("Problem getting the response: %s\n", err)
cr = append(cr, ComponentResponse{
v.Id,
404,
err.Error(),
})
} else {
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Problem reading the body: %s\n", err)
}
cr = append(cr, ComponentResponse{
v.Id,
resp.StatusCode,
string(contents),
})
}
}(i, v)
}
wg.Wait()
j, err := json.Marshal(Result{overallStatus, cr})
if err != nil {
fmt.Printf("Problem converting to JSON: %s\n", err)
return
}
fmt.Println(string(j))
}
If you want to fan out then aggregate results and you want specific timeout behavior the net/http package isn't giving you, then you may want to use goroutines and channels.
I just watched this video today and it will walk you through exactly those scenarios using the concurrency features of Go. Plus, the speaker Rob Pike is quite the authority -- he explains it much better than I could.
https://www.youtube.com/watch?v=f6kdp27TYZs
I am adding this for completes, as the correct answer was provided by Dave C in the comments of the accepted answer.
We can try to cast the error to a net.Error and check if it is a timeout.
resp, err := client.Get(url)
if err != nil {
// if there is an error check if its a timeout error
if e, ok := err.(net.Error); ok && e.Timeout() {
// handle timeout
return
}
// otherwise handle other types of error
}
The Go 1.5 release solved this issue by being more specific about the type of error it has handled.
So if you see this example https://github.com/Integralist/Go-Requester/blob/master/requester.go#L38 you'll see that I'm able to apply a regex pattern to the error message to decipher if the error was indeed a timeout or not
status := checkError(err.Error())
func checkError(msg string) int {
timeout, _ := regexp.MatchString("Timeout", msg)
if timeout {
return 408
}
return 500
}

Resources