如何与大猩猩Mux建立无状态连接?

如何与大猩猩Mux建立无状态连接?

问题描述:

My program are running fine with one connection per time, but not with concurrent connections.

I need all connections being rendered by one function, which will have all data I need in my service, and that is not working fine, so I ilustrated with the simple code below:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "github.com/rs/cors"
    "net/http"
    "reflect"
    "time"
)

var Out struct {
        Code     int             `json:"status"`
        Message  []interface{}   `json:"message"`
}

func Clear(v interface{}) {
    p := reflect.ValueOf(v).Elem()
    p.Set(reflect.Zero(p.Type()))
}

func YourHandler(w http.ResponseWriter, r *http.Request) {
    Clear(&Out.Message)
    Out.Code = 0

    // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Headers","Content-Type,access-control-allow-origin, access-control-allow-headers")
    w.WriteHeader(http.StatusOK)

    for i:=0; i<10; i++ {
        Out.Code = Out.Code + 1
        Out.Message = append(Out.Message, "Running...")
        time.Sleep(1000 * time.Millisecond)

        if err := json.NewEncoder(w).Encode(Out)
        err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
}

func main() {
    r := mux.NewRouter()
    r.StrictSlash(true);

    r.HandleFunc("/", YourHandler)

    handler := cors.New(cors.Options{
        AllowedOrigins: []string{"*"},
        AllowCredentials: true,
        Debug: true,
        AllowedHeaders: []string{"Content-Type"},
        AllowedMethods: []string{"GET"},
    }).Handler(r)

    fmt.Println("Working in localhost:5000")
    http.ListenAndServe(":5000", handler)
}

If you run this code, you won't see anything wrong in one connection per time, but if you run it in another tab/browser/etc, at same time, because of the delay, the status code will not be from 1 to 10, but it will be shuffled with all calls.

So I guess that means it's not stateless, and I need it to be, so even with 300 connections at same time, it will always return status code from 1 to 10 in each one.

How can I do it? (As I said, it's a simple code, the structure and the render functions are in separeted packages from each other and of all data collection and)

First I would like to thanks ThunderCat and Ramil for the help, yours answers gave me a north to find the correctly answer.

A short answer is: Go don't have stateless connections, so I can't do what I was looking for.

Once that said, the reason why I think (based on RFC 7230) it doesn't have is because:

  • In a traditional web server application we have a program that handle the connections (Apache, nginx etc) and open a thread to the routed application, while in Go we have both in same application, so anything global are always shared between connections.
  • In languages that may work like Go (the application that opens a port and stay listen it), like C++, they are Object Oriented, so even public variables are inside a class, so you won't share it, since you have to create an instance of the class each time.

Create a thread would resolve the problem, but Go don't have it, instead it have Goroutines, more detail about it in:

https://translate.google.com/translate?sl=ko&tl=en&u=https%3A%2F%2Ftech.ssut.me%2F2017%2F08%2F20%2Fgoroutine-vs-threads%2F

After days on that and the help here, I'll fix it changing my struct to type and put it local, like that:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "github.com/rs/cors"
    "net/http"
    "reflect"
    "time"
)

type Out struct {
    Code     int             `json:"status"`
    Message  []interface{}   `json:"message"`
}

func Clear(v interface{}) {
    p := reflect.ValueOf(v).Elem()
    p.Set(reflect.Zero(p.Type()))
}

func YourHandler(w http.ResponseWriter, r *http.Request) {
    localOut := Out{0,nil}

    // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Headers","Content-Type,access-control-allow-origin, access-control-allow-headers")
    w.WriteHeader(http.StatusOK)

    for i:=0; i<10; i++ {
        localOut.Code = localOut.Code + 1
        localOut.Message = append(localOut.Message, "Running...")
        time.Sleep(1000 * time.Millisecond)

        if err := json.NewEncoder(w).Encode(localOut)
        err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
}

func main() {
    r := mux.NewRouter()
    r.StrictSlash(true);

    r.HandleFunc("/", YourHandler)

    handler := cors.New(cors.Options{
        AllowedOrigins: []string{"*"},
        AllowCredentials: true,
        Debug: true,
        AllowedHeaders: []string{"X-Session-Token","Content-Type"},
        AllowedMethods: []string{"GET","POST","PUT","DELETE"},
    }).Handler(r)

    fmt.Println("Working in localhost:5000")
    http.ListenAndServe(":5000", handler)
}

Of course that will take some weeks, so for now I put my application behind nginx and now it works as expected.

Handlers are called concurrently by the net/http server. The server creates a goroutine for each client connection and calls handlers on those goroutines.

The Gorilla Mux is passive with respect to concurrency. The mux calls through to the registered application handler on whatever goroutine the mux is called on.

Use a sync.Mutex to limit execution to one goroutine at a time:

var mu sync.Mutex

func YourHandler(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    defer mu.Unlock()

    Clear(&Out.Message)
    Out.Code = 0
    ...

This is not a good solution given the time.Sleep calls in the handler. The server will process at most one request every 10 seconds.

A better solution is to declare Out as a local variable inside the handler function. With this change, here's no need for the mutex or to clear Out:

func YourHandler(w http.ResponseWriter, r *http.Request) {

    var Out struct {
        Code     int             `json:"status"`
        Message  []interface{}   `json:"message"`
    }


    // w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    ...

If it's not possible to move the declaration of Out, then copy the value to a local variable:

func YourHandler(w http.ResponseWriter, r *http.Request) {
    Out := Out // local Out is copy of package-level Out
    Clear(&Out.Message)
    Out.Code = 0
    ...

Gorilla Mix uses Go's net/http server to process your http requests. Go creates a Go routine to service each of these incoming requests. If I understand your question correctly, you expect that the Go responses will have your custom status codes in order from 1 to 10 since you were expecting each request coming in synchronously in that order. Go routine's parallelism doesn't guarantee order of execution just like Java threads are if you're familiar with Java. So if Go routines were spawned for each of the requests created in the for 1-to-10 loop then, the routines will execute on its own without regard for order who goes and complete first. Each of these Go routines will serve your requests as it finishes. If you want to control the order of these requests processed in parallel but in order then you can use channels. Look at this link to control synchonization between your 10 Go routines for each of those http requests. https://gobyexample.com/channel-synchronization