常规:Web服务器中的共享全局变量

I have go web server running on port and handling post request which internally calls different url to fetch response using goroutine and proceed.

I have divided the whole flow to different method. Draft of the code.

package main

import (
    "bytes"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "time"
)

var status_codes string

func main() {

    router := mux.NewRouter().StrictSlash(true)
    /*router := NewRouter()*/
    router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, _ = fmt.Fprintf(w, "Hello!!!")
    })

    router.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        prepare(w, r, vars["name"])

    }).Methods("POST")

    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), router))

}

func prepare(w http.ResponseWriter, r *http.Request, name string) {
    //initializing for the current request, need to maintain this variable for each request coming
    status_codes = ""

    //other part of the code and call to goroutine
    var urls []string
    //lets say all the url loaded, call the go routine func and wait for channel to respond and then proceed with the response of all url
    results := callUrls(urls)
    process(w, results)

}

type Response struct {
    status          int
    url             string
    body            string
}

func callUrls(urls []string) []*Response {
    ch := make(chan *Response, len(urls))
    for _, url := range urls {
        go func(url string) {
            //http post on url,
            //base on status code of url call, add to status code
            //some thing like

            req, err := http.NewRequest("POST", url, bytes.NewBuffer(somePostData))
            req.Header.Set("Content-Type", "application/json")
            req.Close = true

            client := &http.Client{
                Timeout: time.Duration(time.Duration(100) * time.Second),
            }

            response, err := client.Do(req)

            if err != nil {
                status_codes += "200,"
                //do other thing with the response received
            } else {
                status_codes += "500,"

            }

            // return to channel accordingly
            ch <- &Response{200, "url", "response body"}

        }(url)
    }
    var results []*Response
    for {
        select {
        case r := <-ch:
            results = append(results, r)
            if len(results) == len(urls) {
                //Done
                close(ch)
                return results
            }

        }
    }
}

func process(w http.ResponseWriter, results []*Response){
    //read those status code received from all urls call for the given request
    fmt.Println("status", status_codes)

    //Now the above line keep getting status code from other request as well
    //for eg. if I have called 5 urls then it should have
    //200,500,204,404,200,

    //but instead it is 
    //200,500,204,404,200,204,404,200,204,404,200, and some more keep growing with time
}

The above code does:

  1. Variable declare globally, Initialized in prepare function.
  2. append value in go routine callUrls function
  3. read those variable in process function

Now should I pass those variable declared globally to each function call to make them local as it won't be shared then?(I would hate to do this.)

Or is there any other approach to achieve the same thing without adding more argument to function being called.

As I will have few other string and int value as well that will be used across the program and in go routine function as well.

What will be the correct way of making them thread safe and only 5 codes for each request coming on port simultaneously.

Don't use global variables, be explicit instead and use function arguments. Moreover, you have a race condition on status_codes because it is accessed by multiple goroutines without any mutex lock.

Take a look at my fix below.

func prepare(w http.ResponseWriter, r *http.Request, name string) {
    var urls []string
    //status_codes is populated by callUris(), so let it return the slice with values
    results, status_codes := callUrls(urls)
    //process() needs status_codes in order to work, so pass the variable explicitely
    process(w, results, status_codes)

}

type Response struct {
    status int
    url    string
    body   string
}

func callUrls(urls []string) []*Response {
    ch := make(chan *Response, len(urls))
    //In order to avoid race condition, let's use a channel
    statusChan := make(chan string, len(urls))
    for _, url := range urls {
        go func(url string) {
            //http post on url,
            //base on status code of url call, add to status code
            //some thing like

            req, err := http.NewRequest("POST", url, bytes.NewBuffer(somePostData))
            req.Header.Set("Content-Type", "application/json")
            req.Close = true

            client := &http.Client{
                Timeout: time.Duration(time.Duration(100) * time.Second),
            }

            response, err := client.Do(req)

            if err != nil {
                statusChan <- "200"
                //do other thing with the response received
            } else {
                statusChan <- "500"

            }

            // return to channel accordingly
            ch <- &Response{200, "url", "response body"}

        }(url)
    }
    var results []*Response
    var status_codes []string
    for !doneRes || !doneStatus { //continue until both slices are filled with values
        select {
        case r := <-ch:
            results = append(results, r)
            if len(results) == len(urls) {
                //Done
                close(ch)      //Not really needed here
                doneRes = true //we are done with results, set the corresponding flag

            }
        case status := <-statusChan:
            status_codes = append(status_codes, status)
            if len(status_codes) == len(urls) {
                //Done
                close(statusChan) //Not really needed here
                doneStatus = true //we are done with statusChan, set the corresponding flag
            }
        }

    }
    return results, status_codes
}

func process(w http.ResponseWriter, results []*Response, status_codes []string) {
    fmt.Println("status", status_codes)
}