Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can i prevent a http connection to close #3874

Open
kingstonduy opened this issue Mar 11, 2024 · 3 comments
Open

How can i prevent a http connection to close #3874

kingstonduy opened this issue Mar 11, 2024 · 3 comments

Comments

@kingstonduy
Copy link

kingstonduy commented Mar 11, 2024

Description

Hi, i'm new from golang and gin. I want to implement a system that: Client sends http request to service A. Service A communicates with service B through kafka. Then service A takes the response from kafka then matches with the request id from client and responses back to client. The normal approach of service A would be:

func main() {
    app := fiber.Default()

    // An example to describe the question
    app.POST("/", func(c *gin.Context) error {
        var req share.SaferRequest
		if err := c.BindJSON(&req); err != nil {
			return c.JSON(400, err.Error())
		} else {
			ProduceToKafka(req)
			res := ConsumeFromKafka(req.ID) 
			resBytes, _ := json.Marshal(res)
			c.JSON(200, resBytes)
		}
    })
    log.Fatal(app.Run(":3000"))
}

Where we can see that the goroutine of each request has to wait for the message consumed from Kafka then response to client.

Now my question is: Does gin support passing the context through other goroutines so that i can use the context corresponding to the request.ID to response to the client. Draft code of my idea:

var m map[string]*gin.Context

func() ConsumeFromKafka() {
    for {
        res := kafka.Consume()
        c := m[res.ID]
        c.JSON(200, res)
    }
}

func main() {
    app := gin.Default()

    // An example to describe the question
    app.Post("/", func(c *gin.Context) error {
        var req share.SaferRequest
		if err := c.BindJSON(&req); err != nil {
			return c.JSON(400, err.Error())
		} else {
			m[req.ID] = c
			ProduceToKafka(req)
		}
    })
    log.Fatal(app.Run(":3000"))

    go ConsumeFromKafka()

     forever chan bool
    <- forever
}

On the about implementation, the connection must be kept alive during the process produce and consume message from kafka. The connection only shutdown when servers response a message back to client. My teammate successfully implemented this idea using vertx using eventbus and RoutingContext so i guess there are some mechanics to work around that. I tried to implement in gin but the response doesnt match with the request (for example client sends a request with id=1 and receives the response with id=2). I notice that gin will sends a reponse with status = 200, body = nil as soon as thefunc(c *gin.Context)returns so how can we prevent that?

Feel free to discuss.
Thank you for reading my question.

Environment

  • go version: 1.20
  • gin version (or commit ref): lastest
  • operating system: wsl
@jamesstocktonj1
Copy link

By default Gin won't timeout your connection, you need to add your own middleware if you want that functionality. Have you tried this code? What issue are you facing in regard to this?

This scenario isn't something that is usually implemented. Usually, you use asynchronous message queues like Kafka and RabbitMQ when you don't want a response in a set timeframe. If you want service B to reply within the same http request then you should use something that is synchronous like another http request or gRPC call. Waiting for a response from Kafka whilst keeping the http connection open can open a whole host of issues. What if the request or response is lost in Kafka? The client making the http request will wait indefinitely.

I hope this clears things up and answers your question.

@kingstonduy
Copy link
Author

Thank @jamesstocktonj1 for the response.
Screenshot 2024-03-20 225235

  • I think i should explain about my case. I attached the diagram. Service A in this diagram is like the adapter to transform http request to message queue. Traditionally, for each request, service A uses 1 go routine for the handler function to receive http request, produce message to a queue to internal services, consume message in a another queue, match the message's correlationID to the corresponding request then response back to client (everything is in 1 and only 1 go routine. We can seperate the produce/consume to different goroutines but the main point is http request and response currently are in the same goroutine). Now is there anyway to receive http request in 1 go routines, produce message to a queue then the go routine terminates. While we open another go routine to consume message from queue then perform somematching context/collerationID and response back to client? To sum up, for each request i want to spawn 2 goroutines: 1 for reading the request's body -> producing message, another one for consuming message -> response back to the client.

  • Right now, because of the low cost of context switching and magic go's cocurrent mechanic, the traditional approach may work fine but to me it is not optimized since we have to wait for the corresponding message. Maybe my thought is different from GO's world. Please Let me know your opinion.

@jamesstocktonj1
Copy link

A context switch will always take less time than making a network call to add to the message queue.

Go's goroutines have even less context to switch than a standard thread due to their lightweight nature. Also as for having to wait for a message. Go's scheduler knows when goroutines are blocked and won't schedule them unless they are unblocked. Therefore when a goroutine is blocked waiting for a http response it will not be blocking another goroutine from being able to run on the cpu.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants